pax_global_header00006660000000000000000000000064137025231200014505gustar00rootroot0000000000000052 comment=ee237a4ce4b7660dcbafda789dd6f524a4a4b47d revel-1.0.0/000077500000000000000000000000001370252312000126205ustar00rootroot00000000000000revel-1.0.0/.codebeatsettings000066400000000000000000000004121370252312000161450ustar00rootroot00000000000000{ "GOLANG": { "ABC":[15, 25, 50, 70], "BLOCK_NESTING":[5, 6, 7, 8], "CYCLO":[20, 30, 45, 60], "TOO_MANY_IVARS": [15, 18, 20, 25], "TOO_MANY_FUNCTIONS": [20, 30, 40, 50], "TOTAL_COMPLEXITY": [150, 250, 400, 500], "LOC": [50, 75, 90, 120] } }revel-1.0.0/.gitignore000066400000000000000000000001061370252312000146050ustar00rootroot00000000000000tmp/ routes/ test-results/ revel/revel # editor *.swp .idea/ *.iml revel-1.0.0/.travis.yml000066400000000000000000000031051370252312000147300ustar00rootroot00000000000000language: go go: - "1.12.x" - "1.13.x" - "1.14.x" - "tip" os: - linux - osx - windows sudo: false branches: only: - master - develop services: # github.com/revel/revel/cache - memcache - redis-server before_install: # TRAVIS_OS_NAME - linux and osx - echo $TRAVIS_OS_NAME - echo $PATH - | if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update && brew install memcached redis && brew services start redis && brew services start memcached fi - | if [[ "$TRAVIS_OS_NAME" != "windows" ]]; then redis-server --daemonize yes redis-cli info else # redis-server.exe # redis-cli.exe info echo $PATH fi install: # Setting environments variables - export PATH=$PATH:$HOME/gopath/bin - export REVEL_BRANCH="develop" - 'if [[ "$TRAVIS_BRANCH" == "master" ]]; then export REVEL_BRANCH="master"; fi' - 'echo "Travis branch: $TRAVIS_BRANCH, Revel dependency branch: $REVEL_BRANCH"' - git clone -b $REVEL_BRANCH git://github.com/revel/modules ../modules/ - git clone -b $REVEL_BRANCH git://github.com/revel/cmd ../cmd/ - git clone -b $REVEL_BRANCH git://github.com/revel/config ../config/ - git clone -b $REVEL_BRANCH git://github.com/revel/cron ../cron/ - git clone -b $REVEL_BRANCH git://github.com/revel/examples ../examples/ - go get -t -v github.com/revel/revel/... script: - | if [[ "$TRAVIS_OS_NAME" != "windows" ]]; then go test -v github.com/revel/revel/... else go test -v github.com/revel/revel/. fi matrix: allow_failures: - go: tip - os: windows revel-1.0.0/AUTHORS000066400000000000000000000000531370252312000136660ustar00rootroot00000000000000# TODO Revel Framework Authors Information revel-1.0.0/CHANGELOG.md000066400000000000000000000634241370252312000144420ustar00rootroot00000000000000# CHANGELOG ## v0.19.0 # Release 0.19.0 # Maintenance Release This release is focused on improving the security and resolving some issues. **There are no breaking changes from version 0.18** [[revel/cmd](https://github.com/revel/cmd)] * Improved vendor folder detection revel/cmd#117 * Added ordering of controllers so order remains consistent in main.go revel/cmd#112 * Generate same value of `AppVersion` regardless of where Revel is run revel/cmd#108 * Added referrer policy security header revel/cmd#114 [[revel/modules](https://github.com/revel/modules)] * Added directory representation to static module revel/modules#46 * Gorp enhancements (added abstraction layer for transactions and database connection so both can be used), Added security fix for CSRF module revel/modules#68 * Added authorization configuration options to job page revel/modules#44 [[revel/examples](https://github.com/revel/examples)] * General improvements and examples added revel/examples#39 revel/examples#40 ## v0.18 # Release 0.18 ## Upgrade path The main breaking change is the removal of `http.Request` from the `revel.Request` object. Everything else should just work.... ## New items * Server Engine revel/revel#998 The server engine implementation is described in the [docs](http://revel.github.io/manual/server-engine.html) * Allow binding to a structured map. revel/revel#998 Have a structure inside a map object which will be realized properly from params * Gorm module revel/modules/#51 Added transaction controller * Gorp module revel/modules/#52 * Autorun on startup in develop mode revel/cmd#95 Start the application without doing a request first using revel run .... * Logger update revel/revel#1213 Configurable logger and added context logging on controller via controller.Log * Before after finally panic controller method detection revel/revel#1211 Controller methods will be automatically detected and called - similar to interceptors but without the extra code * Float validation revel/revel#1209 Added validation for floats * Timeago template function revel/revel#1207 Added timeago function to Revel template functions * Authorization to jobs module revel/module#44 Added ability to specify authorization to access the jobs module routes * Add MessageKey, ErrorKey methods to ValidationResult object revel/revel#1215 This allows the message translator to translate the keys added. So model objects can send out validation codes * Vendor friendlier - Revel recognizes and uses `deps` (to checkout go libraries) if a vendor folder exists in the project root. * Updated examples to use Gorp modules and new loggers ### Breaking Changes * `http.Request` is no longer contained in `revel.Request` revel.Request remains functionally the same but you cannot extract the `http.Request` from it. You can get the `http.Request` from `revel.Controller.Request.In.GetRaw().(*http.Request)` * `http.Response.Out` Is not the http.Response and is deprecated, you can get the output writer by doing `http.Response.GetWriter()`. You can get the `http.Response` from revel.Controller.Response.Out.Server.GetRaw().(*http.Response)` * `Websocket` changes. `revel.ServerWebsocket` is the new type of object you need to declare for controllers which should need to attach to websockets. Implementation of these objects have been simplified Old ``` func (c WebSocket) RoomSocket(user string, ws *websocket.Conn) revel.Result { // Join the room. subscription := chatroom.Subscribe() defer subscription.Cancel() chatroom.Join(user) defer chatroom.Leave(user) // Send down the archive. for _, event := range subscription.Archive { if websocket.JSON.Send(ws, &event) != nil { // They disconnected return nil } } // In order to select between websocket messages and subscription events, we // need to stuff websocket events into a channel. newMessages := make(chan string) go func() { var msg string for { err := websocket.Message.Receive(ws, &msg) if err != nil { close(newMessages) return } newMessages <- msg } }() ``` New ``` func (c WebSocket) RoomSocket(user string, ws revel.ServerWebSocket) revel.Result { // Join the room. subscription := chatroom.Subscribe() defer subscription.Cancel() chatroom.Join(user) defer chatroom.Leave(user) // Send down the archive. for _, event := range subscription.Archive { if ws.MessageSendJSON(&event) != nil { // They disconnected return nil } } // In order to select between websocket messages and subscription events, we // need to stuff websocket events into a channel. newMessages := make(chan string) go func() { var msg string for { err := ws.MessageReceiveJSON(&msg) if err != nil { close(newMessages) return } newMessages <- msg } }() ``` * GORM module has been refactored into modules/orm/gorm ### Deprecated methods * `revel.Request.FormValue()` Is deprecated, you should use methods in the controller.Params to access this data * `revel.Request.PostFormValue()` Is deprecated, you should use methods in the controller.Params.Form to access this data * `revel.Request.ParseForm()` Is deprecated - not needed * `revel.Request.ParseMultipartForm()` Is deprecated - not needed * `revel.Request.Form` Is deprecated, you should use the controller.Params.Form to access this data * `revel.Request.MultipartForm` Is deprecated, you should use the controller.Params.Form to access this data * `revel.TRACE`, `revel.INFO` `revel.WARN` `revel.ERROR` are deprecated. Use new application logger `revel.AppLog` and the controller logger `controller.Log`. See [logging](http://revel.github.io/manual/logging.html) for more details. ### Features * Pluggable server engine support. You can now implement **your own server engine**. This means if you need to listen to more then 1 IP address or port you can implement a custom server engine to do this. By default Revel uses GO http server, but also available is fasthttp server in the revel/modules repository. See the docs for more information on how to implement your own engine. ### Enhancements * Controller instances are cached for reuse. This speeds up the request response time and prevents unnecessary garbage collection cycles. ### Bug fixes ## v0.17 [[revel/revel](https://github.com/revel/revel)] * add-validation * i18-lang-by-param * Added namespace to routes, controllers * Added go 1.6 to testing * Adds the ability to set the language by a url parameter. The route file will need to specify the parameter so that it will be picked up * Changed url validation logic to regex * Added new validation mehtods (IPAddr,MacAddr,Domain,URL,PureText) [[revel/cmd](https://github.com/revel/cmd)] * no changes [[revel/config](https://github.com/revel/config)] * no changes [[revel/modules](https://github.com/revel/modules)] * Added Gorm module [[revel/cron](https://github.com/revel/cron)] * Updated cron task manager * Added ability to run a specific job, reschedules job if cron is running. [[revel/examples](https://github.com/revel/examples)] * Gorm module (Example) # v0.16.0 Deprecating support for golang versions prior to 1.6 ### Breaking Changes * `CurrentLocaleRenderArg` to `CurrentLocaleViewArg` for consistency * JSON requests are now parsed by Revel, if the content type is `text/json` or `application/json`. The raw data is available in `Revel.Controller.Params.JSON`. But you can also use the automatic controller operation to load the data like you would any structure or map. See [here](http://revel.github.io/manual/parameters.html) for more details ### Features * Modular Template Engine #1170 * Pongo2 engine driver added revel/modules#39 * Ace engine driver added revel/modules#40 * Added i18n template support #746 ### Enhancements * JSON request binding #1161 * revel.SetSecretKey function added #1127 * ResolveFormat now looks at the extension as well (this sets the content type) #936 * Updated command to run tests using the configuration revel/cmd#61 ### Bug fixes * Updated documentation typos revel/modules#37 * Updated order of parameter map assignment #1155 * Updated cookie lifetime for firefox #1174 * Added test path for modules, so modules will run tests as well #1162 * Fixed go profiler module revel/modules#20 # v0.15.0 @shawncatz released this on 2017-05-11 Deprecating support for golang versions prior to 1.7 ### Breaking Changes * None ### Features * None ### Enhancements * Update and improve docs revel/examples#17 revel/cmd#85 ### Bug fixes * Prevent XSS revel/revel#1153 * Improve error checking for go version detection revel/cmd#86 # v0.14.0 @notzippy released this on 2017-03-24 ## Changes since v0.13.0 #### Breaking Changes - `revel/revel`: - change RenderArgs to ViewArgs PR #1135 - change RenderJson to RenderJSON PR #1057 - change RenderHtml to RenderHTML PR #1057 - change RenderXml to RenderXML PR #1057 #### Features - `revel/revel`: #### Enhancements - `revel/revel`: #### Bug Fixes - `revel/revel`: # v0.13.1 @jeevatkm released this on 2016-06-07 **Bug fix:** - Windows path fix #1064 # v0.13.0 @jeevatkm released this on 2016-06-06 ## Changes since v0.12.0 #### Breaking Changes - `revel/revel`: - Application Config name changed from `watcher.*` to `watch.*` PR #992, PR #991 #### Features - `revel/revel`: - Request access log PR #1059, PR #913, #1055 - Messages loaded from modules too PR #828 - `revel/cmd`: - Added `revel version` command emits the revel version and go version revel/cmd#19 #### Enhancements - `revel/revel`: - Creates log directory if missing PR #1039 - Added `application/javascript` to accepted headers PR #1022 - You can change `Server.Addr` value via hook function PR #999 - Improved deflate/gzip compressor PR #995 - Consistent config name `watch.*` PR #992, PR #991 - Defaults to HttpOnly and always secure cookies for non-dev mode #942, PR #943 - Configurable server Read and Write Timeout via app config #936, PR #940 - `OnAppStart` hook now supports order param too PR #935 - Added `PutForm` and `PutFormCustom` helper method in `testing.TestSuite` #898 - Validator supports UTF-8 string too PR #891, #841 - Added `InitServer` method that returns `http.HandlerFunc` PR #879 - Symlink aware processing Views, Messages and Watch mode PR #867, #673 - Added i18n settings support unknown format PR #852 - i18n: Make Message Translation pluggable PR #768 - jQuery `min-2.2.4` & Bootstrap `min-3.3.6` version updated in `skeleton/public` #1063 - `revel/cmd`: - Revel identifies current `GOPATH` and performs `new` command; relative to directory revel/revel#1004 - Installs package dependencies during a build PR revel/cmd#43 - Non-200 response of test case request will correctly result into error PR revel/cmd#38 - Websockets SSL support in `dev` mode PR revel/cmd#32 - Won't yell about non-existent directory while cleaning PR revel/cmd#31, #908 - [x] non-fatal errors when building #908 - Improved warnings about route generation PR revel/cmd#25 - Command is Symlink aware PR revel/cmd#20 - `revel package` & `revel build` now supports environment mode PR revel/cmd#14 - `revel clean` now cleans generated routes too PR revel/cmd#6 - `revel/config`: - Upstream `robfig/config` refresh and import path updated from `github.com/revel/revel/config` to `github.com/revel/config`, PR #868 - Config loading order and external configuration to override application configuration revel/config#4 [commit](https://github.com/revel/revel/commit/f3a422c228994978ae0a5dd837afa97248b26b41) - Application config error will produce insight on error PR revel/config#3 [commit](https://github.com/revel/config/commit/85a123061070899a82f59c5ef6187e8fb4457f64) - `revel/modules`: - Testrunner enhancements - Minor improvement on testrunner module PR #820, #895 - Add Test Runner panels per test group PR revel/modules#12 - `revel/revel.github.io`: - Update `index.md` and homepage (change how samples repo is installed) PR [#85](https://github.com/revel/revel.github.io/pull/85) - Couple of UI improvements PR [#93](https://github.com/revel/revel.github.io/pull/93) - Updated techempower benchmarks Round 11 [URL](http://www.techempower.com/benchmarks/#section=data-r11) - Docs updated for v0.13 release - Cross-Platform Support - Slashes should be normalized in paths #260, PR #1028, PR #928 #### Bug Fixes - `revel/revel`: - Binder: Multipart `io.Reader` parameters needs to be closed #756 - Default Date & Time Format correct in skeleton PR #1062, #878 - Addressed with alternative for `json: unsupported type: <-chan struct {}` on Go 1.6 revel/revel#1037 - Addressed one edge case, invalid Accept-Encoding header causes panic revel/revel#914 # v0.11.3 @brendensoares released this on 2015-01-04 This is a minor release to address a critical bug (#824) in v0.11.2. Everybody is strongly encouraged to rebuild their projects with the latest version of Revel. To do it, execute the commands: ``` sh $ go get -u github.com/revel/cmd/revel $ revel build github.com/myusername/myproject /path/to/destination/folder ``` # v0.11.2 on 2014-11-23 This is a minor release to address a critical bug in v0.11.0. Everybody is strongly encouraged to rebuild their projects with the latest version of Revel. To do it, execute the commands: ``` sh $ go get -u github.com/revel/cmd/revel $ revel build github.com/myusername/myproject /path/to/destination/folder ``` # v0.11.1 @pushrax released this on 2014-10-27 This is a minor release to address a compilation error in v0.11.0. # v0.12.0 @brendensoares released this on 2015-03-25 Changes since v0.11.3: ## Breaking Changes 1. Add import path to new `testing` sub-package for all Revel tests. For example: ``` go package tests import "github.com/revel/revel/testing" type AppTest struct { testing.TestSuite } ``` 1. We've relocated modules to a dedicated repo. Make sure you update your `conf/app.conf`. For example, change: ``` ini module.static=github.com/revel/revel/modules/static module.testrunner = github.com/revel/revel/modules/testrunner ``` to the new paths: ``` ini module.static=github.com/revel/modules/static module.testrunner = github.com/revel/modules/testrunner ``` ## [ROADMAP] Focus: Improve Internal Organization The majority of our effort here is increasing the modularity of the code within Revel so that further development can be done more productively while keeping documentation up to date. - `revel/revel.github.io` - [x] Improve docs #[43](https://github.com/revel/revel.github.io/pull/43) - `revel/revel`: - [x] Move the `revel/revel/harness` to the `revel/cmd` repo since it's only used during build time. #[714](https://github.com/revel/revel/issues/714) - [x] Move `revel/revel/modules` to the `revel/modules` repo #[785](https://github.com/revel/revel/issues/785) - [x] Move `revel/revel/samples` to the `revel/samples` repo #[784](https://github.com/revel/revel/issues/784) - [x] `testing` TestSuite #[737](https://github.com/revel/revel/issues/737) #[810](https://github.com/revel/revel/issues/810) - [x] Feature/sane http timeout defaults #[837](https://github.com/revel/revel/issues/837) PR#[843](https://github.com/revel/revel/issues/843) Bug Fix PR#[860](https://github.com/revel/revel/issues/860) - [x] Eagerly load templates in dev mode #[353](https://github.com/revel/revel/issues/353) PR#[844](https://github.com/revel/revel/pull/844) - [x] Add an option to trim whitespace from rendered HTML #[800](https://github.com/revel/revel/issues/800) - [x] Remove built-in mailer in favor of 3rd party package #[783](https://github.com/revel/revel/issues/783) - [x] Allow local reverse proxy access to jobs module status page for IPv4/6 #[481](https://github.com/revel/revel/issues/481) PR#[6](https://github.com/revel/modules/pull/6) PR#[7](https://github.com/revel/modules/pull/7) - [x] Add default http.Status code for render methods. #[728](https://github.com/revel/revel/issues/728) - [x] add domain for cookie #[770](https://github.com/revel/revel/issues/770) PR#[882](https://github.com/revel/revel/pull/882) - [x] production mode panic bug #[831](https://github.com/revel/revel/issues/831) PR#[881](https://github.com/revel/revel/pull/881) - [x] Fixes template loading order whether watcher is enabled or not #[844](https://github.com/revel/revel/issues/844) - [x] Fixes reverse routing wildcard bug PR#[886](https://github.com/revel/revel/pull/886) #[869](https://github.com/revel/revel/issues/869) - [x] Fixes router app start bug without routes. PR #[855](https://github.com/revel/revel/pull/855) - [x] Friendly URL template errors; Fixes template `url` func "index out of range" when param is `undefined` #[811](https://github.com/revel/revel/issues/811) PR#[880](https://github.com/revel/revel/pull/880) - [x] Make result compression conditional PR#[888](https://github.com/revel/revel/pull/888) - [x] ensure routes are loaded before returning from OnAppStart callback PR#[884](https://github.com/revel/revel/pull/884) - [x] Use "302 Found" HTTP code for redirect PR#[900](https://github.com/revel/revel/pull/900) - [x] Fix broken fake app tests PR#[899](https://github.com/revel/revel/pull/899) - [x] Optimize search of template names PR#[885](https://github.com/revel/revel/pull/885) - `revel/cmd`: - [x] track current Revel version #[418](https://github.com/revel/revel/issues/418) PR#[858](https://github.com/revel/revel/pull/858) - [x] log path error After revel build? #[763](https://github.com/revel/revel/issues/763) - [x] Use a separate directory for revel project binaries #[17](https://github.com/revel/cmd/pull/17) #[819](https://github.com/revel/revel/issues/819) - [x] Overwrite generated app files instead of deleting directory #[551](https://github.com/revel/revel/issues/551) PR#[23](https://github.com/revel/cmd/pull/23) - `revel/modules`: - [x] Adds runtime pprof/trace support #[9](https://github.com/revel/modules/pull/9) - Community Goals: - [x] Issue labels #[545](https://github.com/revel/revel/issues/545) - [x] Sync up labels/milestones in other repos #[721](https://github.com/revel/revel/issues/721) - [x] Update the Revel Manual to reflect current features - [x] [revel/revel.github.io/32](https://github.com/revel/revel.github.io/issues/32) - [x] [revel/revel.github.io/39](https://github.com/revel/revel.github.io/issues/39) - [x] Docs are obsolete, inaccessible TestRequest.testSuite #[791](https://github.com/revel/revel/issues/791) - [x] Some questions about revel & go docs #[793](https://github.com/revel/revel/issues/793) - [x] RFCs to organize features #[827](https://github.com/revel/revel/issues/827) [Full list of commits](https://github.com/revel/revel/compare/v0.11.3...v0.12.0) # v0.11.0 @brendensoares released this on 2014-10-26 Note, Revel 0.11 requires Go 1.3 or higher. Changes since v0.10: [BUG] #729 Adding define inside the template results in an error (Changes how template file name case insensitivity is handled) [ENH] #769 Add swap files to gitignore [ENH] #766 Added passing in build flags to the go build command [ENH] #761 Fixing cross-compiling issue #456 setting windows path from linux [ENH] #759 Include upload sample's tests in travis [ENH] #755 Changes c.Action to be the action method name's letter casing per #635 [ENH] #754 Adds call stack display to runtime panic in browser to match console [ENH] #740 Redis Cache: Add timeouts. [ENH] #734 watcher: treat fsnotify Op as a bitmask [ENH] #731 Second struct in type revel fails to find the controller [ENH] #725 Testrunner: show response info [ENH] #723 Improved compilation errors and open file from error page [ENH] #720 Get testrunner path from config file [ENH] #707 Add log.colorize option to enable/disable colorize [ENH] #696 Revel file upload testing [ENH] #694 Install dependencies at build time [ENH] #693 Prefer extension over Accept header [ENH] #692 Update fsnotify to v1 API [ENH] #690 Support zero downtime restarts [ENH] #687 Tests: request override [ENH] #685 Persona sample tests and bugfix [ENH] #598 Added README file to Revel skeleton [ENH] #591 Realtime rebuild [ENH] #573 Add AppRoot to allow changing the root path of an application [FTR] #606 CSRF Support [Full list of commits](https://github.com/revel/revel/compare/v0.10.0...v0.11.0) # v0.10.0 @brendensoares released this on 2014-08-10 Changes since v0.9.1: - [FTR] #641 - Add "X-HTTP-Method-Override" to router - [FTR] #583 - Added HttpMethodOverride filter to routes - [FTR] #540 - watcher flag for refresh on app start - [BUG] #681 - Case insensitive comparison for websocket upgrades (Fixes IE Websockets ... - [BUG] #668 - Compression: Properly close gzip/deflate - [BUG] #667 - Fix redis GetMulti and improve test coverage - [BUG] #664 - Is compression working correct? - [BUG] #657 - Redis Cache: panic when testing Ge - [BUG] #637 - RedisCache: fix Get/GetMulti error return - [BUG] #621 - Bugfix/router csv error - [BUG] #618 - Router throws exception when parsing line with multiple default string arguments - [BUG] #604 - Compression: Properly close gzip/deflate. - [BUG] #567 - Fixed regex pattern to properly require message files to have a dot in filename - [BUG] #566 - Compression fails ("unexpected EOF" in tests) - [BUG] #287 - Don't remove the parent folders containing generated code. - [BUG] #556 - fix for #534, also added url path to not found message - [BUG] #534 - Websocket route not found - [BUG] #343 - validation.Required(funtionCall).Key(...) - reflect.go:715: Failed to generate name for field. - [ENH] #643 - Documentation Fix in Skeleton for OnAppStart - [ENH] #674 - Removes custom `eq` template function - [ENH] #669 - Develop compress closenotifier - [ENH] #663 - fix for static content type not being set and defaulting to OS - [ENH] #658 - Minor: fix niggle with import statement - [ENH] #652 - Update the contributing guidelines - [ENH] #651 - Use upstream gomemcache again - [ENH] #650 - Go back to upstream memcached library - [ENH] #612 - Fix CI package error - [ENH] #611 - Fix "go vet" problems - [ENH] #610 - Added MakeMultipartRequest() to the TestSuite - [ENH] #608 - Develop compress closenotifier - [ENH] #596 - Expose redis cache options to config - [ENH] #581 - Make the option template tag type agnostic. - [ENH] #576 - Defer session instantiation to first set - [ENH] #565 - Fix #563 -- Some custom template funcs cannot be used in JavaScript cont... - [ENH] #563 - TemplateFuncs cannot be used in JavaScript context - [ENH] #561 - Fix missing extension from message file causing panic - [ENH] #560 - enhancement / templateFunc `firstof` - [ENH] #555 - adding symlink handling to the template loader and watcher processes - [ENH] #531 - Update app.conf.template - [ENH] #520 - Respect controller's Response.Status when action returns nil - [ENH] #519 - Link to issues - [ENH] #486 - Support for json compress - [ENH] #480 - Eq implementation in template.go still necessary ? - [ENH] #461 - Cron jobs not started until I pull a page - [ENH] #323 - disable session/set-cookie for `Static.Serve()` [Full list of commits](https://github.com/revel/revel/compare/v0.9.1...v0.10.0) # v0.9.1 @pushrax released this on 2014-03-02 Minor patch release to address a couple bugs. Changes since v0.9.0: - [BUG] #529 - Wrong path was used to determine existence of `.git` - [BUG] #532 - Fix typo for new type `ValidEmail` The full list of commits can be found [here](https://github.com/revel/revel/compare/v0.9.0...v0.9.1). # v0.9.0 @pushrax released this on 2014-02-26 ## Revel GitHub Organization We've moved development of the framework to the @revel GitHub organization, to help manage the project as Revel grows. The old import path is still valid, but will not be updated in the future. You'll need to manually update your apps to work with the new import path. This can be done by replacing all instances of `github.com/robfig/revel` with `github.com/revel/revel` in your app, and running: ``` $ cd your_app_folder $ go get -u github.com/howeyc/fsnotify # needs updating $ go get github.com/revel/revel $ go get github.com/revel/cmd/revel # command line tools have moved ``` **Note:** if you have references to `github.com/robfig/revel/revel` in any files, you need to replace them with `github.com/revel/cmd/revel` _before_ replacing `github.com/robfig/revel`! (note the prefix collision) If you have any trouble upgrading or notice something we missed, feel free to hop in the IRC channel (#revel on Freenode) or send the mailing list a message. Also note, the documentation is now at [revel.github.io](http://revel.github.io)! Changes since v0.8: - [BUG] #522 - `revel new` bug - [BUG] - Booking sample error - [BUG] #504 - File access via URL security issue - [BUG] #489 - Email validator bug - [BUG] #475 - File watcher infinite loop - [BUG] #333 - Extensions in routes break parameters - [FTR] #472 - Support for 3rd part app skeletons - [ENH] #512 - Per session expiration methods - [ENH] #496 - Type check renderArgs[CurrentLocalRenderArg] - [ENH] #490 - App.conf manual typo - [ENH] #487 - Make files executable on `revel build` - [ENH] #482 - Retain input values after form valdiation - [ENH] #473 - OnAppStart documentation - [ENH] #466 - JSON error template quoting fix - [ENH] #464 - Remove unneeded trace statement - [ENH] #457 - Remove unneeded trace - [ENH] #508 - Support arbitrary network types - [ENH] #516 - Add Date and Message-Id mail headers The full list of commits can be found [here](https://github.com/revel/revel/compare/v0.8...v0.9.0). # v0.8 @pushrax released this on 2014-01-06 Changes since v0.7: - [BUG] #379 - HTTP 500 error for not found public path files - [FTR] #424 - HTTP pprof support - [FTR] #346 - Redis Cache support - [FTR] #292 - SMTP Mailer - [ENH] #443 - Validator constructors to improve `v.Check()` usage - [ENH] #439 - Basic terminal output coloring - [ENH] #428 - Improve error message for missing `RenderArg` - [ENH] #422 - Route embedding for modules - [ENH] #413 - App version variable - [ENH] #153 - $GOPATH-wide file watching aka hot loading # v0.6 @robfig released this on 2013-09-16 revel-1.0.0/CONTRIBUTING.md000066400000000000000000000154631370252312000150620ustar00rootroot00000000000000## Contributing to Revel This describes how developers may contribute to Revel. ## Mission Revel's mission is to provide a batteries-included framework for making large scale web application development as efficient and maintainable as possible. The design should be configurable and modular so that it can grow with the developer. However, it should provide a wonderful un-boxing experience and default configuration that can woo new developers and make simple web apps straight forward. The framework should have an opinion about how to do all of the common tasks in web development to reduce unnecessary cognitive load. Perhaps most important of all, Revel should be a joy to use. We want to reduce the time spent on tedious boilerplate functionality and increase the time available for creating polished solutions for your application's target users. ## How to Contribute ### Join the Community The first step to making Revel better is joining the community! You can find the community on: * [Google Groups](https://groups.google.com/forum/#!forum/revel-framework) via [revel-framework@googlegroups.com](mailto:revel-framework@googlegroups.com) * [GitHub Issues](https://github.com/revel/revel/issues) * [StackOverflow Questions](http://stackoverflow.com/questions/tagged/revel) * [IRC](http://webchat.freenode.net/?channels=%23revel&uio=d4) via #revel on Freenode Once you've joined, there are many ways to contribute to Revel: * Report bugs (via GitHub) * Answer questions of other community members (via Google Groups or IRC) * Give feedback on new feature discussions (via GitHub and Google Groups) * Propose your own ideas (via Google Groups or GitHub) ### How Revel is Developed We have begun to formalize the development process by adopting pragmatic practices such as: * Developing on the `develop` branch * Merging `develop` branch to `master` branch in 6 week iterations * Tagging releases with MAJOR.MINOR syntax (e.g. v0.8) ** We may also tag MAJOR.MINOR.HOTFIX releases as needed (e.g. v0.8.1) to address urgent bugs. Such releases will not introduce or change functionality * Managing bugs, enhancements, features and release milestones via GitHub's Issue Tracker * Using feature branches to create pull requests * Discussing new features **before** hacking away at it ### How to Correctly Fork Go uses the repository URL to import packages, so forking and `go get`ing the forked project **will not work**. Instead, follow these steps: 1. Install Revel normally 2. Fork Revel on GitHub 3. Add your fork as a git remote Here's the commands to do so: ``` $ go get github.com/revel/revel # Install Revel $ cd $GOPATH/src/github.com/revel/revel # Change directory to revel repo $ git remote add fork git@github.com:$USER/revel.git # Add your fork as a remote, where $USER is your GitHub username ``` ### Create a Feature Branch & Code Away! Now that you've properly installed and forked Revel, you are ready to start coding (assuming you have a validated your ideas with other community members)! In order to have your pull requests accepted, we recommend you make your changes to Revel on a new git branch. For example, ``` $ git checkout -b feature/useful-new-thing origin/develop # Create a new branch based on develop and switch to it $ ... # Make your changes and commit them $ git push fork feature/useful-new-thing # After new commits, push to your fork ``` ### Format Your Code Remember to run `go fmt` before committing your changes. Many Go developers opt to have their editor run `go fmt` automatically when saving Go files. Additionally, follow the [core Go style conventions](https://code.google.com/p/go-wiki/wiki/CodeReviewComments) to have your pull requests accepted. ### Write Tests (and Benchmarks for Bonus Points) Significant new features require tests. Besides unit tests, it is also possible to test a feature by exercising it in one of the sample apps and verifying its operation using that app's test suite. This has the added benefit of providing example code for developers to refer to. Benchmarks are helpful but not required. ### Run the Tests Typically running the main set of unit tests will be sufficient: ``` $ go test github.com/revel/revel ``` Refer to the [Travis configuration](https://github.com/revel/revel/blob/master/.travis.yml) for the full set of tests. They take less than a minute to run. ### Document Your Feature Due to the wide audience and shared nature of Revel, documentation is an essential addition to your new code. **Pull requests risk not being accepted** until proper documentation is created to detail how to make use of new functionality. The [Revel web site](http://revel.github.io/) is hosted on GitHub Pages and [built with Jekyll](https://help.github.com/articles/using-jekyll-with-pages). To develop the Jekyll site locally: # Clone the documentation repository $ git clone git@github.com:revel/revel.github.io $ cd revel.github.io # Install and run Jekyll to generate and serve the site $ gem install jekyll kramdown $ jekyll serve --watch # Now load in your browser $ open http://localhost:4000/ Any changes you make to the site should be reflected within a few seconds. ### Submit Pull Request Once you've done all of the above & pushed your changes to your fork, you can create a pull request for review and acceptance. ## Potential Projects These are outstanding feature requests, roughly ordered by priority. Additionally, there are frequently smaller feature requests or items in the [issues](https://github.com/revel/revel/issues?labels=contributor+ready&page=1&state=open). 1. Better ORM support. Provide more samples (or modules) and better documentation for setting up common situations like SQL database, Mongo, LevelDB, etc. 1. Support for other templating languages (e.g. mustache, HAML). Make TemplateLoader pluggable. Use Pongo instead of vanilla Go templates (and update the samples) 1. Test Fixtures 1. Authenticity tokens for CSRF protection 1. Coffeescript pre-processor. Could potentially use [otto](https://github.com/robertkrimen/otto) as a native Go method to compiling. 1. SCSS/LESS pre-processor. 1. GAE support. Some progress made in the 'appengine' branch -- the remaining piece is running the appengine services in development. 1. More Form helpers (template funcs). 1. A Mongo module (perhaps with a sample app) 1. Deployment to OpenShift (support, documentation, etc) 1. Improve the logging situation. The configuration is a little awkward and not very powerful. Integrating something more powerful would be good. (like [seelog](https://github.com/cihub/seelog) or [log4go](https://code.google.com/p/log4go/)) 1. ETags, cache controls 1. A module or plugins for adding HTTP Basic Auth 1. Allowing the app to hook into the source code processing step revel-1.0.0/LICENSE000066400000000000000000000021141370252312000136230ustar00rootroot00000000000000The MIT License (MIT) Copyright (C) 2012-2018 The Revel Framework Authors. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. revel-1.0.0/README.md000066400000000000000000000042421370252312000141010ustar00rootroot00000000000000# Revel Framework [![Build Status](https://secure.travis-ci.org/revel/revel.svg?branch=master)](http://travis-ci.org/revel/revel) [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![Go Report Card](https://goreportcard.com/badge/github.com/revel/revel)](https://goreportcard.com/report/github.com/revel/revel) A high productivity, full-stack web framework for the [Go language](http://www.golang.org). Current Version: 1.0.0 (2020-07-11) **Supports go.mod package management** ## Quick Start Install Revel: go get -u github.com/revel/cmd/revel Create & Run your app: revel new -a my-app -r Open http://localhost:9000 in your browser and you should see "It works!" ## Community * [Gitter](https://gitter.im/revel/community) * [StackOverflow](http://stackoverflow.com/questions/tagged/revel) ## Learn More * [Manual, Samples, Godocs, etc](http://revel.github.io) * [Apps using Revel](https://github.com/revel/revel/wiki/Apps-in-the-Wild) * [Articles Featuring Revel](https://github.com/revel/revel/wiki/Articles) ## Contributing * [Contributing Code Guidelines](https://github.com/revel/revel/blob/master/CONTRIBUTING.md) * [Revel Contributors](https://github.com/revel/revel/graphs/contributors) ## Contributors [![](https://sourcerer.io/fame/notzippy/revel/revel/images/0)](https://sourcerer.io/fame/notzippy/revel/revel/links/0) [![](https://sourcerer.io/fame/notzippy/revel/revel/images/1)](https://sourcerer.io/fame/notzippy/revel/revel/links/1) [![](https://sourcerer.io/fame/notzippy/revel/revel/images/2)](https://sourcerer.io/fame/notzippy/revel/revel/links/2) [![](https://sourcerer.io/fame/notzippy/revel/revel/images/3)](https://sourcerer.io/fame/notzippy/revel/revel/links/3) [![](https://sourcerer.io/fame/notzippy/revel/revel/images/4)](https://sourcerer.io/fame/notzippy/revel/revel/links/4) [![](https://sourcerer.io/fame/notzippy/revel/revel/images/5)](https://sourcerer.io/fame/notzippy/revel/revel/links/5) [![](https://sourcerer.io/fame/notzippy/revel/revel/images/6)](https://sourcerer.io/fame/notzippy/revel/revel/links/6) [![](https://sourcerer.io/fame/notzippy/revel/revel/images/7)](https://sourcerer.io/fame/notzippy/revel/revel/links/7) revel-1.0.0/before_after_filter.go000066400000000000000000000030041370252312000171340ustar00rootroot00000000000000package revel import ( "reflect" ) // Autocalls any defined before and after methods on the target controller // If either calls returns a value then the result is returned func BeforeAfterFilter(c *Controller, fc []Filter) { defer func() { if resultValue := beforeAfterFilterInvoke(FINALLY, c); resultValue != nil && !resultValue.IsNil() { c.Result = resultValue.Interface().(Result) } }() defer func() { if err := recover(); err != nil { if resultValue := beforeAfterFilterInvoke(PANIC, c); resultValue != nil && !resultValue.IsNil() { c.Result = resultValue.Interface().(Result) } panic(err) } }() if resultValue := beforeAfterFilterInvoke(BEFORE, c); resultValue != nil && !resultValue.IsNil() { c.Result = resultValue.Interface().(Result) return } fc[0](c, fc[1:]) if resultValue := beforeAfterFilterInvoke(AFTER, c); resultValue != nil && !resultValue.IsNil() { c.Result = resultValue.Interface().(Result) } } func beforeAfterFilterInvoke(method When, c *Controller) (r *reflect.Value) { if c.Type == nil { return } var index []*ControllerFieldPath switch method { case BEFORE: index = c.Type.ControllerEvents.Before case AFTER: index = c.Type.ControllerEvents.After case FINALLY: index = c.Type.ControllerEvents.Finally case PANIC: index = c.Type.ControllerEvents.Panic } if len(index) == 0 { return } for _, function := range index { result := function.Invoke(reflect.ValueOf(c.AppController), nil)[0] if !result.IsNil() { return &result } } return } revel-1.0.0/before_after_filter_test.go000066400000000000000000000021651370252312000202020ustar00rootroot00000000000000// Copyright (c) 2019 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package revel import ( "testing" ) type bafTestController struct { *Controller } func (c bafTestController) Before() (Result, bafTestController) { return c.Redirect("http://www.example.com"), c } func (c bafTestController) Index() Result { // We shouldn't get here panic("Should not be called") } type failingFilter struct { t *testing.T } func (f failingFilter) FailIfCalled(c *Controller, filterChain []Filter) { f.t.Error("Filter should not have been called") } func TestInterceptorsNotCalledIfBeforeReturns(t *testing.T) { Init("prod", "github.com/revel/revel/testdata", "") controllers = make(map[string]*ControllerType) RegisterController((*bafTestController)(nil), []*MethodType{ { Name: "Before", }, { Name: "Index", }, }) c := NewControllerEmpty() err := c.SetAction("bafTestController", "Index") if err != nil { t.Error(err.Error()) } BeforeAfterFilter(c, []Filter{failingFilter{t}.FailIfCalled}) } revel-1.0.0/binder.go000066400000000000000000000403701370252312000144160ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package revel import ( "encoding/json" "fmt" "io" "io/ioutil" "mime/multipart" "os" "reflect" "strconv" "strings" "time" ) // A Binder translates between string parameters and Go data structures. type Binder struct { // Bind takes the name and type of the desired parameter and constructs it // from one or more values from Params. // // Example // // Request: // url?id=123&ol[0]=1&ol[1]=2&ul[]=str&ul[]=array&user.Name=rob // // Action: // Example.Action(id int, ol []int, ul []string, user User) // // Calls: // Bind(params, "id", int): 123 // Bind(params, "ol", []int): {1, 2} // Bind(params, "ul", []string): {"str", "array"} // Bind(params, "user", User): User{Name:"rob"} // // Note that only exported struct fields may be bound. Bind func(params *Params, name string, typ reflect.Type) reflect.Value // Unbind serializes a given value to one or more URL parameters of the given // name. Unbind func(output map[string]string, name string, val interface{}) } var binderLog = RevelLog.New("section", "binder") // ValueBinder is adapter for easily making one-key-value binders. func ValueBinder(f func(value string, typ reflect.Type) reflect.Value) func(*Params, string, reflect.Type) reflect.Value { return func(params *Params, name string, typ reflect.Type) reflect.Value { vals, ok := params.Values[name] if !ok || len(vals) == 0 { return reflect.Zero(typ) } return f(vals[0], typ) } } // Revel's default date and time constants const ( DefaultDateFormat = "2006-01-02" DefaultDateTimeFormat = "2006-01-02 15:04" ) // Binders type and kind definition var ( // These are the lookups to find a Binder for any type of data. // The most specific binder found will be used (Type before Kind) TypeBinders = make(map[reflect.Type]Binder) KindBinders = make(map[reflect.Kind]Binder) // Applications can add custom time formats to this array, and they will be // automatically attempted when binding a time.Time. TimeFormats = []string{} DateFormat string DateTimeFormat string TimeZone = time.UTC IntBinder = Binder{ Bind: ValueBinder(func(val string, typ reflect.Type) reflect.Value { if len(val) == 0 { return reflect.Zero(typ) } intValue, err := strconv.ParseInt(val, 10, 64) if err != nil { binderLog.Warn("IntBinder Conversion Error", "error", err) return reflect.Zero(typ) } pValue := reflect.New(typ) pValue.Elem().SetInt(intValue) return pValue.Elem() }), Unbind: func(output map[string]string, key string, val interface{}) { output[key] = fmt.Sprintf("%d", val) }, } UintBinder = Binder{ Bind: ValueBinder(func(val string, typ reflect.Type) reflect.Value { if len(val) == 0 { return reflect.Zero(typ) } uintValue, err := strconv.ParseUint(val, 10, 64) if err != nil { binderLog.Warn("UintBinder Conversion Error", "error", err) return reflect.Zero(typ) } pValue := reflect.New(typ) pValue.Elem().SetUint(uintValue) return pValue.Elem() }), Unbind: func(output map[string]string, key string, val interface{}) { output[key] = fmt.Sprintf("%d", val) }, } FloatBinder = Binder{ Bind: ValueBinder(func(val string, typ reflect.Type) reflect.Value { if len(val) == 0 { return reflect.Zero(typ) } floatValue, err := strconv.ParseFloat(val, 64) if err != nil { binderLog.Warn("FloatBinder Conversion Error", "error", err) return reflect.Zero(typ) } pValue := reflect.New(typ) pValue.Elem().SetFloat(floatValue) return pValue.Elem() }), Unbind: func(output map[string]string, key string, val interface{}) { output[key] = fmt.Sprintf("%f", val) }, } StringBinder = Binder{ Bind: ValueBinder(func(val string, typ reflect.Type) reflect.Value { return reflect.ValueOf(val) }), Unbind: func(output map[string]string, name string, val interface{}) { output[name] = val.(string) }, } // Booleans support a various value formats, // refer `revel.Atob` method. BoolBinder = Binder{ Bind: ValueBinder(func(val string, typ reflect.Type) reflect.Value { return reflect.ValueOf(Atob(val)) }), Unbind: func(output map[string]string, name string, val interface{}) { output[name] = fmt.Sprintf("%t", val) }, } PointerBinder = Binder{ Bind: func(params *Params, name string, typ reflect.Type) reflect.Value { v := Bind(params, name, typ.Elem()) if v.CanAddr() { return v.Addr() } return v }, Unbind: func(output map[string]string, name string, val interface{}) { Unbind(output, name, reflect.ValueOf(val).Elem().Interface()) }, } TimeBinder = Binder{ Bind: ValueBinder(func(val string, typ reflect.Type) reflect.Value { for _, f := range TimeFormats { if r, err := time.ParseInLocation(f, val, TimeZone); err == nil { return reflect.ValueOf(r) } } return reflect.Zero(typ) }), Unbind: func(output map[string]string, name string, val interface{}) { var ( t = val.(time.Time) format = DateTimeFormat h, m, s = t.Clock() ) if h == 0 && m == 0 && s == 0 { format = DateFormat } output[name] = t.Format(format) }, } MapBinder = Binder{ Bind: bindMap, Unbind: unbindMap, } ) // Used to keep track of the index for individual keyvalues. type sliceValue struct { index int // Index extracted from brackets. If -1, no index was provided. value reflect.Value // the bound value for this slice element. } // This function creates a slice of the given type, Binds each of the individual // elements, and then sets them to their appropriate location in the slice. // If elements are provided without an explicit index, they are added (in // unspecified order) to the end of the slice. func bindSlice(params *Params, name string, typ reflect.Type) reflect.Value { // Collect an array of slice elements with their indexes (and the max index). maxIndex := -1 numNoIndex := 0 sliceValues := []sliceValue{} maxIndexBound := Config.IntDefault("params.max_index", 4096) // Factor out the common slice logic (between form values and files). processElement := func(key string, vals []string, files []*multipart.FileHeader) { if !strings.HasPrefix(key, name+"[") { return } // Extract the index, and the index where a sub-key starts. (e.g. field[0].subkey) index := -1 leftBracket, rightBracket := len(name), strings.Index(key[len(name):], "]")+len(name) if rightBracket > leftBracket+1 { index, _ = strconv.Atoi(key[leftBracket+1 : rightBracket]) } subKeyIndex := rightBracket + 1 // Handle the indexed case. if index > -1 { // Just ignore illegal index, fix issue #1424 if index > maxIndexBound { binderLog.Error("Ignoring parameter for security reason", "index", index, "key", key) return } if index > maxIndex { maxIndex = index } sliceValues = append(sliceValues, sliceValue{ index: index, value: Bind(params, key[:subKeyIndex], typ.Elem()), }) return } // It's an un-indexed element. (e.g. element[]) numNoIndex += len(vals) + len(files) for _, val := range vals { // Unindexed values can only be direct-bound. sliceValues = append(sliceValues, sliceValue{ index: -1, value: BindValue(val, typ.Elem()), }) } for _, fileHeader := range files { sliceValues = append(sliceValues, sliceValue{ index: -1, value: BindFile(fileHeader, typ.Elem()), }) } } for key, vals := range params.Values { processElement(key, vals, nil) } for key, fileHeaders := range params.Files { processElement(key, nil, fileHeaders) } resultArray := reflect.MakeSlice(typ, maxIndex+1, maxIndex+1+numNoIndex) for _, sv := range sliceValues { if sv.index != -1 { resultArray.Index(sv.index).Set(sv.value) } else { resultArray = reflect.Append(resultArray, sv.value) } } return resultArray } // Break on dots and brackets. // e.g. bar => "bar", bar.baz => "bar", bar[0] => "bar" func nextKey(key string) string { fieldLen := strings.IndexAny(key, ".[") if fieldLen == -1 { return key } return key[:fieldLen] } func unbindSlice(output map[string]string, name string, val interface{}) { v := reflect.ValueOf(val) for i := 0; i < v.Len(); i++ { Unbind(output, fmt.Sprintf("%s[%d]", name, i), v.Index(i).Interface()) } } func bindStruct(params *Params, name string, typ reflect.Type) reflect.Value { resultPointer := reflect.New(typ) result := resultPointer.Elem() if params.JSON != nil { // Try to inject the response as a json into the created result if err := json.Unmarshal(params.JSON, resultPointer.Interface()); err != nil { binderLog.Error("bindStruct Unable to unmarshal request", "name", name, "error", err, "data", string(params.JSON)) } return result } fieldValues := make(map[string]reflect.Value) for key := range params.Values { if !strings.HasPrefix(key, name+".") { continue } // Get the name of the struct property. // Strip off the prefix. e.g. foo.bar.baz => bar.baz suffix := key[len(name)+1:] fieldName := nextKey(suffix) fieldLen := len(fieldName) if _, ok := fieldValues[fieldName]; !ok { // Time to bind this field. Get it and make sure we can set it. fieldValue := result.FieldByName(fieldName) if !fieldValue.IsValid() { binderLog.Warn("bindStruct Field not found", "name", fieldName) continue } if !fieldValue.CanSet() { binderLog.Warn("bindStruct Field not settable", "name", fieldName) continue } boundVal := Bind(params, key[:len(name)+1+fieldLen], fieldValue.Type()) fieldValue.Set(boundVal) fieldValues[fieldName] = boundVal } } return result } func unbindStruct(output map[string]string, name string, iface interface{}) { val := reflect.ValueOf(iface) typ := val.Type() for i := 0; i < val.NumField(); i++ { structField := typ.Field(i) fieldValue := val.Field(i) // PkgPath is specified to be empty exactly for exported fields. if structField.PkgPath == "" { Unbind(output, fmt.Sprintf("%s.%s", name, structField.Name), fieldValue.Interface()) } } } // Helper that returns an upload of the given name, or nil. func getMultipartFile(params *Params, name string) multipart.File { for _, fileHeader := range params.Files[name] { file, err := fileHeader.Open() if err == nil { return file } binderLog.Warn("getMultipartFile: Failed to open uploaded file", "name", name, "error", err) } return nil } func bindFile(params *Params, name string, typ reflect.Type) reflect.Value { reader := getMultipartFile(params, name) if reader == nil { return reflect.Zero(typ) } // If it's already stored in a temp file, just return that. if osFile, ok := reader.(*os.File); ok { return reflect.ValueOf(osFile) } // Otherwise, have to store it. tmpFile, err := ioutil.TempFile("", "revel-upload") if err != nil { binderLog.Warn("bindFile: Failed to create a temp file to store upload", "name", name, "error", err) return reflect.Zero(typ) } // Register it to be deleted after the request is done. params.tmpFiles = append(params.tmpFiles, tmpFile) _, err = io.Copy(tmpFile, reader) if err != nil { binderLog.Warn("bindFile: Failed to copy upload to temp file", "name", name, "error", err) return reflect.Zero(typ) } _, err = tmpFile.Seek(0, 0) if err != nil { binderLog.Warn("bindFile: Failed to seek to beginning of temp file", "name", name, "error", err) return reflect.Zero(typ) } return reflect.ValueOf(tmpFile) } func bindByteArray(params *Params, name string, typ reflect.Type) reflect.Value { if reader := getMultipartFile(params, name); reader != nil { b, err := ioutil.ReadAll(reader) if err == nil { return reflect.ValueOf(b) } binderLog.Warn("bindByteArray: Error reading uploaded file contents", "name", name, "error", err) } return reflect.Zero(typ) } func bindReadSeeker(params *Params, name string, typ reflect.Type) reflect.Value { if reader := getMultipartFile(params, name); reader != nil { return reflect.ValueOf(reader.(io.ReadSeeker)) } return reflect.Zero(typ) } // bindMap converts parameters using map syntax into the corresponding map. e.g.: // params["a[5]"]=foo, name="a", typ=map[int]string => map[int]string{5: "foo"} func bindMap(params *Params, name string, typ reflect.Type) reflect.Value { var ( keyType = typ.Key() valueType = typ.Elem() resultPtr = reflect.New(reflect.MapOf(keyType, valueType)) result = resultPtr.Elem() ) result.Set(reflect.MakeMap(typ)) if params.JSON != nil { // Try to inject the response as a json into the created result if err := json.Unmarshal(params.JSON, resultPtr.Interface()); err != nil { binderLog.Warn("bindMap: Unable to unmarshal request", "name", name, "error", err) } return result } for paramName := range params.Values { // The paramName string must start with the value in the "name" parameter, // otherwise there is no way the parameter is part of the map if !strings.HasPrefix(paramName, name) { continue } suffix := paramName[len(name)+1:] fieldName := nextKey(suffix) if fieldName != "" { fieldName = fieldName[:len(fieldName)-1] } if !strings.HasPrefix(paramName, name+"["+fieldName+"]") { continue } result.SetMapIndex(BindValue(fieldName, keyType), Bind(params, name+"["+fieldName+"]", valueType)) } return result } func unbindMap(output map[string]string, name string, iface interface{}) { mapValue := reflect.ValueOf(iface) for _, key := range mapValue.MapKeys() { Unbind(output, name+"["+fmt.Sprintf("%v", key.Interface())+"]", mapValue.MapIndex(key).Interface()) } } // Bind takes the name and type of the desired parameter and constructs it // from one or more values from Params. // Returns the zero value of the type upon any sort of failure. func Bind(params *Params, name string, typ reflect.Type) reflect.Value { if binder, found := binderForType(typ); found { return binder.Bind(params, name, typ) } return reflect.Zero(typ) } func BindValue(val string, typ reflect.Type) reflect.Value { return Bind(&Params{Values: map[string][]string{"": {val}}}, "", typ) } func BindFile(fileHeader *multipart.FileHeader, typ reflect.Type) reflect.Value { return Bind(&Params{Files: map[string][]*multipart.FileHeader{"": {fileHeader}}}, "", typ) } func Unbind(output map[string]string, name string, val interface{}) { if binder, found := binderForType(reflect.TypeOf(val)); found { if binder.Unbind != nil { binder.Unbind(output, name, val) } else { binderLog.Error("Unbind: Unable to unmarshal request", "name", name, "value", val) } } } func binderForType(typ reflect.Type) (Binder, bool) { binder, ok := TypeBinders[typ] if !ok { binder, ok = KindBinders[typ.Kind()] if !ok { binderLog.Error("binderForType: no binder for type", "type", typ) return Binder{}, false } } return binder, true } // Sadly, the binder lookups can not be declared initialized -- that results in // an "initialization loop" compile error. func init() { KindBinders[reflect.Int] = IntBinder KindBinders[reflect.Int8] = IntBinder KindBinders[reflect.Int16] = IntBinder KindBinders[reflect.Int32] = IntBinder KindBinders[reflect.Int64] = IntBinder KindBinders[reflect.Uint] = UintBinder KindBinders[reflect.Uint8] = UintBinder KindBinders[reflect.Uint16] = UintBinder KindBinders[reflect.Uint32] = UintBinder KindBinders[reflect.Uint64] = UintBinder KindBinders[reflect.Float32] = FloatBinder KindBinders[reflect.Float64] = FloatBinder KindBinders[reflect.String] = StringBinder KindBinders[reflect.Bool] = BoolBinder KindBinders[reflect.Slice] = Binder{bindSlice, unbindSlice} KindBinders[reflect.Struct] = Binder{bindStruct, unbindStruct} KindBinders[reflect.Ptr] = PointerBinder KindBinders[reflect.Map] = MapBinder TypeBinders[reflect.TypeOf(time.Time{})] = TimeBinder // Uploads TypeBinders[reflect.TypeOf(&os.File{})] = Binder{bindFile, nil} TypeBinders[reflect.TypeOf([]byte{})] = Binder{bindByteArray, nil} TypeBinders[reflect.TypeOf((*io.Reader)(nil)).Elem()] = Binder{bindReadSeeker, nil} TypeBinders[reflect.TypeOf((*io.ReadSeeker)(nil)).Elem()] = Binder{bindReadSeeker, nil} OnAppStart(func() { DateTimeFormat = Config.StringDefault("format.datetime", DefaultDateTimeFormat) DateFormat = Config.StringDefault("format.date", DefaultDateFormat) TimeFormats = append(TimeFormats, DateTimeFormat, DateFormat) }) } revel-1.0.0/binder_test.go000066400000000000000000000314501370252312000154540ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package revel import ( "encoding/json" "fmt" "github.com/revel/config" "io" "io/ioutil" "os" "reflect" "sort" "strings" "testing" "time" ) type A struct { ID int Name string B B private int } type B struct { Extra string } var ( ParamTestValues = map[string][]string{ "int": {"1"}, "int8": {"1"}, "int16": {"1"}, "int32": {"1"}, "int64": {"1"}, "uint": {"1"}, "uint8": {"1"}, "uint16": {"1"}, "uint32": {"1"}, "uint64": {"1"}, "float32": {"1.000000"}, "float64": {"1.000000"}, "str": {"hello"}, "bool-true": {"true"}, "bool-1": {"1"}, "bool-on": {"on"}, "bool-false": {"false"}, "bool-0": {"0"}, "bool-0.0": {"0.0"}, "bool-off": {"off"}, "bool-f": {"f"}, "date": {"1982-07-09"}, "datetime": {"1982-07-09 21:30"}, "customDate": {"07/09/1982"}, "arr[0]": {"1"}, "arr[1]": {"2"}, "arr[3]": {"3"}, "uarr[]": {"1", "2"}, "arruarr[0][]": {"1", "2"}, "arruarr[1][]": {"3", "4"}, "2darr[0][0]": {"0"}, "2darr[0][1]": {"1"}, "2darr[1][0]": {"10"}, "2darr[1][1]": {"11"}, "A.ID": {"123"}, "A.Name": {"rob"}, "B.ID": {"123"}, "B.Name": {"rob"}, "B.B.Extra": {"hello"}, "pB.ID": {"123"}, "pB.Name": {"rob"}, "pB.B.Extra": {"hello"}, "priv.private": {"123"}, "arrC[0].ID": {"5"}, "arrC[0].Name": {"rob"}, "arrC[0].B.Extra": {"foo"}, "arrC[1].ID": {"8"}, "arrC[1].Name": {"bill"}, "m[a]": {"foo"}, "m[b]": {"bar"}, "m2[1]": {"foo"}, "m2[2]": {"bar"}, "m3[a]": {"1"}, "m3[b]": {"2"}, "m4[a].ID": {"1"}, "m4[a].Name": {"foo"}, "m4[b].ID": {"2"}, "m4[b].Name": {"bar"}, "mapWithAMuchLongerName[a].ID": {"1"}, "mapWithAMuchLongerName[a].Name": {"foo"}, "mapWithAMuchLongerName[b].ID": {"2"}, "mapWithAMuchLongerName[b].Name": {"bar"}, "invalidInt": {"xyz"}, "invalidInt2": {""}, "invalidBool": {"xyz"}, "invalidArr": {"xyz"}, "int8-overflow": {"1024"}, "uint8-overflow": {"1024"}, "arrDoS[2]": {"2"}, "arrDoS[65535]": {"65535"}, } testDate = time.Date(1982, time.July, 9, 0, 0, 0, 0, time.UTC) testDatetime = time.Date(1982, time.July, 9, 21, 30, 0, 0, time.UTC) ) var binderTestCases = map[string]interface{}{ "int": 1, "int8": int8(1), "int16": int16(1), "int32": int32(1), "int64": int64(1), "uint": 1, "uint8": uint8(1), "uint16": uint16(1), "uint32": uint32(1), "uint64": uint64(1), "float32": float32(1.0), "float64": float64(1.0), "str": "hello", "bool-true": true, "bool-1": true, "bool-on": true, "bool-false": false, "bool-0": false, "bool-0.0": false, "bool-off": false, "bool-f": false, "date": testDate, "datetime": testDatetime, "customDate": testDate, "arr": []int{1, 2, 0, 3}, "uarr": []int{1, 2}, "arruarr": [][]int{{1, 2}, {3, 4}}, "2darr": [][]int{{0, 1}, {10, 11}}, "A": A{ID: 123, Name: "rob"}, "B": A{ID: 123, Name: "rob", B: B{Extra: "hello"}}, "pB": &A{ID: 123, Name: "rob", B: B{Extra: "hello"}}, "arrC": []A{ { ID: 5, Name: "rob", B: B{"foo"}, }, { ID: 8, Name: "bill", }, }, "m": map[string]string{"a": "foo", "b": "bar"}, "m2": map[int]string{1: "foo", 2: "bar"}, "m3": map[string]int{"a": 1, "b": 2}, "m4": map[string]A{"a": {ID: 1, Name: "foo"}, "b": {ID: 2, Name: "bar"}}, // NOTE: We also include a map with a longer name than the others since this has caused problems // described in github issue #1285, resolved in pull request #1344. This test case should // prevent regression. "mapWithAMuchLongerName": map[string]A{"a": {ID: 1, Name: "foo"}, "b": {ID: 2, Name: "bar"}}, // TODO: Tests that use TypeBinders // Invalid value tests (the result should always be the zero value for that type) // The point of these is to ensure that invalid user input does not cause panics. "invalidInt": 0, "invalidInt2": 0, "invalidBool": true, "invalidArr": []int{}, "priv": A{}, "int8-overflow": int8(0), "uint8-overflow": uint8(0), "arrDoS": []int{0, 0, 2}, } // Types that files may be bound to, and a func that can read the content from // that type. // TODO: Is there any way to create a slice, given only the element Type? var fileBindings = []struct{ val, arrval, f interface{} }{ {(**os.File)(nil), []*os.File{}, ioutil.ReadAll}, {(*[]byte)(nil), [][]byte{}, func(b []byte) []byte { return b }}, {(*io.Reader)(nil), []io.Reader{}, ioutil.ReadAll}, {(*io.ReadSeeker)(nil), []io.ReadSeeker{}, ioutil.ReadAll}, } func TestJsonBinder(t *testing.T) { // create a structure to be populated { d, _ := json.Marshal(map[string]int{"a": 1}) params := &Params{JSON: d} foo := struct{ A int }{} c := NewTestController(nil, getMultipartRequest()) ParseParams(params, NewRequest(c.Request.In)) actual := Bind(params, "test", reflect.TypeOf(foo)) valEq(t, "TestJsonBinder", reflect.ValueOf(actual.Interface().(struct{ A int }).A), reflect.ValueOf(1)) } { d, _ := json.Marshal(map[string]interface{}{"a": map[string]int{"b": 45}}) params := &Params{JSON: d} testMap := map[string]interface{}{} actual := Bind(params, "test", reflect.TypeOf(testMap)).Interface().(map[string]interface{}) if actual["a"].(map[string]interface{})["b"].(float64) != 45 { t.Errorf("Failed to fetch map value %#v", actual["a"]) } // Check to see if a named map works actualb := Bind(params, "test", reflect.TypeOf(map[string]map[string]float64{})).Interface().(map[string]map[string]float64) if actualb["a"]["b"] != 45 { t.Errorf("Failed to fetch map value %#v", actual["a"]) } } } func TestBinder(t *testing.T) { // Reuse the mvc_test.go multipart request to test the binder. params := &Params{} c := NewTestController(nil, getMultipartRequest()) if Config == nil { Config = config.NewContext() defer func() { Config = nil }() } ParseParams(params, NewRequest(c.Request.In)) params.Values = ParamTestValues // Values for k, v := range binderTestCases { actual := Bind(params, k, reflect.TypeOf(v)) expected := reflect.ValueOf(v) valEq(t, k, actual, expected) } // Files // Get the keys in sorted order to make the expectation right. keys := []string{} for k := range expectedFiles { keys = append(keys, k) } sort.Strings(keys) expectedBoundFiles := make(map[string][]fh) for _, k := range keys { fhs := expectedFiles[k] k := nextKey(k) expectedBoundFiles[k] = append(expectedBoundFiles[k], fhs...) } for k, fhs := range expectedBoundFiles { if len(fhs) == 1 { // Test binding single files to: *os.File, []byte, io.Reader, io.ReadSeeker for _, binding := range fileBindings { typ := reflect.TypeOf(binding.val).Elem() actual := Bind(params, k, typ) if !actual.IsValid() || (actual.Kind() == reflect.Interface && actual.IsNil()) { t.Errorf("%s (%s) - Returned nil.", k, typ) continue } returns := reflect.ValueOf(binding.f).Call([]reflect.Value{actual}) valEq(t, k, returns[0], reflect.ValueOf(fhs[0].content)) } } else { // Test binding multi to: // []*os.File, [][]byte, []io.Reader, []io.ReadSeeker for _, binding := range fileBindings { typ := reflect.TypeOf(binding.arrval) actual := Bind(params, k, typ) if actual.Len() != len(fhs) { t.Fatalf("%s (%s) - Number of files: (expected) %d != %d (actual)", k, typ, len(fhs), actual.Len()) } for i := range fhs { returns := reflect.ValueOf(binding.f).Call([]reflect.Value{actual.Index(i)}) if !returns[0].IsValid() { t.Errorf("%s (%s) - Returned nil.", k, typ) continue } valEq(t, k, returns[0], reflect.ValueOf(fhs[i].content)) } } } } } // Unbinding tests var unbinderTestCases = map[string]interface{}{ "int": 1, "int8": int8(1), "int16": int16(1), "int32": int32(1), "int64": int64(1), "uint": 1, "uint8": uint8(1), "uint16": uint16(1), "uint32": uint32(1), "uint64": uint64(1), "float32": float32(1.0), "float64": float64(1.0), "str": "hello", "bool-true": true, "bool-false": false, "date": testDate, "datetime": testDatetime, "arr": []int{1, 2, 0, 3}, "2darr": [][]int{{0, 1}, {10, 11}}, "A": A{ID: 123, Name: "rob"}, "B": A{ID: 123, Name: "rob", B: B{Extra: "hello"}}, "pB": &A{ID: 123, Name: "rob", B: B{Extra: "hello"}}, "arrC": []A{ { ID: 5, Name: "rob", B: B{"foo"}, }, { ID: 8, Name: "bill", }, }, "m": map[string]string{"a": "foo", "b": "bar"}, "m2": map[int]string{1: "foo", 2: "bar"}, "m3": map[string]int{"a": 1, "b": 2}, } // Some of the unbinding results are not exactly what is in ParamTestValues, since it // serializes implicit zero values explicitly. var unbinderOverrideAnswers = map[string]map[string]string{ "arr": { "arr[0]": "1", "arr[1]": "2", "arr[2]": "0", "arr[3]": "3", }, "A": { "A.ID": "123", "A.Name": "rob", "A.B.Extra": "", }, "arrC": { "arrC[0].ID": "5", "arrC[0].Name": "rob", "arrC[0].B.Extra": "foo", "arrC[1].ID": "8", "arrC[1].Name": "bill", "arrC[1].B.Extra": "", }, "m": {"m[a]": "foo", "m[b]": "bar"}, "m2": {"m2[1]": "foo", "m2[2]": "bar"}, "m3": {"m3[a]": "1", "m3[b]": "2"}, } func TestUnbinder(t *testing.T) { for k, v := range unbinderTestCases { actual := make(map[string]string) Unbind(actual, k, v) // Get the expected key/values. expected, ok := unbinderOverrideAnswers[k] if !ok { expected = make(map[string]string) for k2, v2 := range ParamTestValues { if k == k2 || strings.HasPrefix(k2, k+".") || strings.HasPrefix(k2, k+"[") { expected[k2] = v2[0] } } } // Compare length and values. if len(actual) != len(expected) { t.Errorf("Length mismatch\nExpected length %d, actual %d\nExpected: %s\nActual: %s", len(expected), len(actual), expected, actual) } for k, v := range actual { if expected[k] != v { t.Errorf("Value mismatch.\nExpected: %s\nActual: %s", expected, actual) } } } } // Helpers func valEq(t *testing.T, name string, actual, expected reflect.Value) { switch expected.Kind() { case reflect.Slice: // Check the type/length/element type if !eq(t, name+" (type)", actual.Kind(), expected.Kind()) || !eq(t, name+" (len)", actual.Len(), expected.Len()) || !eq(t, name+" (elem)", actual.Type().Elem(), expected.Type().Elem()) { return } // Check value equality for each element. for i := 0; i < actual.Len(); i++ { valEq(t, fmt.Sprintf("%s[%d]", name, i), actual.Index(i), expected.Index(i)) } case reflect.Ptr: // Check equality on the element type. valEq(t, name, actual.Elem(), expected.Elem()) case reflect.Map: if !eq(t, name+" (len)", actual.Len(), expected.Len()) { return } for _, key := range expected.MapKeys() { expectedValue := expected.MapIndex(key) actualValue := actual.MapIndex(key) if actualValue.IsValid() { valEq(t, fmt.Sprintf("%s[%s]", name, key), actualValue, expectedValue) } else { t.Errorf("Expected key %s not found", key) } } default: eq(t, name, actual.Interface(), expected.Interface()) } } func init() { DateFormat = DefaultDateFormat DateTimeFormat = DefaultDateTimeFormat TimeFormats = append(TimeFormats, DefaultDateFormat, DefaultDateTimeFormat, "01/02/2006") } revel-1.0.0/cache/000077500000000000000000000000001370252312000136635ustar00rootroot00000000000000revel-1.0.0/cache/cache.go000066400000000000000000000126161370252312000152630ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package cache import ( "errors" "time" ) // Length of time to cache an item. const ( DefaultExpiryTime = time.Duration(0) ForEverNeverExpiry = time.Duration(-1) ) // Getter is an interface for getting / decoding an element from a cache. type Getter interface { // Get the content associated with the given key. decoding it into the given // pointer. // // Returns: // - nil if the value was successfully retrieved and ptrValue set // - ErrCacheMiss if the value was not in the cache // - an implementation specific error otherwise Get(key string, ptrValue interface{}) error } // Cache is an interface to an expiring cache. It behaves (and is modeled) like // the Memcached interface. It is keyed by strings (250 bytes at most). // // Many callers will make exclusive use of Set and Get, but more exotic // functions are also available. // // Example // // Here is a typical Get/Set interaction: // // var items []*Item // if err := cache.Get("items", &items); err != nil { // items = loadItems() // go cache.Set("items", items, cache.DefaultExpiryTime) // } // // Note that the caller will frequently not wait for Set() to complete. // // Errors // // It is assumed that callers will infrequently check returned errors, since any // request should be fulfillable without finding anything in the cache. As a // result, all errors other than ErrCacheMiss and ErrNotStored will be logged to // revel.ERROR, so that the developer does not need to check the return value to // discover things like deserialization or connection errors. type Cache interface { // The Cache implements a Getter. Getter // Set the given key/value in the cache, overwriting any existing value // associated with that key. Keys may be at most 250 bytes in length. // // Returns: // - nil on success // - an implementation specific error otherwise Set(key string, value interface{}, expires time.Duration) error // Get the content associated multiple keys at once. On success, the caller // may decode the values one at a time from the returned Getter. // // Returns: // - the value getter, and a nil error if the operation completed. // - an implementation specific error otherwise GetMulti(keys ...string) (Getter, error) // Delete the given key from the cache. // // Returns: // - nil on a successful delete // - ErrCacheMiss if the value was not in the cache // - an implementation specific error otherwise Delete(key string) error // Add the given key/value to the cache ONLY IF the key does not already exist. // // Returns: // - nil if the value was added to the cache // - ErrNotStored if the key was already present in the cache // - an implementation-specific error otherwise Add(key string, value interface{}, expires time.Duration) error // Set the given key/value in the cache ONLY IF the key already exists. // // Returns: // - nil if the value was replaced // - ErrNotStored if the key does not exist in the cache // - an implementation specific error otherwise Replace(key string, value interface{}, expires time.Duration) error // Increment the value stored at the given key by the given amount. // The value silently wraps around upon exceeding the uint64 range. // // Returns the new counter value if the operation was successful, or: // - ErrCacheMiss if the key was not found in the cache // - an implementation specific error otherwise Increment(key string, n uint64) (newValue uint64, err error) // Decrement the value stored at the given key by the given amount. // The value is capped at 0 on underflow, with no error returned. // // Returns the new counter value if the operation was successful, or: // - ErrCacheMiss if the key was not found in the cache // - an implementation specific error otherwise Decrement(key string, n uint64) (newValue uint64, err error) // Expire all cache entries immediately. // This is not implemented for the memcached cache (intentionally). // Returns an implementation specific error if the operation failed. Flush() error } var ( Instance Cache ErrCacheMiss = errors.New("revel/cache: key not found") ErrNotStored = errors.New("revel/cache: not stored") ErrInvalidValue = errors.New("revel/cache: invalid value") ) // The package implements the Cache interface (as sugar). func Get(key string, ptrValue interface{}) error { return Instance.Get(key, ptrValue) } func GetMulti(keys ...string) (Getter, error) { return Instance.GetMulti(keys...) } func Delete(key string) error { return Instance.Delete(key) } func Increment(key string, n uint64) (newValue uint64, err error) { return Instance.Increment(key, n) } func Decrement(key string, n uint64) (newValue uint64, err error) { return Instance.Decrement(key, n) } func Flush() error { return Instance.Flush() } func Set(key string, value interface{}, expires time.Duration) error { return Instance.Set(key, value, expires) } func Add(key string, value interface{}, expires time.Duration) error { return Instance.Add(key, value, expires) } func Replace(key string, value interface{}, expires time.Duration) error { return Instance.Replace(key, value, expires) } revel-1.0.0/cache/cache_test.go000066400000000000000000000150771370252312000163260ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package cache import ( "math" "testing" "time" ) // Tests against a generic Cache interface. // They should pass for all implementations. type cacheFactory func(*testing.T, time.Duration) Cache // Test typical cache interactions func typicalGetSet(t *testing.T, newCache cacheFactory) { var err error cache := newCache(t, time.Hour) value := "foo" if err = cache.Set("value", value, DefaultExpiryTime); err != nil { t.Errorf("Error setting a value: %s", err) } value = "" err = cache.Get("value", &value) if err != nil { t.Errorf("Error getting a value: %s", err) } if value != "foo" { t.Errorf("Expected to get foo back, got %s", value) } } // Test the increment-decrement cases func incrDecr(t *testing.T, newCache cacheFactory) { var err error cache := newCache(t, time.Hour) // Normal increment / decrement operation. if err = cache.Set("int", 10, ForEverNeverExpiry); err != nil { t.Errorf("Error setting int: %s", err) } time.Sleep(time.Second) newValue, err := cache.Increment("int", 50) if err != nil { t.Errorf("Error incrementing int: %s", err) } if newValue != 60 { t.Errorf("Expected 60, was %d", newValue) } if newValue, err = cache.Decrement("int", 50); err != nil { t.Errorf("Error decrementing: %s", err) } if newValue != 10 { t.Errorf("Expected 10, was %d", newValue) } // Increment wraparound newValue, err = cache.Increment("int", math.MaxUint64-5) if err != nil { t.Errorf("Error wrapping around: %s", err) } if newValue != 4 { t.Errorf("Expected wraparound 4, got %d", newValue) } // Decrement capped at 0 newValue, err = cache.Decrement("int", 25) if err != nil { t.Errorf("Error decrementing below 0: %s", err) } if newValue != 0 { t.Errorf("Expected capped at 0, got %d", newValue) } } func expiration(t *testing.T, newCache cacheFactory) { // memcached does not support expiration times less than 1 second. var err error cache := newCache(t, time.Second) // Test Set w/ DefaultExpiryTime value := 10 if err = cache.Set("int", value, DefaultExpiryTime); err != nil { t.Errorf("Set failed: %s", err) } time.Sleep(2 * time.Second) if err = cache.Get("int", &value); err != ErrCacheMiss { t.Errorf("Expected CacheMiss, but got: %s", err) } // Test Set w/ short time if err = cache.Set("int", value, time.Second); err != nil { t.Errorf("Set failed: %s", err) } time.Sleep(2 * time.Second) if err = cache.Get("int", &value); err != ErrCacheMiss { t.Errorf("Expected CacheMiss, but got: %s", err) } // Test Set w/ longer time. if err = cache.Set("int", value, time.Hour); err != nil { t.Errorf("Set failed: %s", err) } time.Sleep(2 * time.Second) if err = cache.Get("int", &value); err != nil { t.Errorf("Expected to get the value, but got: %s", err) } // Test Set w/ forever. if err = cache.Set("int", value, ForEverNeverExpiry); err != nil { t.Errorf("Set failed: %s", err) } time.Sleep(2 * time.Second) if err = cache.Get("int", &value); err != nil { t.Errorf("Expected to get the value, but got: %s", err) } } func emptyCache(t *testing.T, newCache cacheFactory) { var err error cache := newCache(t, time.Hour) err = cache.Get("notexist", 0) if err == nil { t.Errorf("Error expected for non-existent key") } if err != ErrCacheMiss { t.Errorf("Expected ErrCacheMiss for non-existent key: %s", err) } err = cache.Delete("notexist") if err != ErrCacheMiss { t.Errorf("Expected ErrCacheMiss for non-existent key: %s", err) } _, err = cache.Increment("notexist", 1) if err != ErrCacheMiss { t.Errorf("Expected cache miss incrementing non-existent key: %s", err) } _, err = cache.Decrement("notexist", 1) if err != ErrCacheMiss { t.Errorf("Expected cache miss decrementing non-existent key: %s", err) } } func testReplace(t *testing.T, newCache cacheFactory) { var err error cache := newCache(t, time.Hour) // Replace in an empty cache. if err = cache.Replace("notexist", 1, ForEverNeverExpiry); err != ErrNotStored { t.Errorf("Replace in empty cache: expected ErrNotStored, got: %s", err) } // Set a value of 1, and replace it with 2 if err = cache.Set("int", 1, time.Second); err != nil { t.Errorf("Unexpected error: %s", err) } if err = cache.Replace("int", 2, time.Second); err != nil { t.Errorf("Unexpected error: %s", err) } var i int if err = cache.Get("int", &i); err != nil { t.Errorf("Unexpected error getting a replaced item: %s", err) } if i != 2 { t.Errorf("Expected 2, got %d", i) } // Wait for it to expire and replace with 3 (unsuccessfully). time.Sleep(2 * time.Second) if err = cache.Replace("int", 3, time.Second); err != ErrNotStored { t.Errorf("Expected ErrNotStored, got: %s", err) } if err = cache.Get("int", &i); err != ErrCacheMiss { t.Errorf("Expected cache miss, got: %s", err) } } func testAdd(t *testing.T, newCache cacheFactory) { var err error cache := newCache(t, time.Hour) // Add to an empty cache. if err = cache.Add("int", 1, time.Second*3); err != nil { t.Errorf("Unexpected error adding to empty cache: %s", err) } // Try to add again. (fail) if err = cache.Add("int", 2, time.Second*3); err != nil { if err != ErrNotStored { t.Errorf("Expected ErrNotStored adding dupe to cache: %s", err) } } // Wait for it to expire, and add again. time.Sleep(8 * time.Second) if err = cache.Add("int", 3, time.Second*5); err != nil { t.Errorf("Unexpected error adding to cache: %s", err) } // Get and verify the value. var i int if err = cache.Get("int", &i); err != nil { t.Errorf("Unexpected error: %s", err) } if i != 3 { t.Errorf("Expected 3, got: %d", i) } } func testGetMulti(t *testing.T, newCache cacheFactory) { cache := newCache(t, time.Hour) m := map[string]interface{}{ "str": "foo", "num": 42, "foo": struct{ Bar string }{"baz"}, } var keys []string for key, value := range m { keys = append(keys, key) if err := cache.Set(key, value, time.Second*30); err != nil { t.Errorf("Error setting a value: %s", err) } } g, err := cache.GetMulti(keys...) if err != nil { t.Errorf("Error in get-multi: %s", err) } var str string if err = g.Get("str", &str); err != nil || str != "foo" { t.Errorf("Error getting str: %s / %s", err, str) } var num int if err = g.Get("num", &num); err != nil || num != 42 { t.Errorf("Error getting num: %s / %v", err, num) } var foo struct{ Bar string } if err = g.Get("foo", &foo); err != nil || foo.Bar != "baz" { t.Errorf("Error getting foo: %s / %v", err, foo) } } revel-1.0.0/cache/init.go000066400000000000000000000037161370252312000151640ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package cache import ( "strings" "time" "github.com/revel/revel" ) var cacheLog = revel.RevelLog.New("section", "cache") func init() { revel.OnAppStart(func() { // Set the default expiration time. defaultExpiration := time.Hour // The default for the default is one hour. if expireStr, found := revel.Config.String("cache.expires"); found { var err error if defaultExpiration, err = time.ParseDuration(expireStr); err != nil { cacheLog.Panic("Could not parse default cache expiration duration " + expireStr + ": " + err.Error()) } } // make sure you aren't trying to use both memcached and redis if revel.Config.BoolDefault("cache.memcached", false) && revel.Config.BoolDefault("cache.redis", false) { cacheLog.Panic("You've configured both memcached and redis, please only include configuration for one cache!") } // Use memcached? if revel.Config.BoolDefault("cache.memcached", false) { hosts := strings.Split(revel.Config.StringDefault("cache.hosts", ""), ",") if len(hosts) == 0 { cacheLog.Panic("Memcache enabled but no memcached hosts specified!") } Instance = NewMemcachedCache(hosts, defaultExpiration) return } // Use Redis (share same config as memcached)? if revel.Config.BoolDefault("cache.redis", false) { hosts := strings.Split(revel.Config.StringDefault("cache.hosts", ""), ",") if len(hosts) == 0 { cacheLog.Panic("Redis enabled but no Redis hosts specified!") } if len(hosts) > 1 { cacheLog.Panic("Redis currently only supports one host!") } password := revel.Config.StringDefault("cache.redis.password", "") Instance = NewRedisCache(hosts[0], password, defaultExpiration) return } // By default, use the in-memory cache. Instance = NewInMemoryCache(defaultExpiration) }) } revel-1.0.0/cache/inmemory.go000066400000000000000000000072071370252312000160570ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package cache import ( "fmt" "reflect" "time" "github.com/patrickmn/go-cache" "sync" ) type InMemoryCache struct { cache cache.Cache // Only expose the methods we want to make available mu sync.RWMutex // For increment / decrement prevent reads and writes } func NewInMemoryCache(defaultExpiration time.Duration) InMemoryCache { return InMemoryCache{cache: *cache.New(defaultExpiration, time.Minute), mu: sync.RWMutex{}} } func (c InMemoryCache) Get(key string, ptrValue interface{}) error { c.mu.RLock() defer c.mu.RUnlock() value, found := c.cache.Get(key) if !found { return ErrCacheMiss } v := reflect.ValueOf(ptrValue) if v.Type().Kind() == reflect.Ptr && v.Elem().CanSet() { v.Elem().Set(reflect.ValueOf(value)) return nil } err := fmt.Errorf("revel/cache: attempt to get %s, but can not set value %v", key, v) cacheLog.Error(err.Error()) return err } func (c InMemoryCache) GetMulti(keys ...string) (Getter, error) { return c, nil } func (c InMemoryCache) Set(key string, value interface{}, expires time.Duration) error { c.mu.Lock() defer c.mu.Unlock() // NOTE: go-cache understands the values of DefaultExpiryTime and ForEverNeverExpiry c.cache.Set(key, value, expires) return nil } func (c InMemoryCache) Add(key string, value interface{}, expires time.Duration) error { c.mu.Lock() defer c.mu.Unlock() err := c.cache.Add(key, value, expires) if err != nil { return ErrNotStored } return err } func (c InMemoryCache) Replace(key string, value interface{}, expires time.Duration) error { c.mu.Lock() defer c.mu.Unlock() if err := c.cache.Replace(key, value, expires); err != nil { return ErrNotStored } return nil } func (c InMemoryCache) Delete(key string) error { c.mu.RLock() defer c.mu.RUnlock() if _, found := c.cache.Get(key); !found { return ErrCacheMiss } c.cache.Delete(key) return nil } func (c InMemoryCache) Increment(key string, n uint64) (newValue uint64, err error) { c.mu.Lock() defer c.mu.Unlock() if _, found := c.cache.Get(key); !found { return 0, ErrCacheMiss } if err = c.cache.Increment(key, int64(n)); err != nil { return } return c.convertTypeToUint64(key) } func (c InMemoryCache) Decrement(key string, n uint64) (newValue uint64, err error) { c.mu.Lock() defer c.mu.Unlock() if nv, err := c.convertTypeToUint64(key); err != nil { return 0, err } else { // Stop from going below zero if n > nv { n = nv } } if err = c.cache.Decrement(key, int64(n)); err != nil { return } return c.convertTypeToUint64(key) } func (c InMemoryCache) Flush() error { c.mu.Lock() defer c.mu.Unlock() c.cache.Flush() return nil } // Fetches and returns the converted type to a uint64 func (c InMemoryCache) convertTypeToUint64(key string) (newValue uint64, err error) { v, found := c.cache.Get(key) if !found { return newValue, ErrCacheMiss } switch v.(type) { case int: newValue = uint64(v.(int)) case int8: newValue = uint64(v.(int8)) case int16: newValue = uint64(v.(int16)) case int32: newValue = uint64(v.(int32)) case int64: newValue = uint64(v.(int64)) case uint: newValue = uint64(v.(uint)) case uintptr: newValue = uint64(v.(uintptr)) case uint8: newValue = uint64(v.(uint8)) case uint16: newValue = uint64(v.(uint16)) case uint32: newValue = uint64(v.(uint32)) case uint64: newValue = uint64(v.(uint64)) case float32: newValue = uint64(v.(float32)) case float64: newValue = uint64(v.(float64)) default: err = ErrInvalidValue } return } revel-1.0.0/cache/inmemory_test.go000066400000000000000000000020141370252312000171050ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package cache import ( "testing" "time" ) var newInMemoryCache = func(_ *testing.T, defaultExpiration time.Duration) Cache { return NewInMemoryCache(defaultExpiration) } // Test typical cache interactions func TestInMemoryCache_TypicalGetSet(t *testing.T) { typicalGetSet(t, newInMemoryCache) } // Test the increment-decrement cases func TestInMemoryCache_IncrDecr(t *testing.T) { incrDecr(t, newInMemoryCache) } func TestInMemoryCache_Expiration(t *testing.T) { expiration(t, newInMemoryCache) } func TestInMemoryCache_EmptyCache(t *testing.T) { emptyCache(t, newInMemoryCache) } func TestInMemoryCache_Replace(t *testing.T) { testReplace(t, newInMemoryCache) } func TestInMemoryCache_Add(t *testing.T) { testAdd(t, newInMemoryCache) } func TestInMemoryCache_GetMulti(t *testing.T) { testGetMulti(t, newInMemoryCache) } revel-1.0.0/cache/memcached.go000066400000000000000000000061661370252312000161310ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package cache import ( "errors" "time" "github.com/bradfitz/gomemcache/memcache" "github.com/revel/revel/logger" ) // MemcachedCache wraps the Memcached client to meet the Cache interface. type MemcachedCache struct { *memcache.Client defaultExpiration time.Duration } func NewMemcachedCache(hostList []string, defaultExpiration time.Duration) MemcachedCache { return MemcachedCache{memcache.New(hostList...), defaultExpiration} } func (c MemcachedCache) Set(key string, value interface{}, expires time.Duration) error { return c.invoke((*memcache.Client).Set, key, value, expires) } func (c MemcachedCache) Add(key string, value interface{}, expires time.Duration) error { return c.invoke((*memcache.Client).Add, key, value, expires) } func (c MemcachedCache) Replace(key string, value interface{}, expires time.Duration) error { return c.invoke((*memcache.Client).Replace, key, value, expires) } func (c MemcachedCache) Get(key string, ptrValue interface{}) error { item, err := c.Client.Get(key) if err != nil { return convertMemcacheError(err) } return Deserialize(item.Value, ptrValue) } func (c MemcachedCache) GetMulti(keys ...string) (Getter, error) { items, err := c.Client.GetMulti(keys) if err != nil { return nil, convertMemcacheError(err) } return ItemMapGetter(items), nil } func (c MemcachedCache) Delete(key string) error { return convertMemcacheError(c.Client.Delete(key)) } func (c MemcachedCache) Increment(key string, delta uint64) (newValue uint64, err error) { newValue, err = c.Client.Increment(key, delta) return newValue, convertMemcacheError(err) } func (c MemcachedCache) Decrement(key string, delta uint64) (newValue uint64, err error) { newValue, err = c.Client.Decrement(key, delta) return newValue, convertMemcacheError(err) } func (c MemcachedCache) Flush() error { err := errors.New("Flush: can not flush memcached") cacheLog.Error(err.Error()) return err } func (c MemcachedCache) invoke(f func(*memcache.Client, *memcache.Item) error, key string, value interface{}, expires time.Duration) error { switch expires { case DefaultExpiryTime: expires = c.defaultExpiration case ForEverNeverExpiry: expires = time.Duration(0) } b, err := Serialize(value) if err != nil { return err } return convertMemcacheError(f(c.Client, &memcache.Item{ Key: key, Value: b, Expiration: int32(expires / time.Second), })) } // ItemMapGetter implements a Getter on top of the returned item map. type ItemMapGetter map[string]*memcache.Item func (g ItemMapGetter) Get(key string, ptrValue interface{}) error { item, ok := g[key] if !ok { return ErrCacheMiss } return Deserialize(item.Value, ptrValue) } func convertMemcacheError(err error) error { switch err { case nil: return nil case memcache.ErrCacheMiss: return ErrCacheMiss case memcache.ErrNotStored: return ErrNotStored } cacheLog.Error("convertMemcacheError:", "error", err, "trace", logger.NewCallStack()) return err } revel-1.0.0/cache/memcached_test.go000066400000000000000000000025471370252312000171670ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package cache import ( "net" "testing" "time" ) // These tests require memcached running on localhost:11211 (the default) const testServer = "localhost:11211" var newMemcachedCache = func(t *testing.T, defaultExpiration time.Duration) Cache { c, err := net.Dial("tcp", testServer) if err == nil { if _, err = c.Write([]byte("flush_all\r\n")); err != nil { t.Errorf("Write failed: %s", err) } _ = c.Close() return NewMemcachedCache([]string{testServer}, defaultExpiration) } t.Errorf("couldn't connect to memcached on %s", testServer) t.FailNow() panic("") } func TestMemcachedCache_TypicalGetSet(t *testing.T) { typicalGetSet(t, newMemcachedCache) } func TestMemcachedCache_IncrDecr(t *testing.T) { incrDecr(t, newMemcachedCache) } func TestMemcachedCache_Expiration(t *testing.T) { expiration(t, newMemcachedCache) } func TestMemcachedCache_EmptyCache(t *testing.T) { emptyCache(t, newMemcachedCache) } func TestMemcachedCache_Replace(t *testing.T) { testReplace(t, newMemcachedCache) } func TestMemcachedCache_Add(t *testing.T) { testAdd(t, newMemcachedCache) } func TestMemcachedCache_GetMulti(t *testing.T) { testGetMulti(t, newMemcachedCache) } revel-1.0.0/cache/redis.go000066400000000000000000000153521370252312000153260ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package cache import ( "time" "github.com/garyburd/redigo/redis" "github.com/revel/revel" ) // RedisCache wraps the Redis client to meet the Cache interface. type RedisCache struct { pool *redis.Pool defaultExpiration time.Duration } // NewRedisCache returns a new RedisCache with given parameters // until redigo supports sharding/clustering, only one host will be in hostList func NewRedisCache(host string, password string, defaultExpiration time.Duration) RedisCache { var pool = &redis.Pool{ MaxIdle: revel.Config.IntDefault("cache.redis.maxidle", 5), MaxActive: revel.Config.IntDefault("cache.redis.maxactive", 0), IdleTimeout: time.Duration(revel.Config.IntDefault("cache.redis.idletimeout", 240)) * time.Second, Dial: func() (redis.Conn, error) { protocol := revel.Config.StringDefault("cache.redis.protocol", "tcp") toc := time.Millisecond * time.Duration(revel.Config.IntDefault("cache.redis.timeout.connect", 10000)) tor := time.Millisecond * time.Duration(revel.Config.IntDefault("cache.redis.timeout.read", 5000)) tow := time.Millisecond * time.Duration(revel.Config.IntDefault("cache.redis.timeout.write", 5000)) c, err := redis.Dial(protocol, host, redis.DialConnectTimeout(toc), redis.DialReadTimeout(tor), redis.DialWriteTimeout(tow)) if err != nil { return nil, err } if len(password) > 0 { if _, err = c.Do("AUTH", password); err != nil { _ = c.Close() return nil, err } } else { // check with PING if _, err = c.Do("PING"); err != nil { _ = c.Close() return nil, err } } return c, err }, // custom connection test method TestOnBorrow: func(c redis.Conn, t time.Time) error { _, err := c.Do("PING") return err }, } return RedisCache{pool, defaultExpiration} } func (c RedisCache) Set(key string, value interface{}, expires time.Duration) error { conn := c.pool.Get() defer func() { _ = conn.Close() }() return c.invoke(conn.Do, key, value, expires) } func (c RedisCache) Add(key string, value interface{}, expires time.Duration) error { conn := c.pool.Get() defer func() { _ = conn.Close() }() existed, err := exists(conn, key) if err != nil { return err } else if existed { return ErrNotStored } return c.invoke(conn.Do, key, value, expires) } func (c RedisCache) Replace(key string, value interface{}, expires time.Duration) error { conn := c.pool.Get() defer func() { _ = conn.Close() }() existed, err := exists(conn, key) if err != nil { return err } else if !existed { return ErrNotStored } err = c.invoke(conn.Do, key, value, expires) if value == nil { return ErrNotStored } return err } func (c RedisCache) Get(key string, ptrValue interface{}) error { conn := c.pool.Get() defer func() { _ = conn.Close() }() raw, err := conn.Do("GET", key) if err != nil { return err } else if raw == nil { return ErrCacheMiss } item, err := redis.Bytes(raw, err) if err != nil { return err } return Deserialize(item, ptrValue) } func generalizeStringSlice(strs []string) []interface{} { ret := make([]interface{}, len(strs)) for i, str := range strs { ret[i] = str } return ret } func (c RedisCache) GetMulti(keys ...string) (Getter, error) { conn := c.pool.Get() defer func() { _ = conn.Close() }() items, err := redis.Values(conn.Do("MGET", generalizeStringSlice(keys)...)) if err != nil { return nil, err } else if items == nil { return nil, ErrCacheMiss } m := make(map[string][]byte) for i, key := range keys { m[key] = nil if i < len(items) && items[i] != nil { s, ok := items[i].([]byte) if ok { m[key] = s } } } return RedisItemMapGetter(m), nil } func exists(conn redis.Conn, key string) (bool, error) { return redis.Bool(conn.Do("EXISTS", key)) } func (c RedisCache) Delete(key string) error { conn := c.pool.Get() defer func() { _ = conn.Close() }() existed, err := redis.Bool(conn.Do("DEL", key)) if err == nil && !existed { err = ErrCacheMiss } return err } func (c RedisCache) Increment(key string, delta uint64) (uint64, error) { conn := c.pool.Get() defer func() { _ = conn.Close() }() // Check for existence *before* increment as per the cache contract. // redis will auto create the key, and we don't want that. Since we need to do increment // ourselves instead of natively via INCRBY (redis doesn't support wrapping), we get the value // and do the exists check this way to minimize calls to Redis val, err := conn.Do("GET", key) if err != nil { return 0, err } else if val == nil { return 0, ErrCacheMiss } currentVal, err := redis.Int64(val, nil) if err != nil { return 0, err } sum := currentVal + int64(delta) _, err = conn.Do("SET", key, sum) if err != nil { return 0, err } return uint64(sum), nil } func (c RedisCache) Decrement(key string, delta uint64) (newValue uint64, err error) { conn := c.pool.Get() defer func() { _ = conn.Close() }() // Check for existence *before* increment as per the cache contract. // redis will auto create the key, and we don't want that, hence the exists call existed, err := exists(conn, key) if err != nil { return 0, err } else if !existed { return 0, ErrCacheMiss } // Decrement contract says you can only go to 0 // so we go fetch the value and if the delta is greater than the amount, // 0 out the value currentVal, err := redis.Int64(conn.Do("GET", key)) if err != nil { return 0, err } if delta > uint64(currentVal) { var tempint int64 tempint, err = redis.Int64(conn.Do("DECRBY", key, currentVal)) return uint64(tempint), err } tempint, err := redis.Int64(conn.Do("DECRBY", key, delta)) return uint64(tempint), err } func (c RedisCache) Flush() error { conn := c.pool.Get() defer func() { _ = conn.Close() }() _, err := conn.Do("FLUSHALL") return err } func (c RedisCache) invoke(f func(string, ...interface{}) (interface{}, error), key string, value interface{}, expires time.Duration) error { switch expires { case DefaultExpiryTime: expires = c.defaultExpiration case ForEverNeverExpiry: expires = time.Duration(0) } b, err := Serialize(value) if err != nil { return err } conn := c.pool.Get() defer func() { _ = conn.Close() }() if expires > 0 { _, err = f("SETEX", key, int32(expires/time.Second), b) return err } _, err = f("SET", key, b) return err } // RedisItemMapGetter implements a Getter on top of the returned item map. type RedisItemMapGetter map[string][]byte func (g RedisItemMapGetter) Get(key string, ptrValue interface{}) error { item, ok := g[key] if !ok { return ErrCacheMiss } return Deserialize(item, ptrValue) } revel-1.0.0/cache/redis_test.go000066400000000000000000000027761370252312000163730ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package cache import ( "net" "testing" "time" "github.com/revel/config" "github.com/revel/revel" ) // These tests require redis server running on localhost:6379 (the default) const redisTestServer = "localhost:6379" var newRedisCache = func(t *testing.T, defaultExpiration time.Duration) Cache { revel.Config = config.NewContext() c, err := net.Dial("tcp", redisTestServer) if err == nil { if _, err = c.Write([]byte("flush_all\r\n")); err != nil { t.Errorf("Write failed: %s", err) } _ = c.Close() redisCache := NewRedisCache(redisTestServer, "", defaultExpiration) if err = redisCache.Flush(); err != nil { t.Errorf("Flush failed: %s", err) } return redisCache } t.Errorf("couldn't connect to redis on %s", redisTestServer) t.FailNow() panic("") } func TestRedisCache_TypicalGetSet(t *testing.T) { typicalGetSet(t, newRedisCache) } func TestRedisCache_IncrDecr(t *testing.T) { incrDecr(t, newRedisCache) } func TestRedisCache_Expiration(t *testing.T) { expiration(t, newRedisCache) } func TestRedisCache_EmptyCache(t *testing.T) { emptyCache(t, newRedisCache) } func TestRedisCache_Replace(t *testing.T) { testReplace(t, newRedisCache) } func TestRedisCache_Add(t *testing.T) { testAdd(t, newRedisCache) } func TestRedisCache_GetMulti(t *testing.T) { testGetMulti(t, newRedisCache) } revel-1.0.0/cache/serialization.go000066400000000000000000000044601370252312000170730ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package cache import ( "bytes" "encoding/gob" "reflect" "strconv" ) // Serialize transforms the given value into bytes following these rules: // - If value is a byte array, it is returned as-is. // - If value is an int or uint type, it is returned as the ASCII representation // - Else, encoding/gob is used to serialize func Serialize(value interface{}) ([]byte, error) { if data, ok := value.([]byte); ok { return data, nil } switch v := reflect.ValueOf(value); v.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return []byte(strconv.FormatInt(v.Int(), 10)), nil case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: return []byte(strconv.FormatUint(v.Uint(), 10)), nil } var b bytes.Buffer encoder := gob.NewEncoder(&b) if err := encoder.Encode(value); err != nil { cacheLog.Error("Serialize: gob encoding failed", "value", value, "error", err) return nil, err } return b.Bytes(), nil } // Deserialize transforms bytes produced by Serialize back into a Go object, // storing it into "ptr", which must be a pointer to the value type. func Deserialize(byt []byte, ptr interface{}) (err error) { if data, ok := ptr.(*[]byte); ok { *data = byt return } if v := reflect.ValueOf(ptr); v.Kind() == reflect.Ptr { switch p := v.Elem(); p.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: var i int64 i, err = strconv.ParseInt(string(byt), 10, 64) if err != nil { cacheLog.Error("Deserialize: failed to parse int", "value", string(byt), "error", err) } else { p.SetInt(i) } return case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: var i uint64 i, err = strconv.ParseUint(string(byt), 10, 64) if err != nil { cacheLog.Error("Deserialize: failed to parse uint", "value", string(byt), "error", err) } else { p.SetUint(i) } return } } b := bytes.NewBuffer(byt) decoder := gob.NewDecoder(b) if err = decoder.Decode(ptr); err != nil { cacheLog.Error("Deserialize: glob decoding failed", "error", err) return } return } revel-1.0.0/cache/serialization_test.go000066400000000000000000000043321370252312000201300ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package cache import ( "reflect" "testing" ) type Struct1 struct { X int } func (s Struct1) Method1() {} type Interface1 interface { Method1() } var ( struct1 = Struct1{1} ptrStruct = &Struct1{2} emptyIface interface{} = Struct1{3} iface1 Interface1 = Struct1{4} sliceStruct = []Struct1{{5}, {6}, {7}} ptrSliceStruct = []*Struct1{{8}, {9}, {10}} valueMap = map[string]interface{}{ "bytes": []byte{0x61, 0x62, 0x63, 0x64}, "string": "string", "bool": true, "int": 5, "int8": int8(5), "int16": int16(5), "int32": int32(5), "int64": int64(5), "uint": uint(5), "uint8": uint8(5), "uint16": uint16(5), "uint32": uint32(5), "uint64": uint64(5), "float32": float32(5), "float64": float64(5), "array": [5]int{1, 2, 3, 4, 5}, "slice": []int{1, 2, 3, 4, 5}, "emptyIf": emptyIface, "Iface1": iface1, "map": map[string]string{"foo": "bar"}, "ptrStruct": ptrStruct, "struct1": struct1, "sliceStruct": sliceStruct, "ptrSliceStruct": ptrSliceStruct, } ) // Test passing all kinds of data between serialize and deserialize. func TestRoundTrip(t *testing.T) { for _, expected := range valueMap { bytes, err := Serialize(expected) if err != nil { t.Error(err) continue } ptrActual := reflect.New(reflect.TypeOf(expected)).Interface() err = Deserialize(bytes, ptrActual) if err != nil { t.Error(err) continue } actual := reflect.ValueOf(ptrActual).Elem().Interface() if !reflect.DeepEqual(expected, actual) { t.Errorf("(expected) %T %v != %T %v (actual)", expected, expected, actual, actual) } } } func zeroMap(arg map[string]interface{}) map[string]interface{} { result := map[string]interface{}{} for key, value := range arg { result[key] = reflect.Zero(reflect.TypeOf(value)).Interface() } return result } revel-1.0.0/compress.go000066400000000000000000000232561370252312000150120ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package revel import ( "compress/gzip" "compress/zlib" "io" "net/http" "strconv" "strings" ) var compressionTypes = [...]string{ "gzip", "deflate", } var compressableMimes = [...]string{ "text/plain", "text/html", "text/xml", "text/css", "application/json", "application/xml", "application/xhtml+xml", "application/rss+xml", "application/javascript", "application/x-javascript", } // Local log instance for this class var compressLog = RevelLog.New("section", "compress") // WriteFlusher interface for compress writer type WriteFlusher interface { io.Writer // An IO Writer io.Closer // A closure Flush() error /// A flush function } // The compressed writer type CompressResponseWriter struct { Header *BufferedServerHeader // The header ControllerResponse *Response // The response OriginalWriter io.Writer // The writer compressWriter WriteFlusher // The flushed writer compressionType string // The compression type headersWritten bool // True if written closeNotify chan bool // The notify channel to close parentNotify <-chan bool // The parent chanel to receive the closed event closed bool // True if closed } // CompressFilter does compression of response body in gzip/deflate if // `results.compressed=true` in the app.conf func CompressFilter(c *Controller, fc []Filter) { if c.Response.Out.internalHeader.Server != nil && Config.BoolDefault("results.compressed", false) { if c.Response.Status != http.StatusNoContent && c.Response.Status != http.StatusNotModified { if found, compressType, compressWriter := detectCompressionType(c.Request, c.Response); found { writer := CompressResponseWriter{ ControllerResponse: c.Response, OriginalWriter: c.Response.GetWriter(), compressWriter: compressWriter, compressionType: compressType, headersWritten: false, closeNotify: make(chan bool, 1), closed: false, } // Swap out the header with our own writer.Header = NewBufferedServerHeader(c.Response.Out.internalHeader.Server) c.Response.Out.internalHeader.Server = writer.Header if w, ok := c.Response.GetWriter().(http.CloseNotifier); ok { writer.parentNotify = w.CloseNotify() } c.Response.SetWriter(&writer) } } else { compressLog.Debug("CompressFilter: Compression disabled for response ", "status", c.Response.Status) } } fc[0](c, fc[1:]) } // Called to notify the writer is closing func (c CompressResponseWriter) CloseNotify() <-chan bool { if c.parentNotify != nil { return c.parentNotify } return c.closeNotify } // Cancel the writer func (c *CompressResponseWriter) cancel() { c.closed = true } // Prepare the headers func (c *CompressResponseWriter) prepareHeaders() { if c.compressionType != "" { responseMime := "" if t := c.Header.Get("Content-Type"); len(t) > 0 { responseMime = t[0] } responseMime = strings.TrimSpace(strings.SplitN(responseMime, ";", 2)[0]) shouldEncode := false if len(c.Header.Get("Content-Encoding")) == 0 { for _, compressableMime := range compressableMimes { if responseMime == compressableMime { shouldEncode = true c.Header.Set("Content-Encoding", c.compressionType) c.Header.Del("Content-Length") break } } } if !shouldEncode { c.compressWriter = nil c.compressionType = "" } } c.Header.Release() } // Write the headers func (c *CompressResponseWriter) WriteHeader(status int) { if c.closed { return } c.headersWritten = true c.prepareHeaders() c.Header.SetStatus(status) } // Close the writer func (c *CompressResponseWriter) Close() error { if c.closed { return nil } if !c.headersWritten { c.prepareHeaders() } if c.compressionType != "" { c.Header.Del("Content-Length") if err := c.compressWriter.Close(); err != nil { // TODO When writing directly to stream, an error will be generated compressLog.Error("Close: Error closing compress writer", "type", c.compressionType, "error", err) } } // Non-blocking write to the closenotifier, if we for some reason should // get called multiple times select { case c.closeNotify <- true: default: } c.closed = true return nil } // Write to the underling buffer func (c *CompressResponseWriter) Write(b []byte) (int, error) { if c.closed { return 0, io.ErrClosedPipe } // Abort if parent has been closed if c.parentNotify != nil { select { case <-c.parentNotify: return 0, io.ErrClosedPipe default: } } // Abort if we ourselves have been closed if c.closed { return 0, io.ErrClosedPipe } if !c.headersWritten { c.prepareHeaders() c.headersWritten = true } if c.compressionType != "" { return c.compressWriter.Write(b) } return c.OriginalWriter.Write(b) } // DetectCompressionType method detects the compression type // from header "Accept-Encoding" func detectCompressionType(req *Request, resp *Response) (found bool, compressionType string, compressionKind WriteFlusher) { if Config.BoolDefault("results.compressed", false) { acceptedEncodings := strings.Split(req.GetHttpHeader("Accept-Encoding"), ",") largestQ := 0.0 chosenEncoding := len(compressionTypes) // I have fixed one edge case for issue #914 // But it's better to cover all possible edge cases or // Adapt to https://github.com/golang/gddo/blob/master/httputil/header/header.go#L172 for _, encoding := range acceptedEncodings { encoding = strings.TrimSpace(encoding) encodingParts := strings.SplitN(encoding, ";", 2) // If we are the format "gzip;q=0.8" if len(encodingParts) > 1 { q := strings.TrimSpace(encodingParts[1]) if len(q) == 0 || !strings.HasPrefix(q, "q=") { continue } // Strip off the q= num, err := strconv.ParseFloat(q[2:], 32) if err != nil { continue } if num >= largestQ && num > 0 { if encodingParts[0] == "*" { chosenEncoding = 0 largestQ = num continue } for i, encoding := range compressionTypes { if encoding == encodingParts[0] { if i < chosenEncoding { largestQ = num chosenEncoding = i } break } } } } else { // If we can accept anything, chose our preferred method. if encodingParts[0] == "*" { chosenEncoding = 0 largestQ = 1 break } // This is for just plain "gzip" for i, encoding := range compressionTypes { if encoding == encodingParts[0] { if i < chosenEncoding { largestQ = 1.0 chosenEncoding = i } break } } } } if largestQ == 0 { return } compressionType = compressionTypes[chosenEncoding] switch compressionType { case "gzip": compressionKind = gzip.NewWriter(resp.GetWriter()) found = true case "deflate": compressionKind = zlib.NewWriter(resp.GetWriter()) found = true } } return } // BufferedServerHeader will not send content out until the Released is called, from that point on it will act normally // It implements all the ServerHeader type BufferedServerHeader struct { cookieList []string // The cookie list headerMap map[string][]string // The header map status int // The status released bool // True if released original ServerHeader // The original header } // Creates a new instance based on the ServerHeader func NewBufferedServerHeader(o ServerHeader) *BufferedServerHeader { return &BufferedServerHeader{original: o, headerMap: map[string][]string{}} } // Sets the cookie func (bsh *BufferedServerHeader) SetCookie(cookie string) { if bsh.released { bsh.original.SetCookie(cookie) } else { bsh.cookieList = append(bsh.cookieList, cookie) } } // Returns a cookie func (bsh *BufferedServerHeader) GetCookie(key string) (ServerCookie, error) { return bsh.original.GetCookie(key) } // Sets (replace) the header key func (bsh *BufferedServerHeader) Set(key string, value string) { if bsh.released { bsh.original.Set(key, value) } else { bsh.headerMap[key] = []string{value} } } // Add (append) to a key this value func (bsh *BufferedServerHeader) Add(key string, value string) { if bsh.released { bsh.original.Set(key, value) } else { old := []string{} if v, found := bsh.headerMap[key]; found { old = v } bsh.headerMap[key] = append(old, value) } } // Delete this key func (bsh *BufferedServerHeader) Del(key string) { if bsh.released { bsh.original.Del(key) } else { delete(bsh.headerMap, key) } } // Get this key func (bsh *BufferedServerHeader) Get(key string) (value []string) { if bsh.released { value = bsh.original.Get(key) } else { if v, found := bsh.headerMap[key]; found && len(v) > 0 { value = v } else { value = bsh.original.Get(key) } } return } // Get all header keys func (bsh *BufferedServerHeader) GetKeys() (value []string) { if bsh.released { value = bsh.original.GetKeys() } else { value = bsh.original.GetKeys() for key := range bsh.headerMap { found := false for _,v := range value { if v==key { found = true break } } if !found { value = append(value,key) } } } return } // Set the status func (bsh *BufferedServerHeader) SetStatus(statusCode int) { if bsh.released { bsh.original.SetStatus(statusCode) } else { bsh.status = statusCode } } // Release the header and push the results to the original func (bsh *BufferedServerHeader) Release() { bsh.released = true for k, v := range bsh.headerMap { for _, r := range v { bsh.original.Set(k, r) } } for _, c := range bsh.cookieList { bsh.original.SetCookie(c) } if bsh.status > 0 { bsh.original.SetStatus(bsh.status) } } revel-1.0.0/compress_test.go000066400000000000000000000032041370252312000160400ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package revel import ( "net/http/httptest" "strings" "testing" ) // Test that the render response is as expected. func TestBenchmarkCompressed(t *testing.T) { startFakeBookingApp() resp := httptest.NewRecorder() c := NewTestController(resp, showRequest) if err := c.SetAction("Hotels", "Show"); err != nil { t.Errorf("SetAction failed: %s", err) } Config.SetOption("results.compressed", "true") result := Hotels{c}.Show(3) result.Apply(c.Request, c.Response) if !strings.Contains(resp.Body.String(), "300 Main St.") { t.Errorf("Failed to find hotel address in action response:\n%s", resp.Body) } } func BenchmarkRenderCompressed(b *testing.B) { startFakeBookingApp() resp := httptest.NewRecorder() resp.Body = nil c := NewTestController(resp, showRequest) if err := c.SetAction("Hotels", "Show"); err != nil { b.Errorf("SetAction failed: %s", err) } Config.SetOption("results.compressed", "true") b.ResetTimer() hotels := Hotels{c} for i := 0; i < b.N; i++ { hotels.Show(3).Apply(c.Request, c.Response) } } func BenchmarkRenderUnCompressed(b *testing.B) { startFakeBookingApp() resp := httptest.NewRecorder() resp.Body = nil c := NewTestController(resp, showRequest) if err := c.SetAction("Hotels", "Show"); err != nil { b.Errorf("SetAction failed: %s", err) } Config.SetOption("results.compressed", "false") b.ResetTimer() hotels := Hotels{c} for i := 0; i < b.N; i++ { hotels.Show(3).Apply(c.Request, c.Response) } } revel-1.0.0/conf/000077500000000000000000000000001370252312000135455ustar00rootroot00000000000000revel-1.0.0/conf/mime-types.conf000066400000000000000000000331701370252312000165110ustar00rootroot000000000000003dm=x-world/x-3dmf 3dmf=x-world/x-3dmf 7z=application/x-7z-compressed a=application/octet-stream aab=application/x-authorware-bin aam=application/x-authorware-map aas=application/x-authorware-seg abc=text/vndabc ace=application/x-ace-compressed acgi=text/html afl=video/animaflex ai=application/postscript aif=audio/aiff aifc=audio/aiff aiff=audio/aiff aim=application/x-aim aip=text/x-audiosoft-intra alz=application/x-alz-compressed ani=application/x-navi-animation aos=application/x-nokia-9000-communicator-add-on-software aps=application/mime arc=application/x-arc-compressed arj=application/arj art=image/x-jg asf=video/x-ms-asf asm=text/x-asm asp=text/asp asx=application/x-mplayer2 au=audio/basic avi=video/x-msvideo avs=video/avs-video bcpio=application/x-bcpio bin=application/mac-binary bmp=image/bmp boo=application/book book=application/book boz=application/x-bzip2 bsh=application/x-bsh bz2=application/x-bzip2 bz=application/x-bzip c++=text/plain c=text/x-c cab=application/vnd.ms-cab-compressed cat=application/vndms-pkiseccat cc=text/x-c ccad=application/clariscad cco=application/x-cocoa cdf=application/cdf cer=application/pkix-cert cha=application/x-chat chat=application/x-chat chrt=application/vnd.kde.kchart class=application/java # ? class=application/java-vm com=text/plain conf=text/plain cpio=application/x-cpio cpp=text/x-c cpt=application/mac-compactpro crl=application/pkcs-crl crt=application/pkix-cert crx=application/x-chrome-extension csh=text/x-scriptcsh css=text/css csv=text/csv cxx=text/plain dar=application/x-dar dcr=application/x-director deb=application/x-debian-package deepv=application/x-deepv def=text/plain der=application/x-x509-ca-cert dif=video/x-dv dir=application/x-director divx=video/divx dl=video/dl dmg=application/x-apple-diskimage doc=application/msword dot=application/msword dp=application/commonground drw=application/drafting dump=application/octet-stream dv=video/x-dv dvi=application/x-dvi dwf=drawing/x-dwf=(old) dwg=application/acad dxf=application/dxf dxr=application/x-director el=text/x-scriptelisp elc=application/x-bytecodeelisp=(compiled=elisp) eml=message/rfc822 env=application/x-envoy eps=application/postscript es=application/x-esrehber etx=text/x-setext evy=application/envoy exe=application/octet-stream f77=text/x-fortran f90=text/x-fortran f=text/x-fortran fdf=application/vndfdf fif=application/fractals fli=video/fli flo=image/florian flv=video/x-flv flx=text/vndfmiflexstor fmf=video/x-atomic3d-feature for=text/x-fortran fpx=image/vndfpx frl=application/freeloader funk=audio/make g3=image/g3fax g=text/plain gif=image/gif gl=video/gl gsd=audio/x-gsm gsm=audio/x-gsm gsp=application/x-gsp gss=application/x-gss gtar=application/x-gtar gz=application/x-compressed gzip=application/x-gzip h=text/x-h hdf=application/x-hdf help=application/x-helpfile hgl=application/vndhp-hpgl hh=text/x-h hlb=text/x-script hlp=application/hlp hpg=application/vndhp-hpgl hpgl=application/vndhp-hpgl hqx=application/binhex hta=application/hta htc=text/x-component htm=text/html html=text/html htmls=text/html htt=text/webviewhtml htx=text/html ice=x-conference/x-cooltalk ico=image/x-icon ics=text/calendar icz=text/calendar idc=text/plain ief=image/ief iefs=image/ief iges=application/iges igs=application/iges ima=application/x-ima imap=application/x-httpd-imap inf=application/inf ins=application/x-internett-signup ip=application/x-ip2 isu=video/x-isvideo it=audio/it iv=application/x-inventor ivr=i-world/i-vrml ivy=application/x-livescreen jam=audio/x-jam jav=text/x-java-source java=text/x-java-source jcm=application/x-java-commerce jfif-tbnl=image/jpeg jfif=image/jpeg jnlp=application/x-java-jnlp-file jpe=image/jpeg jpeg=image/jpeg jpg=image/jpeg jps=image/x-jps js=application/javascript json=application/json jut=image/jutvision kar=audio/midi karbon=application/vnd.kde.karbon kfo=application/vnd.kde.kformula flw=application/vnd.kde.kivio kml=application/vnd.google-earth.kml+xml kmz=application/vnd.google-earth.kmz kon=application/vnd.kde.kontour kpr=application/vnd.kde.kpresenter kpt=application/vnd.kde.kpresenter ksp=application/vnd.kde.kspread kwd=application/vnd.kde.kword kwt=application/vnd.kde.kword ksh=text/x-scriptksh la=audio/nspaudio lam=audio/x-liveaudio latex=application/x-latex lha=application/lha lhx=application/octet-stream list=text/plain lma=audio/nspaudio log=text/plain lsp=text/x-scriptlisp lst=text/plain lsx=text/x-la-asf ltx=application/x-latex lzh=application/octet-stream lzx=application/lzx m1v=video/mpeg m2a=audio/mpeg m2v=video/mpeg m3u=audio/x-mpegurl m=text/x-m man=application/x-troff-man manifest=text/cache-manifest map=application/x-navimap mar=text/plain mbd=application/mbedlet mc$=application/x-magic-cap-package-10 mcd=application/mcad mcf=text/mcf mcp=application/netmc me=application/x-troff-me mht=message/rfc822 mhtml=message/rfc822 mid=application/x-midi midi=application/x-midi mif=application/x-frame mime=message/rfc822 mjf=audio/x-vndaudioexplosionmjuicemediafile mjpg=video/x-motion-jpeg mm=application/base64 mme=application/base64 mod=audio/mod moov=video/quicktime mov=video/quicktime movie=video/x-sgi-movie mp2=audio/mpeg mp3=audio/mpeg3 mp4=video/mp4 mpa=audio/mpeg mpc=application/x-project mpe=video/mpeg mpeg=video/mpeg mpg=video/mpeg mpga=audio/mpeg mpp=application/vndms-project mpt=application/x-project mpv=application/x-project mpx=application/x-project mrc=application/marc ms=application/x-troff-ms mv=video/x-sgi-movie my=audio/make mzz=application/x-vndaudioexplosionmzz nap=image/naplps naplps=image/naplps nc=application/x-netcdf ncm=application/vndnokiaconfiguration-message nif=image/x-niff niff=image/x-niff nix=application/x-mix-transfer nsc=application/x-conference nvd=application/x-navidoc o=application/octet-stream oda=application/oda odb=application/vnd.oasis.opendocument.database odc=application/vnd.oasis.opendocument.chart odf=application/vnd.oasis.opendocument.formula odg=application/vnd.oasis.opendocument.graphics odi=application/vnd.oasis.opendocument.image odm=application/vnd.oasis.opendocument.text-master odp=application/vnd.oasis.opendocument.presentation ods=application/vnd.oasis.opendocument.spreadsheet odt=application/vnd.oasis.opendocument.text oga=audio/ogg ogg=audio/ogg ogv=video/ogg omc=application/x-omc omcd=application/x-omcdatamaker omcr=application/x-omcregerator otc=application/vnd.oasis.opendocument.chart-template otf=application/vnd.oasis.opendocument.formula-template otg=application/vnd.oasis.opendocument.graphics-template oth=application/vnd.oasis.opendocument.text-web oti=application/vnd.oasis.opendocument.image-template otm=application/vnd.oasis.opendocument.text-master otp=application/vnd.oasis.opendocument.presentation-template ots=application/vnd.oasis.opendocument.spreadsheet-template ott=application/vnd.oasis.opendocument.text-template p10=application/pkcs10 p12=application/pkcs-12 p7a=application/x-pkcs7-signature p7c=application/pkcs7-mime p7m=application/pkcs7-mime p7r=application/x-pkcs7-certreqresp p7s=application/pkcs7-signature p=text/x-pascal part=application/pro_eng pas=text/pascal pbm=image/x-portable-bitmap pcl=application/vndhp-pcl pct=image/x-pict pcx=image/x-pcx pdb=chemical/x-pdb pdf=application/pdf pfunk=audio/make pgm=image/x-portable-graymap pic=image/pict pict=image/pict pkg=application/x-newton-compatible-pkg pko=application/vndms-pkipko pl=text/x-scriptperl plx=application/x-pixclscript pm4=application/x-pagemaker pm5=application/x-pagemaker pm=text/x-scriptperl-module png=image/png pnm=application/x-portable-anymap pot=application/mspowerpoint pov=model/x-pov ppa=application/vndms-powerpoint ppm=image/x-portable-pixmap pps=application/mspowerpoint ppt=application/mspowerpoint ppz=application/mspowerpoint pre=application/x-freelance prt=application/pro_eng ps=application/postscript psd=application/octet-stream pvu=paleovu/x-pv pwz=application/vndms-powerpoint py=text/x-scriptphyton pyc=applicaiton/x-bytecodepython qcp=audio/vndqcelp qd3=x-world/x-3dmf qd3d=x-world/x-3dmf qif=image/x-quicktime qt=video/quicktime qtc=video/x-qtc qti=image/x-quicktime qtif=image/x-quicktime ra=audio/x-pn-realaudio ram=audio/x-pn-realaudio rar=application/x-rar-compressed ras=application/x-cmu-raster rast=image/cmu-raster rexx=text/x-scriptrexx rf=image/vndrn-realflash rgb=image/x-rgb rm=application/vndrn-realmedia rmi=audio/mid rmm=audio/x-pn-realaudio rmp=audio/x-pn-realaudio rng=application/ringing-tones rnx=application/vndrn-realplayer roff=application/x-troff rp=image/vndrn-realpix rpm=audio/x-pn-realaudio-plugin rt=text/vndrn-realtext rtf=text/richtext rtx=text/richtext rv=video/vndrn-realvideo s=text/x-asm s3m=audio/s3m s7z=application/x-7z-compressed saveme=application/octet-stream sbk=application/x-tbook scm=text/x-scriptscheme sdml=text/plain sdp=application/sdp sdr=application/sounder sea=application/sea set=application/set sgm=text/x-sgml sgml=text/x-sgml sh=text/x-scriptsh shar=application/x-bsh shtml=text/x-server-parsed-html sid=audio/x-psid skd=application/x-koan skm=application/x-koan skp=application/x-koan skt=application/x-koan sit=application/x-stuffit sitx=application/x-stuffitx sl=application/x-seelogo smi=application/smil smil=application/smil snd=audio/basic sol=application/solids spc=text/x-speech spl=application/futuresplash spr=application/x-sprite sprite=application/x-sprite spx=audio/ogg src=application/x-wais-source ssi=text/x-server-parsed-html ssm=application/streamingmedia sst=application/vndms-pkicertstore step=application/step stl=application/sla stp=application/step sv4cpio=application/x-sv4cpio sv4crc=application/x-sv4crc svf=image/vnddwg svg=image/svg+xml svr=application/x-world swf=application/x-shockwave-flash t=application/x-troff talk=text/x-speech tar=application/x-tar tbk=application/toolbook tcl=text/x-scripttcl tcsh=text/x-scripttcsh tex=application/x-tex texi=application/x-texinfo texinfo=application/x-texinfo text=text/plain tgz=application/gnutar tif=image/tiff tiff=image/tiff tr=application/x-troff tsi=audio/tsp-audio tsp=application/dsptype tsv=text/tab-separated-values turbot=image/florian txt=text/plain uil=text/x-uil uni=text/uri-list unis=text/uri-list unv=application/i-deas uri=text/uri-list uris=text/uri-list ustar=application/x-ustar uu=text/x-uuencode uue=text/x-uuencode vcd=application/x-cdlink vcf=text/x-vcard vcard=text/x-vcard vcs=text/x-vcalendar vda=application/vda vdo=video/vdo vew=application/groupwise viv=video/vivo vivo=video/vivo vmd=application/vocaltec-media-desc vmf=application/vocaltec-media-file voc=audio/voc vos=video/vosaic vox=audio/voxware vqe=audio/x-twinvq-plugin vqf=audio/x-twinvq vql=audio/x-twinvq-plugin vrml=application/x-vrml vrt=x-world/x-vrt vsd=application/x-visio vst=application/x-visio vsw=application/x-visio wasm=application/wasm w60=application/wordperfect60 w61=application/wordperfect61 w6w=application/msword wav=audio/wav wb1=application/x-qpro wbmp=image/vnd.wap.wbmp web=application/vndxara wiz=application/msword wk1=application/x-123 wmf=windows/metafile wml=text/vnd.wap.wml wmlc=application/vnd.wap.wmlc wmls=text/vnd.wap.wmlscript wmlsc=application/vnd.wap.wmlscriptc word=application/msword wp5=application/wordperfect wp6=application/wordperfect wp=application/wordperfect wpd=application/wordperfect wq1=application/x-lotus wri=application/mswrite wrl=application/x-world wrz=model/vrml wsc=text/scriplet wsrc=application/x-wais-source wtk=application/x-wintalk x-png=image/png xbm=image/x-xbitmap xdr=video/x-amt-demorun xgz=xgl/drawing xif=image/vndxiff xl=application/excel xla=application/excel xlb=application/excel xlc=application/excel xld=application/excel xlk=application/excel xll=application/excel xlm=application/excel xls=application/excel xlt=application/excel xlv=application/excel xlw=application/excel xm=audio/xm xml=text/xml xmz=xgl/movie xpix=application/x-vndls-xpix xpm=image/x-xpixmap xsr=video/x-amt-showrun xwd=image/x-xwd xyz=chemical/x-pdb z=application/x-compress zip=application/zip zoo=application/octet-stream zsh=text/x-scriptzsh # Office 2007 mess - http://wdg.uncc.edu/Microsoft_Office_2007_MIME_Types_for_Apache_and_IIS docx=application/vnd.openxmlformats-officedocument.wordprocessingml.document docm=application/vnd.ms-word.document.macroEnabled.12 dotx=application/vnd.openxmlformats-officedocument.wordprocessingml.template dotm=application/vnd.ms-word.template.macroEnabled.12 xlsx=application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsm=application/vnd.ms-excel.sheet.macroEnabled.12 xltx=application/vnd.openxmlformats-officedocument.spreadsheetml.template xltm=application/vnd.ms-excel.template.macroEnabled.12 xlsb=application/vnd.ms-excel.sheet.binary.macroEnabled.12 xlam=application/vnd.ms-excel.addin.macroEnabled.12 pptx=application/vnd.openxmlformats-officedocument.presentationml.presentation pptm=application/vnd.ms-powerpoint.presentation.macroEnabled.12 ppsx=application/vnd.openxmlformats-officedocument.presentationml.slideshow ppsm=application/vnd.ms-powerpoint.slideshow.macroEnabled.12 potx=application/vnd.openxmlformats-officedocument.presentationml.template potm=application/vnd.ms-powerpoint.template.macroEnabled.12 ppam=application/vnd.ms-powerpoint.addin.macroEnabled.12 sldx=application/vnd.openxmlformats-officedocument.presentationml.slide sldm=application/vnd.ms-powerpoint.slide.macroEnabled.12 thmx=application/vnd.ms-officetheme onetoc=application/onenote onetoc2=application/onenote onetmp=application/onenote onepkg=application/onenote # koffice # iWork key=application/x-iwork-keynote-sffkey kth=application/x-iwork-keynote-sffkth nmbtemplate=application/x-iwork-numbers-sfftemplate numbers=application/x-iwork-numbers-sffnumbers pages=application/x-iwork-pages-sffpages template=application/x-iwork-pages-sfftemplate # Extensions for Mozilla apps (Firefox and friends) xpi=application/x-xpinstall # Opera extensions oex=application/x-opera-extension revel-1.0.0/controller.go000066400000000000000000000422541370252312000153410ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package revel import ( "errors" "fmt" "io" "net/http" "os" "path/filepath" "reflect" "runtime" "strings" "time" "github.com/revel/revel/logger" "github.com/revel/revel/session" "github.com/revel/revel/utils" ) // Controller Revel's controller structure that gets embedded in user defined // controllers type Controller struct { Name string // The controller name, e.g. "Application" Type *ControllerType // A description of the controller type. MethodName string // The method name, e.g. "Index" MethodType *MethodType // A description of the invoked action type. AppController interface{} // The controller that was instantiated. embeds revel.Controller Action string // The fully qualified action name, e.g. "App.Index" ClientIP string // holds IP address of request came from Request *Request Response *Response Result Result Flash Flash // User cookie, cleared after 1 request. Session session.Session // Session, stored using the session engine specified Params *Params // Parameters from URL and form (including multipart). Args map[string]interface{} // Per-request scratch space. ViewArgs map[string]interface{} // Variables passed to the template. Validation *Validation // Data validation helpers Log logger.MultiLogger // Context Logger } // The map of controllers, controllers are mapped by using the namespace|controller_name as the key var controllers = make(map[string]*ControllerType) var controllerLog = RevelLog.New("section", "controller") // NewController returns new controller instance for Request and Response func NewControllerEmpty() *Controller { return &Controller{Request: NewRequest(nil), Response: NewResponse(nil)} } // New controller, creates a new instance wrapping the request and response in it func NewController(context ServerContext) *Controller { c := NewControllerEmpty() c.SetController(context) return c } // Sets the request and the response for the controller func (c *Controller) SetController(context ServerContext) { c.Request.SetRequest(context.GetRequest()) c.Response.SetResponse(context.GetResponse()) c.Request.controller = c c.Params = new(Params) c.Args = map[string]interface{}{} c.ViewArgs = map[string]interface{}{ "RunMode": RunMode, "DevMode": DevMode, } } func (c *Controller) Destroy() { // When the instantiated controller gets injected // It inherits this method, so we need to // check to see if the controller is nil before performing // any actions if c == nil { return } if c.AppController != nil { c.resetAppControllerFields() // Return this instance to the pool appController := c.AppController c.AppController = nil if RevelConfig.Controller.Reuse { RevelConfig.Controller.CachedMap[c.Name].Push(appController) } } c.Request.Destroy() c.Response.Destroy() c.Params = nil c.Args = nil c.ViewArgs = nil c.Name = "" c.Type = nil c.MethodName = "" c.MethodType = nil c.Action = "" c.ClientIP = "" c.Result = nil c.Flash = Flash{} c.Session = session.NewSession() c.Params = nil c.Validation = nil c.Log = nil } // FlashParams serializes the contents of Controller.Params to the Flash // cookie. func (c *Controller) FlashParams() { for key, vals := range c.Params.Values { c.Flash.Out[key] = strings.Join(vals, ",") } } func (c *Controller) SetCookie(cookie *http.Cookie) { c.Response.Out.internalHeader.SetCookie(cookie.String()) } type ErrorCoder interface { HTTPCode() int } func (c *Controller) RenderError(err error) Result { if coder, ok := err.(ErrorCoder); ok { c.setStatusIfNil(coder.HTTPCode()) } else { c.setStatusIfNil(http.StatusInternalServerError) } return ErrorResult{c.ViewArgs, err} } func (c *Controller) setStatusIfNil(status int) { if c.Response.Status == 0 { c.Response.Status = status } } // Render a template corresponding to the calling Controller method. // Arguments will be added to c.ViewArgs prior to rendering the template. // They are keyed on their local identifier. // // For example: // // func (c Users) ShowUser(id int) revel.Result { // user := loadUser(id) // return c.Render(user) // } // // This action will render views/Users/ShowUser.html, passing in an extra // key-value "user": (User). // // This is the slower magical version which uses the runtime // to determine // 1) Set c.ViewArgs to the arguments passed into this function // 2) How to call the RenderTemplate by building the following line // c.RenderTemplate(c.Name + "/" + c.MethodType.Name + "." + c.Request.Format) // // If you want your code to run faster it is recommended you add the template values directly // to the c.ViewArgs and call c.RenderTemplate directly func (c *Controller) Render(extraViewArgs ...interface{}) Result { c.setStatusIfNil(http.StatusOK) // Get the calling function line number. _, _, line, ok := runtime.Caller(1) if !ok { controllerLog.Error("Render: Failed to get Caller information") } // Get the extra ViewArgs passed in. if renderArgNames, ok := c.MethodType.RenderArgNames[line]; ok { if len(renderArgNames) == len(extraViewArgs) { for i, extraRenderArg := range extraViewArgs { c.ViewArgs[renderArgNames[i]] = extraRenderArg } } else { controllerLog.Error(fmt.Sprint(len(renderArgNames), "RenderArg names found for", len(extraViewArgs), "extra ViewArgs")) } } else { controllerLog.Error(fmt.Sprint("No RenderArg names found for Render call on line", line, "(Action", c.Action, ")"), "stack", logger.NewCallStack()) } return c.RenderTemplate(c.Name + "/" + c.MethodType.Name + "." + c.Request.Format) } // RenderTemplate method does less magical way to render a template. // Renders the given template, using the current ViewArgs. func (c *Controller) RenderTemplate(templatePath string) Result { c.setStatusIfNil(http.StatusOK) // Get the Template. lang, _ := c.ViewArgs[CurrentLocaleViewArg].(string) template, err := MainTemplateLoader.TemplateLang(templatePath, lang) if err != nil { return c.RenderError(err) } return &RenderTemplateResult{ Template: template, ViewArgs: c.ViewArgs, } } // TemplateOutput returns the result of the template rendered using the controllers ViewArgs. func (c *Controller) TemplateOutput(templatePath string) (data []byte, err error) { return TemplateOutputArgs(templatePath, c.ViewArgs) } // RenderJSON uses encoding/json.Marshal to return JSON to the client. func (c *Controller) RenderJSON(o interface{}) Result { c.setStatusIfNil(http.StatusOK) return RenderJSONResult{o, ""} } // RenderJSONP renders JSONP result using encoding/json.Marshal func (c *Controller) RenderJSONP(callback string, o interface{}) Result { c.setStatusIfNil(http.StatusOK) return RenderJSONResult{o, callback} } // RenderXML uses encoding/xml.Marshal to return XML to the client. func (c *Controller) RenderXML(o interface{}) Result { c.setStatusIfNil(http.StatusOK) return RenderXMLResult{o} } // RenderText renders plaintext in response, printf style. func (c *Controller) RenderText(text string, objs ...interface{}) Result { c.setStatusIfNil(http.StatusOK) finalText := text if len(objs) > 0 { finalText = fmt.Sprintf(text, objs...) } return &RenderTextResult{finalText} } // RenderHTML renders html in response func (c *Controller) RenderHTML(html string) Result { c.setStatusIfNil(http.StatusOK) return &RenderHTMLResult{html} } // Todo returns an HTTP 501 Not Implemented "todo" indicating that the // action isn't done yet. func (c *Controller) Todo() Result { c.Response.Status = http.StatusNotImplemented controllerLog.Debug("Todo: Not implemented function", "action", c.Action) return c.RenderError(&Error{ Title: "TODO", Description: "This action is not implemented", }) } // NotFound returns an HTTP 404 Not Found response whose body is the // formatted string of msg and objs. func (c *Controller) NotFound(msg string, objs ...interface{}) Result { finalText := msg if len(objs) > 0 { finalText = fmt.Sprintf(msg, objs...) } c.Response.Status = http.StatusNotFound return c.RenderError(&Error{ Title: "Not Found", Description: finalText, }) } // Forbidden returns an HTTP 403 Forbidden response whose body is the // formatted string of msg and objs. func (c *Controller) Forbidden(msg string, objs ...interface{}) Result { finalText := msg if len(objs) > 0 { finalText = fmt.Sprintf(msg, objs...) } c.Response.Status = http.StatusForbidden return c.RenderError(&Error{ Title: "Forbidden", Description: finalText, }) } // RenderFileName returns a file indicated by the path as provided via the filename. // It can be either displayed inline or downloaded as an attachment. // The name and size are taken from the file info. func (c *Controller) RenderFileName(filename string, delivery ContentDisposition) Result { f, err := os.Open(filename) if err != nil { c.Log.Errorf("Cant open file: %v", err) return c.RenderError(err) } return c.RenderFile(f, delivery) } // RenderFile returns a file, either displayed inline or downloaded // as an attachment. The name and size are taken from the file info. func (c *Controller) RenderFile(file *os.File, delivery ContentDisposition) Result { c.setStatusIfNil(http.StatusOK) var ( modtime = time.Now() fileInfo, err = file.Stat() ) if err != nil { controllerLog.Error("RenderFile: error", "error", err) } if fileInfo != nil { modtime = fileInfo.ModTime() } return c.RenderBinary(file, filepath.Base(file.Name()), delivery, modtime) } // RenderBinary is like RenderFile() except that it instead of a file on disk, // it renders data from memory (which could be a file that has not been written, // the output from some function, or bytes streamed from somewhere else, as long // it implements io.Reader). When called directly on something generated or // streamed, modtime should mostly likely be time.Now(). func (c *Controller) RenderBinary(memfile io.Reader, filename string, delivery ContentDisposition, modtime time.Time) Result { c.setStatusIfNil(http.StatusOK) return &BinaryResult{ Reader: memfile, Name: filename, Delivery: delivery, Length: -1, // http.ServeContent gets the length itself unless memfile is a stream. ModTime: modtime, } } // Redirect to an action or to a URL. // c.Redirect(Controller.Action) // c.Redirect("/controller/action") // c.Redirect("/controller/%d/action", id) func (c *Controller) Redirect(val interface{}, args ...interface{}) Result { c.setStatusIfNil(http.StatusFound) if url, ok := val.(string); ok { if len(args) == 0 { return &RedirectToURLResult{url} } return &RedirectToURLResult{fmt.Sprintf(url, args...)} } return &RedirectToActionResult{val, args} } // This stats returns some interesting stats based on what is cached in memory // and what is available directly func (c *Controller) Stats() map[string]interface{} { result := CurrentEngine.Stats() if RevelConfig.Controller.Reuse { result["revel-controllers"] = RevelConfig.Controller.Stack.String() for key, appStack := range RevelConfig.Controller.CachedMap { result["app-" + key] = appStack.String() } } return result } // Message performs a lookup for the given message name using the given // arguments using the current language defined for this controller. // // The current language is set by the i18n plugin. func (c *Controller) Message(message string, args ...interface{}) string { return MessageFunc(c.Request.Locale, message, args...) } // SetAction sets the action that is being invoked in the current request. // It sets the following properties: Name, Action, Type, MethodType func (c *Controller) SetAction(controllerName, methodName string) error { return c.SetTypeAction(controllerName, methodName, nil) } // SetAction sets the assigns the Controller type, sets the action and initializes the controller func (c *Controller) SetTypeAction(controllerName, methodName string, typeOfController *ControllerType) error { // Look up the controller and method types. if typeOfController == nil { if c.Type = ControllerTypeByName(controllerName, anyModule); c.Type == nil { return errors.New("revel/controller: failed to find controller " + controllerName) } } else { c.Type = typeOfController } // Note method name is case insensitive search if c.MethodType = c.Type.Method(methodName); c.MethodType == nil { return errors.New("revel/controller: failed to find action " + controllerName + "." + methodName) } c.Name, c.MethodName = c.Type.Type.Name(), c.MethodType.Name c.Action = c.Name + "." + c.MethodName // Update Logger with controller and namespace if c.Log != nil { c.Log = c.Log.New("action", c.Action, "namespace", c.Type.Namespace) } if RevelConfig.Controller.Reuse { if _, ok := RevelConfig.Controller.CachedMap[c.Name]; !ok { // Create a new stack for this controller localType := c.Type.Type RevelConfig.Controller.CachedMap[c.Name] = utils.NewStackLock( RevelConfig.Controller.CachedStackSize, RevelConfig.Controller.CachedStackMaxSize, func() interface{} { return reflect.New(localType).Interface() }) } // Instantiate the controller. c.AppController = RevelConfig.Controller.CachedMap[c.Name].Pop() } else { c.AppController = reflect.New(c.Type.Type).Interface() } c.setAppControllerFields() return nil } func ControllerTypeByName(controllerName string, moduleSource *Module) (c *ControllerType) { var found bool if c, found = controllers[controllerName]; !found { // Backup, passed in controllerName should be in lower case, but may not be if c, found = controllers[strings.ToLower(controllerName)]; !found { controllerLog.Debug("ControllerTypeByName: Cannot find controller in controllers map ", "controller", controllerName) // Search for the controller by name for _, cType := range controllers { testControllerName := strings.ToLower(cType.Type.Name()) if testControllerName == strings.ToLower(controllerName) && (cType.ModuleSource == moduleSource || moduleSource == anyModule) { controllerLog.Warn("ControllerTypeByName: Matched empty namespace controller ", "controller", controllerName, "namespace", cType.ModuleSource.Name) c = cType found = true break } } } } return } // Injects this instance (c) into the AppController instance func (c *Controller) setAppControllerFields() { appController := reflect.ValueOf(c.AppController).Elem() cValue := reflect.ValueOf(c) for _, index := range c.Type.ControllerIndexes { appController.FieldByIndex(index).Set(cValue) } } // Removes this instance (c) from the AppController instance func (c *Controller) resetAppControllerFields() { appController := reflect.ValueOf(c.AppController).Elem() // Zero out controller for _, index := range c.Type.ControllerIndexes { appController.FieldByIndex(index).Set(reflect.Zero(reflect.TypeOf(c.AppController).Elem().FieldByIndex(index).Type)) } } func findControllers(appControllerType reflect.Type) (indexes [][]int) { // It might be a multi-level embedding. To find the controllers, we follow // every anonymous field, using breadth-first search. type nodeType struct { val reflect.Value index []int } appControllerPtr := reflect.New(appControllerType) queue := []nodeType{{appControllerPtr, []int{}}} for len(queue) > 0 { // Get the next value and de-reference it if necessary. var ( node = queue[0] elem = node.val elemType = elem.Type() ) if elemType.Kind() == reflect.Ptr { elem = elem.Elem() elemType = elem.Type() } queue = queue[1:] // #944 if the type's Kind is not `Struct` move on, // otherwise `elem.NumField()` will panic if elemType.Kind() != reflect.Struct { continue } // Look at all the struct fields. for i := 0; i < elem.NumField(); i++ { // If this is not an anonymous field, skip it. structField := elemType.Field(i) if !structField.Anonymous { continue } fieldValue := elem.Field(i) fieldType := structField.Type // If it's a Controller, record the field indexes to get here. if fieldType == controllerPtrType { indexes = append(indexes, append(node.index, i)) continue } queue = append(queue, nodeType{fieldValue, append(append([]int{}, node.index...), i)}) } } return } // RegisterController registers a Controller and its Methods with Revel. func RegisterController(c interface{}, methods []*MethodType) { // De-star the controller type // (e.g. given TypeOf((*Application)(nil)), want TypeOf(Application)) elem := reflect.TypeOf(c).Elem() // De-star all of the method arg types too. for _, m := range methods { m.lowerName = strings.ToLower(m.Name) for _, arg := range m.Args { arg.Type = arg.Type.Elem() } } // Fetch module for controller, if none found controller must be part of the app controllerModule := ModuleFromPath(elem.PkgPath(), true) controllerType := AddControllerType(controllerModule, elem, methods) controllerLog.Debug("RegisterController:Registered controller", "controller", controllerType.Name(), "module-name", controllerModule.Name) } revel-1.0.0/controller_type.go000066400000000000000000000130231370252312000163720ustar00rootroot00000000000000package revel import ( "reflect" "strings" ) // Controller registry and types. type ControllerType struct { Namespace string // The namespace of the controller ModuleSource *Module // The module for the controller Type reflect.Type Methods []*MethodType ControllerIndexes [][]int // FieldByIndex to all embedded *Controllers ControllerEvents *ControllerTypeEvents } type ControllerTypeEvents struct { Before, After, Finally, Panic []*ControllerFieldPath } // The controller field path provides the caller the ability to invoke the call // directly type ControllerFieldPath struct { IsPointer bool FieldIndexPath []int FunctionCall reflect.Value } type MethodType struct { Name string Args []*MethodArg RenderArgNames map[int][]string lowerName string Index int } type MethodArg struct { Name string Type reflect.Type } // Adds the controller to the controllers map using its namespace, also adds it to the module list of controllers. // If the controller is in the main application it is added without its namespace as well. func AddControllerType(moduleSource *Module, controllerType reflect.Type, methods []*MethodType) (newControllerType *ControllerType) { if moduleSource == nil { moduleSource = appModule } newControllerType = &ControllerType{ModuleSource: moduleSource, Type: controllerType, Methods: methods, ControllerIndexes: findControllers(controllerType)} newControllerType.ControllerEvents = NewControllerTypeEvents(newControllerType) newControllerType.Namespace = moduleSource.Namespace() controllerName := newControllerType.Name() // Store the first controller only in the controllers map with the unmapped namespace. if _, found := controllers[controllerName]; !found { controllers[controllerName] = newControllerType newControllerType.ModuleSource.AddController(newControllerType) if newControllerType.ModuleSource == appModule { // Add the controller mapping into the global namespace controllers[newControllerType.ShortName()] = newControllerType } } else { controllerLog.Errorf("Error, attempt to register duplicate controller as %s", controllerName) } controllerLog.Debugf("Registered controller: %s", controllerName) return } // Method searches for a given exported method (case insensitive) func (ct *ControllerType) Method(name string) *MethodType { lowerName := strings.ToLower(name) for _, method := range ct.Methods { if method.lowerName == lowerName { return method } } return nil } // The controller name with the namespace func (ct *ControllerType) Name() string { return ct.Namespace + ct.ShortName() } // The controller name without the namespace func (ct *ControllerType) ShortName() string { return strings.ToLower(ct.Type.Name()) } func NewControllerTypeEvents(c *ControllerType) (ce *ControllerTypeEvents) { ce = &ControllerTypeEvents{} // Parse the methods for the controller type, assign any control methods checkType := c.Type ce.check(checkType, []int{}) return } // Add in before after panic and finally, recursive call // Befores are ordered in revers, everything else is in order of first encountered func (cte *ControllerTypeEvents) check(theType reflect.Type, fieldPath []int) { typeChecker := func(checkType reflect.Type) { for index := 0; index < checkType.NumMethod(); index++ { m := checkType.Method(index) // Must be two arguments, the second returns the controller type // Go cannot differentiate between promoted methods and // embedded methods, this allows the embedded method to be run // https://github.com/golang/go/issues/21162 if m.Type.NumOut() == 2 && m.Type.Out(1) == checkType { if checkType.Kind() == reflect.Ptr { controllerLog.Debug("Found controller type event method pointer", "name", checkType.Elem().Name(), "methodname", m.Name) } else { controllerLog.Debug("Found controller type event method", "name", checkType.Name(), "methodname", m.Name) } controllerFieldPath := newFieldPath(checkType.Kind() == reflect.Ptr, m.Func, fieldPath) switch strings.ToLower(m.Name) { case "before": cte.Before = append([]*ControllerFieldPath{controllerFieldPath}, cte.Before...) case "after": cte.After = append(cte.After, controllerFieldPath) case "panic": cte.Panic = append(cte.Panic, controllerFieldPath) case "finally": cte.Finally = append(cte.Finally, controllerFieldPath) } } } } // Check methods of both types typeChecker(theType) typeChecker(reflect.PtrTo(theType)) // Check for any sub controllers, ignore any pointers to controllers revel.Controller for i := 0; i < theType.NumField(); i++ { v := theType.Field(i) switch v.Type.Kind() { case reflect.Struct: cte.check(v.Type, append(fieldPath, i)) } } } func newFieldPath(isPointer bool, value reflect.Value, fieldPath []int) *ControllerFieldPath { return &ControllerFieldPath{IsPointer: isPointer, FunctionCall: value, FieldIndexPath: fieldPath} } func (fieldPath *ControllerFieldPath) Invoke(value reflect.Value, input []reflect.Value) (result []reflect.Value) { for _, index := range fieldPath.FieldIndexPath { // You can only fetch fields from non pointers if value.Type().Kind() == reflect.Ptr { value = value.Elem().Field(index) } else { value = value.Field(index) } } if fieldPath.IsPointer && value.Type().Kind() != reflect.Ptr { value = value.Addr() } else if !fieldPath.IsPointer && value.Type().Kind() == reflect.Ptr { value = value.Elem() } return fieldPath.FunctionCall.Call(append([]reflect.Value{value}, input...)) } revel-1.0.0/errors.go000066400000000000000000000113611370252312000144650ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package revel import ( "fmt" "path/filepath" "runtime/debug" "strconv" "strings" ) // Error description, used as an argument to the error template. type Error struct { SourceType string // The type of source that failed to build. Title, Path, Description string // Description of the error, as presented to the user. Line, Column int // Where the error was encountered. SourceLines []string // The entire source file, split into lines. Stack string // The raw stack trace string from debug.Stack(). MetaError string // Error that occurred producing the error page. Link string // A configurable link to wrap the error source in } // SourceLine structure to hold the per-source-line details. type SourceLine struct { Source string Line int IsError bool } // NewErrorFromPanic method finds the deepest stack from in user code and // provide a code listing of that, on the line that eventually triggered // the panic. Returns nil if no relevant stack frame can be found. func NewErrorFromPanic(err interface{}) *Error { // Parse the filename and line from the originating line of app code. // /Users/robfig/code/gocode/src/revel/examples/booking/app/controllers/hotels.go:191 (0x44735) stack := string(debug.Stack()) frame, basePath := findRelevantStackFrame(stack) if frame == -1 { return nil } stack = stack[frame:] stackElement := stack[:strings.Index(stack, "\n")] colonIndex := strings.LastIndex(stackElement, ":") filename := stackElement[:colonIndex] var line int fmt.Sscan(stackElement[colonIndex+1:], &line) // Show an error page. description := "Unspecified error" if err != nil { description = fmt.Sprint(err) } lines, readErr := ReadLines(filename) if readErr != nil { utilLog.Error("Unable to read file", "file", filename, "error", readErr) } return &Error{ Title: "Runtime Panic", Path: filename[len(basePath):], Line: line, Description: description, SourceLines: lines, Stack: stack, } } // Error method constructs a plaintext version of the error, taking // account that fields are optionally set. Returns e.g. Compilation Error // (in views/header.html:51): expected right delim in end; got "}" func (e *Error) Error() string { loc := "" if e.Path != "" { line := "" if e.Line != 0 { line = fmt.Sprintf(":%d", e.Line) } loc = fmt.Sprintf("(in %s%s)", e.Path, line) } header := loc if e.Title != "" { if loc != "" { header = fmt.Sprintf("%s %s: ", e.Title, loc) } else { header = fmt.Sprintf("%s: ", e.Title) } } return fmt.Sprintf("%s%s Stack: %s", header, e.Description, e.Stack) } // ContextSource method returns a snippet of the source around // where the error occurred. func (e *Error) ContextSource() []SourceLine { if e.SourceLines == nil { return nil } start := (e.Line - 1) - 5 if start < 0 { start = 0 } end := (e.Line - 1) + 5 if end > len(e.SourceLines) { end = len(e.SourceLines) } lines := make([]SourceLine, end-start) for i, src := range e.SourceLines[start:end] { fileLine := start + i + 1 lines[i] = SourceLine{src, fileLine, fileLine == e.Line} } return lines } // SetLink method prepares a link and assign to Error.Link attribute func (e *Error) SetLink(errorLink string) { errorLink = strings.Replace(errorLink, "{{Path}}", e.Path, -1) errorLink = strings.Replace(errorLink, "{{Line}}", strconv.Itoa(e.Line), -1) e.Link = "" + e.Path + ":" + strconv.Itoa(e.Line) + "" } // Return the character index of the first relevant stack frame, or -1 if none were found. // Additionally it returns the base path of the tree in which the identified code resides. func findRelevantStackFrame(stack string) (int, string) { // Find first item in SourcePath that isn't in RevelPath. // If first item is in RevelPath, keep track of position, trim and check again. partialStack := stack sourcePath := filepath.ToSlash(SourcePath) revelPath := filepath.ToSlash(RevelPath) sumFrame := 0 for { frame := strings.Index(partialStack, sourcePath) revelFrame := strings.Index(partialStack, revelPath) if frame == -1 { break } else if frame != revelFrame { return sumFrame + frame, SourcePath } else { // Need to at least trim off the first character so this frame isn't caught again. partialStack = partialStack[frame+1:] sumFrame += frame + 1 } } for _, module := range Modules { if frame := strings.Index(stack, filepath.ToSlash(module.Path)); frame != -1 { return frame, module.Path } } return -1, "" } revel-1.0.0/event.go000066400000000000000000000037651370252312000143030ustar00rootroot00000000000000package revel type ( // The event type Event int // The event response EventResponse int // The handler signature EventHandler func(typeOf Event, value interface{}) (responseOf EventResponse) ) const ( // Event type when templates are going to be refreshed (receivers are registered template engines added to the template.engine conf option) TEMPLATE_REFRESH_REQUESTED Event = iota // Event type when templates are refreshed (receivers are registered template engines added to the template.engine conf option) TEMPLATE_REFRESH_COMPLETED // Event type before all module loads, events thrown to handlers added to AddInitEventHandler // Event type before all module loads, events thrown to handlers added to AddInitEventHandler REVEL_BEFORE_MODULES_LOADED // Event type after all module loads, events thrown to handlers added to AddInitEventHandler REVEL_AFTER_MODULES_LOADED // Event type before server engine is initialized, receivers are active server engine and handlers added to AddInitEventHandler ENGINE_BEFORE_INITIALIZED // Event type before server engine is started, receivers are active server engine and handlers added to AddInitEventHandler ENGINE_STARTED // Event raised when the engine is told to shutdown ENGINE_SHUTDOWN_REQUEST // Event type after server engine is stopped, receivers are active server engine and handlers added to AddInitEventHandler ENGINE_SHUTDOWN // Called before routes are refreshed ROUTE_REFRESH_REQUESTED // Called after routes have been refreshed ROUTE_REFRESH_COMPLETED // Fired when a panic is caught during the startup process REVEL_FAILURE ) // Fires system events from revel func RaiseEvent(key Event, value interface{}) (response EventResponse) { utilLog.Info("Raising event", "len", len(initEventList)) for _, handler := range initEventList { response |= handler(key, value) } return } // Add event handler to listen for all system events func AddInitEventHandler(handler EventHandler) { initEventList = append(initEventList, handler) return } revel-1.0.0/event_test.go000066400000000000000000000013501370252312000153260ustar00rootroot00000000000000package revel_test import ( "github.com/revel/revel" "github.com/stretchr/testify/assert" "testing" ) // Test that the event handler can be attached and it dispatches the event received func TestEventHandler(t *testing.T) { counter := 0 newListener := func(typeOf revel.Event, value interface{}) (responseOf revel.EventResponse) { if typeOf == revel.REVEL_FAILURE { counter++ } return } // Attach the same handlder twice so we expect to see the response twice as well revel.AddInitEventHandler(newListener) revel.AddInitEventHandler(newListener) revel.RaiseEvent(revel.REVEL_AFTER_MODULES_LOADED, nil) revel.RaiseEvent(revel.REVEL_FAILURE, nil) assert.Equal(t, counter, 2, "Expected event handler to have been called") } revel-1.0.0/fakeapp_test.go000066400000000000000000000071011370252312000156140ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package revel import ( "os" "path/filepath" "reflect" ) type Hotel struct { HotelID int Name, Address string City, State, Zip string Country string Price int } type Hotels struct { *Controller } type Static struct { *Controller } type Implicit struct { *Controller } type Application struct { *Controller } func (c Hotels) Show(id int) Result { title := "View Hotel" hotel := &Hotel{id, "A Hotel", "300 Main St.", "New York", "NY", "10010", "USA", 300} // The line number below must match the one with the code : RenderArgNames: map[int][]string{43: {"title", "hotel"}}, return c.Render(title, hotel) } func (c Hotels) Book(id int) Result { hotel := &Hotel{id, "A Hotel", "300 Main St.", "New York", "NY", "10010", "USA", 300} return c.RenderJSON(hotel) } func (c Hotels) Index() Result { return c.RenderText("Hello, World!") } func (c Static) Serve(prefix, path string) Result { var basePath, dirName string if !filepath.IsAbs(dirName) { basePath = BasePath } fname := filepath.Join(basePath, prefix, path) file, err := os.Open(fname) if os.IsNotExist(err) { return c.NotFound("") } else if err != nil { RevelLog.Errorf("Problem opening file (%s): %s ", fname, err) return c.NotFound("This was found but not sure why we couldn't open it.") } return c.RenderFile(file, "") } // Register controllers is in its own function so the route test can use it as well func registerControllers() { controllers = make(map[string]*ControllerType) RaiseEvent(ROUTE_REFRESH_REQUESTED, nil) RegisterController((*Hotels)(nil), []*MethodType{ { Name: "Index", }, { Name: "Show", Args: []*MethodArg{ {"id", reflect.TypeOf((*int)(nil))}, }, RenderArgNames: map[int][]string{41: {"title", "hotel"}}, }, { Name: "Book", Args: []*MethodArg{ {"id", reflect.TypeOf((*int)(nil))}, }, }, }) RegisterController((*Static)(nil), []*MethodType{ { Name: "Serve", Args: []*MethodArg{ {Name: "prefix", Type: reflect.TypeOf((*string)(nil))}, {Name: "filepath", Type: reflect.TypeOf((*string)(nil))}, }, RenderArgNames: map[int][]string{}, }, }) RegisterController((*Implicit)(nil), []*MethodType{ { Name: "Implicit", Args: []*MethodArg{ {Name: "prefix", Type: reflect.TypeOf((*string)(nil))}, {Name: "filepath", Type: reflect.TypeOf((*string)(nil))}, }, RenderArgNames: map[int][]string{}, }, }) RegisterController((*Application)(nil), []*MethodType{ { Name: "Application", Args: []*MethodArg{ {Name: "prefix", Type: reflect.TypeOf((*string)(nil))}, {Name: "filepath", Type: reflect.TypeOf((*string)(nil))}, }, RenderArgNames: map[int][]string{}, }, { Name: "Index", Args: []*MethodArg{ {Name: "foo", Type: reflect.TypeOf((*string)(nil))}, {Name: "bar", Type: reflect.TypeOf((*string)(nil))}, }, RenderArgNames: map[int][]string{}, }, }) } func startFakeBookingApp() { Init("prod", "github.com/revel/revel/testdata", "") MainTemplateLoader = NewTemplateLoader([]string{ViewsPath, filepath.Join(RevelPath, "templates")}) if err := MainTemplateLoader.Refresh(); err != nil { RevelLog.Fatal("Template error","error",err) } registerControllers() InitServerEngine(9000, GO_NATIVE_SERVER_ENGINE) RaiseEvent(ENGINE_BEFORE_INITIALIZED, nil) InitServer() RaiseEvent(ENGINE_STARTED, nil) } revel-1.0.0/field.go000066400000000000000000000047401370252312000142370ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package revel import ( "reflect" "strings" ) // Field represents a data field that may be collected in a web form. type Field struct { Name string Error *ValidationError viewArgs map[string]interface{} controller *Controller } func NewField(name string, viewArgs map[string]interface{}) *Field { err, _ := viewArgs["errors"].(map[string]*ValidationError)[name] controller, _ := viewArgs["_controller"].(*Controller) return &Field{ Name: name, Error: err, viewArgs: viewArgs, controller: controller, } } // ID returns an identifier suitable for use as an HTML id. func (f *Field) ID() string { return strings.Replace(f.Name, ".", "_", -1) } // Flash returns the flashed value of this Field. func (f *Field) Flash() string { v, _ := f.viewArgs["flash"].(map[string]string)[f.Name] return v } // Options returns the option list of this Field. func (f *Field) Options() []string { if f.viewArgs["options"] == nil { return nil } v, _ := f.viewArgs["options"].(map[string][]string)[f.Name] return v } // FlashArray returns the flashed value of this Field as a list split on comma. func (f *Field) FlashArray() []string { v := f.Flash() if v == "" { return []string{} } return strings.Split(v, ",") } // Value returns the current value of this Field. func (f *Field) Value() interface{} { pieces := strings.Split(f.Name, ".") answer, ok := f.viewArgs[pieces[0]] if !ok { return "" } val := reflect.ValueOf(answer) for i := 1; i < len(pieces); i++ { if val.Kind() == reflect.Ptr { val = val.Elem() } val = val.FieldByName(pieces[i]) if !val.IsValid() { return "" } } return val.Interface() } // ErrorClass returns ErrorCSSClass if this field has a validation error, else empty string. func (f *Field) ErrorClass() string { if f.Error != nil { if errorClass, ok := f.viewArgs["ERROR_CLASS"]; ok { return errorClass.(string) } return ErrorCSSClass } return "" } // Get the short name and translate it func (f *Field) ShortName() string { name := f.Name if i := strings.LastIndex(name, "."); i > 0 { name = name[i+1:] } return f.Translate(name) } // Translate the text func (f *Field) Translate(text string, args ...interface{}) string { if f.controller != nil { text = f.controller.Message(text, args...) } return text } revel-1.0.0/filter.go000066400000000000000000000025231370252312000144360ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package revel // Filter type definition for Revel's filter type Filter func(c *Controller, filterChain []Filter) // Filters is the default set of global filters. // It may be set by the application on initialization. var Filters = []Filter{ PanicFilter, // Recover from panics and display an error page instead. RouterFilter, // Use the routing table to select the right Action. FilterConfiguringFilter, // A hook for adding or removing per-Action filters. ParamsFilter, // Parse parameters into Controller.Params. SessionFilter, // Restore and write the session cookie. FlashFilter, // Restore and write the flash cookie. ValidationFilter, // Restore kept validation errors and save new ones from cookie. I18nFilter, // Resolve the requested language. InterceptorFilter, // Run interceptors around the action. CompressFilter, // Compress the result. BeforeAfterFilter, ActionInvoker, // Invoke the action. } // NilFilter and NilChain are helpful in writing filter tests. var ( NilFilter = func(_ *Controller, _ []Filter) {} NilChain = []Filter{NilFilter} ) revel-1.0.0/filterconfig.go000066400000000000000000000155611370252312000156320ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package revel import ( "reflect" "strings" ) // Map from "Controller" or "Controller.Method" to the Filter chain var filterOverrides = make(map[string][]Filter) // FilterConfigurator allows the developer configure the filter chain on a // per-controller or per-action basis. The filter configuration is applied by // the FilterConfiguringFilter, which is itself a filter stage. For example, // // Assuming: // Filters = []Filter{ // RouterFilter, // FilterConfiguringFilter, // SessionFilter, // ActionInvoker, // } // // Add: // FilterAction(App.Action). // Add(OtherFilter) // // => RouterFilter, FilterConfiguringFilter, SessionFilter, OtherFilter, ActionInvoker // // Remove: // FilterAction(App.Action). // Remove(SessionFilter) // // => RouterFilter, FilterConfiguringFilter, OtherFilter, ActionInvoker // // Insert: // FilterAction(App.Action). // Insert(OtherFilter, revel.BEFORE, SessionFilter) // // => RouterFilter, FilterConfiguringFilter, OtherFilter, SessionFilter, ActionInvoker // // Filter modifications may be combined between Controller and Action. For example: // FilterController(App{}). // Add(Filter1) // FilterAction(App.Action). // Add(Filter2) // // .. would result in App.Action being filtered by both Filter1 and Filter2. // // Note: the last filter stage is not subject to the configurator. In // particular, Add() adds a filter to the second-to-last place. type FilterConfigurator struct { key string // e.g. "App", "App.Action" controllerName string // e.g. "App" } func newFilterConfigurator(controllerName, methodName string) FilterConfigurator { if methodName == "" { return FilterConfigurator{controllerName, controllerName} } return FilterConfigurator{controllerName + "." + methodName, controllerName} } // FilterController returns a configurator for the filters applied to all // actions on the given controller instance. For example: // FilterController(MyController{}) func FilterController(controllerInstance interface{}) FilterConfigurator { t := reflect.TypeOf(controllerInstance) for t.Kind() == reflect.Ptr { t = t.Elem() } return newFilterConfigurator(t.Name(), "") } // FilterAction returns a configurator for the filters applied to the given // controller method. For example: // FilterAction(MyController.MyAction) func FilterAction(methodRef interface{}) FilterConfigurator { var ( methodValue = reflect.ValueOf(methodRef) methodType = methodValue.Type() ) if methodType.Kind() != reflect.Func || methodType.NumIn() == 0 { panic("Expecting a controller method reference (e.g. Controller.Action), got a " + methodType.String()) } controllerType := methodType.In(0) method := FindMethod(controllerType, methodValue) if method == nil { panic("Action not found on controller " + controllerType.Name()) } for controllerType.Kind() == reflect.Ptr { controllerType = controllerType.Elem() } return newFilterConfigurator(controllerType.Name(), method.Name) } // Add the given filter in the second-to-last position in the filter chain. // (Second-to-last so that it is before ActionInvoker) func (conf FilterConfigurator) Add(f Filter) FilterConfigurator { conf.apply(func(fc []Filter) []Filter { return conf.addFilter(f, fc) }) return conf } func (conf FilterConfigurator) addFilter(f Filter, fc []Filter) []Filter { return append(fc[:len(fc)-1], f, fc[len(fc)-1]) } // Remove a filter from the filter chain. func (conf FilterConfigurator) Remove(target Filter) FilterConfigurator { conf.apply(func(fc []Filter) []Filter { return conf.rmFilter(target, fc) }) return conf } func (conf FilterConfigurator) rmFilter(target Filter, fc []Filter) []Filter { for i, f := range fc { if FilterEq(f, target) { return append(fc[:i], fc[i+1:]...) } } return fc } // Insert a filter into the filter chain before or after another. // This may be called with the BEFORE or AFTER constants, for example: // revel.FilterAction(App.Index). // Insert(MyFilter, revel.BEFORE, revel.ActionInvoker). // Insert(MyFilter2, revel.AFTER, revel.PanicFilter) func (conf FilterConfigurator) Insert(insert Filter, where When, target Filter) FilterConfigurator { if where != BEFORE && where != AFTER { panic("where must be BEFORE or AFTER") } conf.apply(func(fc []Filter) []Filter { return conf.insertFilter(insert, where, target, fc) }) return conf } func (conf FilterConfigurator) insertFilter(insert Filter, where When, target Filter, fc []Filter) []Filter { for i, f := range fc { if FilterEq(f, target) { if where == BEFORE { return append(fc[:i], append([]Filter{insert}, fc[i:]...)...) } return append(fc[:i+1], append([]Filter{insert}, fc[i+1:]...)...) } } return fc } // getChain returns the filter chain that applies to the given controller or // action. If no overrides are configured, then a copy of the default filter // chain is returned. func (conf FilterConfigurator) getChain() []Filter { var filters []Filter if filters = getOverrideChain(conf.controllerName, conf.key); filters == nil { // The override starts with all filters after FilterConfiguringFilter for i, f := range Filters { if FilterEq(f, FilterConfiguringFilter) { filters = make([]Filter, len(Filters)-i-1) copy(filters, Filters[i+1:]) break } } if filters == nil { panic("FilterConfiguringFilter not found in revel.Filters.") } } return filters } // apply applies the given functional change to the filter overrides. // No other function modifies the filterOverrides map. func (conf FilterConfigurator) apply(f func([]Filter) []Filter) { // Updates any actions that have had their filters overridden, if this is a // Controller configurator. if conf.controllerName == conf.key { for k, v := range filterOverrides { if strings.HasPrefix(k, conf.controllerName+".") { filterOverrides[k] = f(v) } } } // Update the Controller or Action overrides. filterOverrides[conf.key] = f(conf.getChain()) } // FilterEq returns true if the two filters reference the same filter. func FilterEq(a, b Filter) bool { return reflect.ValueOf(a).Pointer() == reflect.ValueOf(b).Pointer() } // FilterConfiguringFilter is a filter stage that customizes the remaining // filter chain for the action being invoked. func FilterConfiguringFilter(c *Controller, fc []Filter) { if newChain := getOverrideChain(c.Name, c.Action); newChain != nil { newChain[0](c, newChain[1:]) return } fc[0](c, fc[1:]) } // getOverrideChain retrieves the overrides for the action that is set func getOverrideChain(controllerName, action string) []Filter { if newChain, ok := filterOverrides[action]; ok { return newChain } if newChain, ok := filterOverrides[controllerName]; ok { return newChain } return nil } revel-1.0.0/filterconfig_test.go000066400000000000000000000070141370252312000166630ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package revel import "testing" type FakeController struct{} func (c FakeController) Foo() {} func (c *FakeController) Bar() {} func TestFilterConfiguratorKey(t *testing.T) { conf := FilterController(FakeController{}) if conf.key != "FakeController" { t.Errorf("Expected key 'FakeController', was %s", conf.key) } conf = FilterController(&FakeController{}) if conf.key != "FakeController" { t.Errorf("Expected key 'FakeController', was %s", conf.key) } conf = FilterAction(FakeController.Foo) if conf.key != "FakeController.Foo" { t.Errorf("Expected key 'FakeController.Foo', was %s", conf.key) } conf = FilterAction((*FakeController).Bar) if conf.key != "FakeController.Bar" { t.Errorf("Expected key 'FakeController.Bar', was %s", conf.key) } } func TestFilterConfigurator(t *testing.T) { // Filters is global state. Restore it after this test. oldFilters := make([]Filter, len(Filters)) copy(oldFilters, Filters) defer func() { Filters = oldFilters }() Filters = []Filter{ RouterFilter, FilterConfiguringFilter, SessionFilter, FlashFilter, ActionInvoker, } // Do one of each operation. conf := FilterAction(FakeController.Foo). Add(NilFilter). Remove(FlashFilter). Insert(ValidationFilter, BEFORE, NilFilter). Insert(I18nFilter, AFTER, NilFilter) expected := []Filter{ SessionFilter, ValidationFilter, NilFilter, I18nFilter, ActionInvoker, } actual := getOverride("Foo") if len(actual) != len(expected) || !filterSliceEqual(actual, expected) { t.Errorf("Ops failed.\nActual: %#v\nExpect: %#v\nConf:%v", actual, expected, conf) } // Action2 should be unchanged if getOverride("Bar") != nil { t.Errorf("Filtering Action should not affect Action2.") } // Test that combining overrides on both the Controller and Action works. FilterController(FakeController{}). Add(PanicFilter) expected = []Filter{ SessionFilter, ValidationFilter, NilFilter, I18nFilter, PanicFilter, ActionInvoker, } actual = getOverride("Foo") if len(actual) != len(expected) || !filterSliceEqual(actual, expected) { t.Errorf("Expected PanicFilter added to Foo.\nActual: %#v\nExpect: %#v", actual, expected) } expected = []Filter{ SessionFilter, FlashFilter, PanicFilter, ActionInvoker, } actual = getOverride("Bar") if len(actual) != len(expected) || !filterSliceEqual(actual, expected) { t.Errorf("Expected PanicFilter added to Bar.\nActual: %#v\nExpect: %#v", actual, expected) } FilterAction((*FakeController).Bar). Add(NilFilter) expected = []Filter{ SessionFilter, ValidationFilter, NilFilter, I18nFilter, PanicFilter, ActionInvoker, } actual = getOverride("Foo") if len(actual) != len(expected) || !filterSliceEqual(actual, expected) { t.Errorf("Expected no change to Foo.\nActual: %#v\nExpect: %#v", actual, expected) } expected = []Filter{ SessionFilter, FlashFilter, PanicFilter, NilFilter, ActionInvoker, } actual = getOverride("Bar") if len(actual) != len(expected) || !filterSliceEqual(actual, expected) { t.Errorf("Expected NilFilter added to Bar.\nActual: %#v\nExpect: %#v", actual, expected) } } func filterSliceEqual(a, e []Filter) bool { for i, f := range a { if !FilterEq(f, e[i]) { return false } } return true } func getOverride(methodName string) []Filter { return getOverrideChain("FakeController", "FakeController."+methodName) } revel-1.0.0/flash.go000066400000000000000000000044041370252312000142460ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package revel import ( "fmt" "net/http" "net/url" ) // Flash represents a cookie that is overwritten on each request. // It allows data to be stored across one page at a time. // This is commonly used to implement success or error messages. // E.g. the Post/Redirect/Get pattern: // http://en.wikipedia.org/wiki/Post/Redirect/Get type Flash struct { // `Data` is the input which is read in `restoreFlash`, `Out` is the output which is set in a FLASH cookie at the end of the `FlashFilter()` Data, Out map[string]string } // Error serializes the given msg and args to an "error" key within // the Flash cookie. func (f Flash) Error(msg string, args ...interface{}) { if len(args) == 0 { f.Out["error"] = msg } else { f.Out["error"] = fmt.Sprintf(msg, args...) } } // Success serializes the given msg and args to a "success" key within // the Flash cookie. func (f Flash) Success(msg string, args ...interface{}) { if len(args) == 0 { f.Out["success"] = msg } else { f.Out["success"] = fmt.Sprintf(msg, args...) } } // FlashFilter is a Revel Filter that retrieves and sets the flash cookie. // Within Revel, it is available as a Flash attribute on Controller instances. // The name of the Flash cookie is set as CookiePrefix + "_FLASH". func FlashFilter(c *Controller, fc []Filter) { c.Flash = restoreFlash(c.Request) c.ViewArgs["flash"] = c.Flash.Data fc[0](c, fc[1:]) // Store the flash. var flashValue string for key, value := range c.Flash.Out { flashValue += "\x00" + key + ":" + value + "\x00" } c.SetCookie(&http.Cookie{ Name: CookiePrefix + "_FLASH", Value: url.QueryEscape(flashValue), HttpOnly: true, Secure: CookieSecure, SameSite: CookieSameSite, Path: "/", }) } // restoreFlash deserializes a Flash cookie struct from a request. func restoreFlash(req *Request) Flash { flash := Flash{ Data: make(map[string]string), Out: make(map[string]string), } if cookie, err := req.Cookie(CookiePrefix + "_FLASH"); err == nil { ParseKeyValueCookie(cookie.GetValue(), func(key, val string) { flash.Data[key] = val }) } return flash } revel-1.0.0/http.go000066400000000000000000000320401370252312000141250ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package revel import ( "bytes" "errors" "fmt" "io" "net/http" "net/url" "sort" "strconv" "strings" "context" "mime/multipart" "path/filepath" ) // Request is Revel's HTTP request object structure type Request struct { In ServerRequest // The server request Header *RevelHeader // The revel header ContentType string // The content type Format string // The output format "html", "xml", "json", or "txt" AcceptLanguages AcceptLanguages // The languages to accept Locale string // THe locale WebSocket ServerWebSocket // The websocket Method string // The method RemoteAddr string // The remote address Host string // The host // URL request path from the server (built) URL *url.URL // The url // DEPRECATED use GetForm() Form url.Values // The Form // DEPRECATED use GetMultipartForm() MultipartForm *MultipartForm // The multipart form controller *Controller // The controller, so some of this data can be fetched } var FORM_NOT_FOUND = errors.New("Form Not Found") var httpLog = RevelLog.New("section", "http") // Response is Revel's HTTP response object structure type Response struct { Status int ContentType string Out OutResponse writer io.Writer } // The output response type OutResponse struct { // internalHeader.Server Set by ServerResponse.Get(HTTP_SERVER_HEADER), saves calling the get every time the header needs to be written to internalHeader *RevelHeader // The internal header Server ServerResponse // The server response response *Response // The response } // The header defined in Revel type RevelHeader struct { Server ServerHeader // The server } // NewResponse wraps ServerResponse inside a Revel's Response and returns it func NewResponse(w ServerResponse) (r *Response) { r = &Response{Out: OutResponse{Server: w, internalHeader: &RevelHeader{}}} r.Out.response = r return r } // NewRequest returns a Revel's HTTP request instance with given HTTP instance func NewRequest(r ServerRequest) *Request { req := &Request{Header: &RevelHeader{}} if r != nil { req.SetRequest(r) } return req } func (req *Request) SetRequest(r ServerRequest) { req.In = r if h, e := req.In.Get(HTTP_SERVER_HEADER); e == nil { req.Header.Server = h.(ServerHeader) } req.URL, _ = req.GetValue(HTTP_URL).(*url.URL) req.ContentType = ResolveContentType(req) req.Format = ResolveFormat(req) req.AcceptLanguages = ResolveAcceptLanguage(req) req.Method, _ = req.GetValue(HTTP_METHOD).(string) req.RemoteAddr, _ = req.GetValue(HTTP_REMOTE_ADDR).(string) req.Host, _ = req.GetValue(HTTP_HOST).(string) } // Returns a cookie func (req *Request) Cookie(key string) (ServerCookie, error) { if req.Header.Server != nil { return req.Header.Server.GetCookie(key) } return nil, http.ErrNoCookie } // Fetch the requested URI func (req *Request) GetRequestURI() string { uri, _ := req.GetValue(HTTP_REQUEST_URI).(string) return uri } // Fetch the query func (req *Request) GetQuery() (v url.Values) { v, _ = req.GetValue(ENGINE_PARAMETERS).(url.Values) return } // Fetch the path func (req *Request) GetPath() (path string) { path, _ = req.GetValue(ENGINE_PATH).(string) return } // Fetch the body func (req *Request) GetBody() (body io.Reader) { body, _ = req.GetValue(HTTP_BODY).(io.Reader) return } // Fetch the context func (req *Request) Context() (c context.Context) { c, _ = req.GetValue(HTTP_REQUEST_CONTEXT).(context.Context) return } // Deprecated use controller.Params.Get() func (req *Request) FormValue(key string) (value string) { return req.controller.Params.Get(key) } // Deprecated use controller.Params.Form[Key] func (req *Request) PostFormValue(key string) (value string) { valueList := req.controller.Params.Form[key] if len(valueList) > 0 { value = valueList[0] } return } // Deprecated use GetForm() instead func (req *Request) ParseForm() (e error) { if req.Form == nil { req.Form, e = req.GetForm() } return } func (req *Request) GetForm() (url.Values, error) { if form, err := req.In.Get(HTTP_FORM); err != nil { return nil, err } else if values, found := form.(url.Values); found { req.Form = values return values, nil } return nil, FORM_NOT_FOUND } // Deprecated for backwards compatibility only type MultipartForm struct { File map[string][]*multipart.FileHeader Value url.Values origin ServerMultipartForm } func (req *Request) MultipartReader() (*multipart.Reader, error) { return nil, errors.New("MultipartReader not supported, use controller.Param") } // Deprecated for backwards compatibility only func newMultipareForm(s ServerMultipartForm) (f *MultipartForm) { return &MultipartForm{File: s.GetFiles(), Value: s.GetValues(), origin: s} } // Deprecated use GetMultipartForm() instead func (req *Request) ParseMultipartForm(_ int64) (e error) { var s ServerMultipartForm if s, e = req.GetMultipartForm(); e == nil { req.MultipartForm = newMultipareForm(s) } return } // Return the args for the controller func (req *Request) Args() map[string]interface{} { return req.controller.Args } // Return a multipart form func (req *Request) GetMultipartForm() (ServerMultipartForm, error) { if form, err := req.In.Get(HTTP_MULTIPART_FORM); err != nil { return nil, err } else if values, found := form.(ServerMultipartForm); found { return values, nil } return nil, FORM_NOT_FOUND } // Destroy the request func (req *Request) Destroy() { req.In = nil req.ContentType = "" req.Format = "" req.AcceptLanguages = nil req.Method = "" req.RemoteAddr = "" req.Host = "" req.Header.Destroy() req.URL = nil req.Form = nil req.MultipartForm = nil } // Set the server response func (resp *Response) SetResponse(r ServerResponse) { resp.Out.Server = r if h, e := r.Get(HTTP_SERVER_HEADER); e == nil { resp.Out.internalHeader.Server, _ = h.(ServerHeader) } } // Destroy the output response func (o *OutResponse) Destroy() { o.response = nil o.internalHeader.Destroy() } // Destroy the RevelHeader func (h *RevelHeader) Destroy() { h.Server = nil } // Destroy the Response func (resp *Response) Destroy() { resp.Out.Destroy() resp.Status = 0 resp.ContentType = "" resp.writer = nil } // UserAgent returns the client's User-Agent header string. func (r *Request) UserAgent() string { return r.Header.Get("User-Agent") } // Referer returns the client's Referer header string. func (req *Request) Referer() string { return req.Header.Get("Referer") } // Return the httpheader for the key func (req *Request) GetHttpHeader(key string) string { return req.Header.Get(key) } // Return the value from the server func (r *Request) GetValue(key int) (value interface{}) { value, _ = r.In.Get(key) return } // WriteHeader writes the header (for now, just the status code). // The status may be set directly by the application (c.Response.Status = 501). // If it isn't, then fall back to the provided status code. func (resp *Response) WriteHeader(defaultStatusCode int, defaultContentType string) { if resp.ContentType == "" { resp.ContentType = defaultContentType } resp.Out.internalHeader.Set("Content-Type", resp.ContentType) if resp.Status == 0 { resp.Status = defaultStatusCode } resp.SetStatus(resp.Status) } func (resp *Response) SetStatus(statusCode int) { if resp.Out.internalHeader.Server != nil { resp.Out.internalHeader.Server.SetStatus(statusCode) } else { resp.Out.Server.Set(ENGINE_RESPONSE_STATUS, statusCode) } } // Return the writer func (resp *Response) GetWriter() (writer io.Writer) { writer = resp.writer if writer == nil { if w, e := resp.Out.Server.Get(ENGINE_WRITER); e == nil { writer, resp.writer = w.(io.Writer), w.(io.Writer) } } return } // Replace the writer func (resp *Response) SetWriter(writer io.Writer) bool { resp.writer = writer // Leave it up to the engine to flush and close the writer return resp.Out.Server.Set(ENGINE_WRITER, writer) } // Passes full control to the response to the caller - terminates any initial writes func (resp *Response) GetStreamWriter() (writer StreamWriter) { if w, e := resp.Out.Server.Get(HTTP_STREAM_WRITER); e == nil { writer = w.(StreamWriter) } return } // Return the header func (o *OutResponse) Header() *RevelHeader { return o.internalHeader } // Write the header out func (o *OutResponse) Write(data []byte) (int, error) { return o.response.GetWriter().Write(data) } // Set a value in the header func (h *RevelHeader) Set(key, value string) { if h.Server != nil { h.Server.Set(key, value) } } // Add a key to the header func (h *RevelHeader) Add(key, value string) { if h.Server != nil { h.Server.Add(key, value) } } // Set a cookie in the header func (h *RevelHeader) SetCookie(cookie string) { if h.Server != nil { h.Server.SetCookie(cookie) } } // Set the status for the header func (h *RevelHeader) SetStatus(status int) { if h.Server != nil { h.Server.SetStatus(status) } } // Get a key from the header func (h *RevelHeader) Get(key string) (value string) { values := h.GetAll(key) if len(values) > 0 { value = values[0] } return } // GetAll returns []string of items (the header split by a comma) func (h *RevelHeader) GetAll(key string) (values []string) { if h.Server != nil { values = h.Server.Get(key) } return } // ResolveContentType gets the content type. // e.g. From "multipart/form-data; boundary=--" to "multipart/form-data" // If none is specified, returns "text/html" by default. func ResolveContentType(req *Request) string { contentType := req.Header.Get("Content-Type") if contentType == "" { return "text/html" } return strings.ToLower(strings.TrimSpace(strings.Split(contentType, ";")[0])) } // ResolveFormat maps the request's Accept MIME type declaration to // a Request.Format attribute, specifically "html", "xml", "json", or "txt", // returning a default of "html" when Accept header cannot be mapped to a // value above. func ResolveFormat(req *Request) string { ext := strings.ToLower(filepath.Ext(req.GetPath())) switch ext { case ".html": return "html" case ".json": return "json" case ".xml": return "xml" case ".txt": return "txt" } accept := req.GetHttpHeader("accept") switch { case accept == "", strings.HasPrefix(accept, "*/*"), // */ strings.Contains(accept, "application/xhtml"), strings.Contains(accept, "text/html"): return "html" case strings.Contains(accept, "application/json"), strings.Contains(accept, "text/javascript"), strings.Contains(accept, "application/javascript"): return "json" case strings.Contains(accept, "application/xml"), strings.Contains(accept, "text/xml"): return "xml" case strings.Contains(accept, "text/plain"): return "txt" } return "html" } // AcceptLanguage is a single language from the Accept-Language HTTP header. type AcceptLanguage struct { Language string Quality float32 } // AcceptLanguages is collection of sortable AcceptLanguage instances. type AcceptLanguages []AcceptLanguage func (al AcceptLanguages) Len() int { return len(al) } func (al AcceptLanguages) Swap(i, j int) { al[i], al[j] = al[j], al[i] } func (al AcceptLanguages) Less(i, j int) bool { return al[i].Quality > al[j].Quality } func (al AcceptLanguages) String() string { output := bytes.NewBufferString("") for i, language := range al { if _, err := output.WriteString(fmt.Sprintf("%s (%1.1f)", language.Language, language.Quality)); err != nil { httpLog.Error("String: WriteString failed:", "error", err) } if i != len(al)-1 { if _, err := output.WriteString(", "); err != nil { httpLog.Error("String: WriteString failed:", "error", err) } } } return output.String() } // ResolveAcceptLanguage returns a sorted list of Accept-Language // header values. // // The results are sorted using the quality defined in the header for each // language range with the most qualified language range as the first // element in the slice. // // See the HTTP header fields specification // (http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4) for more details. func ResolveAcceptLanguage(req *Request) AcceptLanguages { header := req.Header.Get("Accept-Language") if header == "" { return req.AcceptLanguages } acceptLanguageHeaderValues := strings.Split(header, ",") acceptLanguages := make(AcceptLanguages, len(acceptLanguageHeaderValues)) for i, languageRange := range acceptLanguageHeaderValues { if qualifiedRange := strings.Split(languageRange, ";q="); len(qualifiedRange) == 2 { quality, err := strconv.ParseFloat(qualifiedRange[1], 32) if err != nil { httpLog.Warn("Detected malformed Accept-Language header quality in assuming quality is 1", "languageRange", languageRange) acceptLanguages[i] = AcceptLanguage{qualifiedRange[0], 1} } else { acceptLanguages[i] = AcceptLanguage{qualifiedRange[0], float32(quality)} } } else { acceptLanguages[i] = AcceptLanguage{languageRange, 1} } } sort.Sort(acceptLanguages) return acceptLanguages } revel-1.0.0/i18n.go000066400000000000000000000175161370252312000137400ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package revel import ( "fmt" "html/template" "os" "path/filepath" "regexp" "strings" "github.com/revel/config" ) const ( // CurrentLocaleViewArg the key for the current locale view arg value CurrentLocaleViewArg = "currentLocale" messageFilesDirectory = "messages" messageFilePattern = `^\w+\.[a-zA-Z]{2}$` defaultUnknownFormat = "??? %s ???" unknownFormatConfigKey = "i18n.unknown_format" defaultLanguageOption = "i18n.default_language" localeCookieConfigKey = "i18n.cookie" ) var ( // All currently loaded message configs. messages map[string]*config.Config localeParameterName string i18nLog = RevelLog.New("section", "i18n") ) // MessageFunc allows you to override the translation interface. // // Set this to your own function that translates to the current locale. // This allows you to set up your own loading and logging of translated texts. // // See Message(...) in i18n.go for example of function. var MessageFunc = Message // MessageLanguages returns all currently loaded message languages. func MessageLanguages() []string { languages := make([]string, len(messages)) i := 0 for language := range messages { languages[i] = language i++ } return languages } // Message performs a message look-up for the given locale and message using the given arguments. // // When either an unknown locale or message is detected, a specially formatted string is returned. func Message(locale, message string, args ...interface{}) string { language, region := parseLocale(locale) unknownValueFormat := getUnknownValueFormat() messageConfig, knownLanguage := messages[language] if !knownLanguage { i18nLog.Debugf("Unsupported language for locale '%s' and message '%s', trying default language", locale, message) if defaultLanguage, found := Config.String(defaultLanguageOption); found { i18nLog.Debugf("Using default language '%s'", defaultLanguage) messageConfig, knownLanguage = messages[defaultLanguage] if !knownLanguage { i18nLog.Debugf("Unsupported default language for locale '%s' and message '%s'", defaultLanguage, message) return fmt.Sprintf(unknownValueFormat, message) } } else { i18nLog.Warnf("Unable to find default language option (%s); messages for unsupported locales will never be translated", defaultLanguageOption) return fmt.Sprintf(unknownValueFormat, message) } } // This works because unlike the goconfig documentation suggests it will actually // try to resolve message in DEFAULT if it did not find it in the given section. value, err := messageConfig.String(region, message) if err != nil { i18nLog.Warnf("Unknown message '%s' for locale '%s'", message, locale) return fmt.Sprintf(unknownValueFormat, message) } if len(args) > 0 { i18nLog.Debugf("Arguments detected, formatting '%s' with %v", value, args) safeArgs := make([]interface{}, 0, len(args)) for _, arg := range args { switch a := arg.(type) { case template.HTML: safeArgs = append(safeArgs, a) case string: safeArgs = append(safeArgs, template.HTML(template.HTMLEscapeString(a))) default: safeArgs = append(safeArgs, a) } } value = fmt.Sprintf(value, safeArgs...) } return value } func parseLocale(locale string) (language, region string) { if strings.Contains(locale, "-") { languageAndRegion := strings.Split(locale, "-") return languageAndRegion[0], languageAndRegion[1] } return locale, "" } // Retrieve message format or default format when i18n message is missing. func getUnknownValueFormat() string { return Config.StringDefault(unknownFormatConfigKey, defaultUnknownFormat) } // Recursively read and cache all available messages from all message files on the given path. func loadMessages(path string) { messages = make(map[string]*config.Config) // Read in messages from the modules. Load the module messges first, // so that it can be override in parent application for _, module := range Modules { i18nLog.Debug("Importing messages from module:", "importpath", module.ImportPath) if err := Walk(filepath.Join(module.Path, messageFilesDirectory), loadMessageFile); err != nil && !os.IsNotExist(err) { i18nLog.Error("Error reading messages files from module:", "error", err) } } if err := Walk(path, loadMessageFile); err != nil && !os.IsNotExist(err) { i18nLog.Error("Error reading messages files:", "error", err) } } // Load a single message file func loadMessageFile(path string, info os.FileInfo, osError error) error { if osError != nil { return osError } if info.IsDir() { return nil } if matched, _ := regexp.MatchString(messageFilePattern, info.Name()); matched { messageConfig, err := parseMessagesFile(path) if err != nil { return err } locale := parseLocaleFromFileName(info.Name()) // If we have already parsed a message file for this locale, merge both if _, exists := messages[locale]; exists { messages[locale].Merge(messageConfig) i18nLog.Debugf("Successfully merged messages for locale '%s'", locale) } else { messages[locale] = messageConfig } i18nLog.Debug("Successfully loaded messages from file", "file", info.Name()) } else { i18nLog.Warn("Ignoring file because it did not have a valid extension", "file", info.Name()) } return nil } func parseMessagesFile(path string) (messageConfig *config.Config, err error) { messageConfig, err = config.ReadDefault(path) return } func parseLocaleFromFileName(file string) string { extension := filepath.Ext(file)[1:] return strings.ToLower(extension) } func init() { OnAppStart(func() { loadMessages(filepath.Join(BasePath, messageFilesDirectory)) localeParameterName = Config.StringDefault("i18n.locale.parameter", "") }, 0) } func I18nFilter(c *Controller, fc []Filter) { foundLocale := false // Search for a parameter first if localeParameterName != "" { if locale, found := c.Params.Values[localeParameterName]; found && len(locale[0]) > 0 { setCurrentLocaleControllerArguments(c, locale[0]) foundLocale = true i18nLog.Debug("Found locale parameter value: ", "locale", locale[0]) } } if !foundLocale { if foundCookie, cookieValue := hasLocaleCookie(c.Request); foundCookie { i18nLog.Debug("Found locale cookie value: ", "cookie", cookieValue) setCurrentLocaleControllerArguments(c, cookieValue) } else if foundHeader, headerValue := hasAcceptLanguageHeader(c.Request); foundHeader { i18nLog.Debug("Found Accept-Language header value: ", "header", headerValue) setCurrentLocaleControllerArguments(c, headerValue) } else { i18nLog.Debug("Unable to find locale in cookie or header, using empty string") setCurrentLocaleControllerArguments(c, "") } } fc[0](c, fc[1:]) } // Set the current locale controller argument (CurrentLocaleControllerArg) with the given locale. func setCurrentLocaleControllerArguments(c *Controller, locale string) { c.Request.Locale = locale c.ViewArgs[CurrentLocaleViewArg] = locale } // Determine whether the given request has valid Accept-Language value. // // Assumes that the accept languages stored in the request are sorted according to quality, with top // quality first in the slice. func hasAcceptLanguageHeader(request *Request) (bool, string) { if request.AcceptLanguages != nil && len(request.AcceptLanguages) > 0 { return true, request.AcceptLanguages[0].Language } return false, "" } // Determine whether the given request has a valid language cookie value. func hasLocaleCookie(request *Request) (bool, string) { if request != nil { name := Config.StringDefault(localeCookieConfigKey, CookiePrefix+"_LANG") cookie, err := request.Cookie(name) if err == nil { return true, cookie.GetValue() } i18nLog.Debug("Unable to read locale cookie ", "name", name, "error", err) } return false, "" } revel-1.0.0/i18n_test.go000066400000000000000000000232321370252312000147670ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package revel import ( "html/template" "net/http" "testing" "time" "github.com/revel/config" "github.com/revel/revel/logger" ) const ( testDataPath string = "testdata/i18n" testConfigPath string = "testdata/i18n/config" testConfigName string = "test_app.conf" ) func TestI18nLoadMessages(t *testing.T) { loadMessages(testDataPath) // Assert that we have the expected number of languages if len(MessageLanguages()) != 2 { t.Fatalf("Expected messages to contain no more or less than 2 languages, instead there are %d languages", len(MessageLanguages())) } } func TestI18nMessage(t *testing.T) { loadMessages(testDataPath) loadTestI18nConfig(t) // Assert that we can get a message and we get the expected return value if message := Message("nl", "greeting"); message != "Hallo" { t.Errorf("Message 'greeting' for locale 'nl' (%s) does not have the expected value", message) } if message := Message("en", "greeting"); message != "Hello" { t.Errorf("Message 'greeting' for locale 'en' (%s) does not have the expected value", message) } if message := Message("en", "folded"); message != "Greeting is 'Hello'" { t.Error("Unexpected unfolded message: ", message) } if message := Message("en", "arguments.string", "Vincent Hanna"); message != "My name is Vincent Hanna" { t.Errorf("Message 'arguments.string' for locale 'en' (%s) does not have the expected value", message) } if message := Message("en", "arguments.hex", 1234567, 1234567); message != "The number 1234567 in hexadecimal notation would be 12d687" { t.Errorf("Message 'arguments.hex' for locale 'en' (%s) does not have the expected value", message) } if message := Message("en", "folded.arguments", 12345); message != "Rob is 12345 years old" { t.Errorf("Message 'folded.arguments' for locale 'en' (%s) does not have the expected value", message) } if message := Message("en-AU", "greeting"); message != "G'day" { t.Errorf("Message 'greeting' for locale 'en-AU' (%s) does not have the expected value", message) } if message := Message("en-AU", "only_exists_in_default"); message != "Default" { t.Errorf("Message 'only_exists_in_default' for locale 'en-AU' (%s) does not have the expected value", message) } // Assert that message merging works if message := Message("en", "greeting2"); message != "Yo!" { t.Errorf("Message 'greeting2' for locale 'en' (%s) does not have the expected value", message) } // Assert that we get the expected return value for a locale that doesn't exist if message := Message("unknown locale", "message"); message != "??? message ???" { t.Error("Locale 'unknown locale' is not supposed to exist") } // Assert that we get the expected return value for a message that doesn't exist if message := Message("nl", "unknown message"); message != "??? unknown message ???" { t.Error("Message 'unknown message' is not supposed to exist") } // XSS if message := Message("en", "arguments.string", ""); message != "My name is <img src=a onerror=alert(1) />" { t.Error("XSS protection for messages is broken:", message) } // Avoid escaping HTML if message := Message("en", "arguments.string", template.HTML("")); message != "My name is " { t.Error("Passing safe HTML to message is broken:", message) } } func TestI18nMessageWithDefaultLocale(t *testing.T) { loadMessages(testDataPath) loadTestI18nConfig(t) if message := Message("doesn't exist", "greeting"); message != "Hello" { t.Errorf("Expected message '%s' for unknown locale to be default '%s' but was '%s'", "greeting", "Hello", message) } if message := Message("doesn't exist", "unknown message"); message != "??? unknown message ???" { t.Error("Message 'unknown message' is not supposed to exist in the default language") } } func TestHasLocaleCookie(t *testing.T) { loadTestI18nConfig(t) if found, value := hasLocaleCookie(buildRequestWithCookie("APP_LANG", "en").Request); !found { t.Errorf("Expected %s cookie with value '%s' but found nothing or unexpected value '%s'", "APP_LANG", "en", value) } if found, value := hasLocaleCookie(buildRequestWithCookie("APP_LANG", "en-US").Request); !found { t.Errorf("Expected %s cookie with value '%s' but found nothing or unexpected value '%s'", "APP_LANG", "en-US", value) } if found, _ := hasLocaleCookie(buildRequestWithCookie("DOESNT_EXIST", "en-US").Request); found { t.Errorf("Expected %s cookie to not exist, but apparently it does", "DOESNT_EXIST") } } func TestHasLocaleCookieWithInvalidConfig(t *testing.T) { loadTestI18nConfigWithoutLanguageCookieOption(t) if found, _ := hasLocaleCookie(buildRequestWithCookie("APP_LANG", "en-US").Request); found { t.Errorf("Expected %s cookie to not exist because the configured name is missing", "APP_LANG") } if found, _ := hasLocaleCookie(buildRequestWithCookie("REVEL_LANG", "en-US").Request); !found { t.Errorf("Expected %s cookie to exist", "REVEL_LANG") } } func TestHasAcceptLanguageHeader(t *testing.T) { if found, value := hasAcceptLanguageHeader(buildRequestWithAcceptLanguages("en-US").Request); !found && value != "en-US" { t.Errorf("Expected to find Accept-Language header with value '%s', found '%s' instead", "en-US", value) } if found, value := hasAcceptLanguageHeader(buildRequestWithAcceptLanguages("en-GB", "en-US", "nl").Request); !found && value != "en-GB" { t.Errorf("Expected to find Accept-Language header with value '%s', found '%s' instead", "en-GB", value) } } func TestBeforeRequest(t *testing.T) { loadTestI18nConfig(t) c := buildEmptyRequest() if I18nFilter(c, NilChain); c.Request.Locale != "" { t.Errorf("Expected to find current language '%s' in controller, found '%s' instead", "", c.Request.Locale) } c = buildRequestWithCookie("APP_LANG", "en-US") if I18nFilter(c, NilChain); c.Request.Locale != "en-US" { t.Errorf("Expected to find current language '%s' in controller, found '%s' instead", "en-US", c.Request.Locale) } c = buildRequestWithAcceptLanguages("en-GB", "en-US") if I18nFilter(c, NilChain); c.Request.Locale != "en-GB" { t.Errorf("Expected to find current language '%s' in controller, found '%s' instead", "en-GB", c.Request.Locale) } } func TestI18nMessageUnknownValueFormat(t *testing.T) { loadMessages(testDataPath) loadTestI18nConfigWithUnknowFormatOption(t) // Assert that we can get a message and we get the expected return value if message := Message("en", "greeting"); message != "Hello" { t.Errorf("Message 'greeting' for locale 'en' (%s) does not have the expected value", message) } // Assert that we get the expected return value with original format if message := Message("unknown locale", "message"); message != "*** message ***" { t.Error("Locale 'unknown locale' is not supposed to exist") } if message := Message("nl", "unknown message"); message != "*** unknown message ***" { t.Error("Message 'unknown message' is not supposed to exist") } } func BenchmarkI18nLoadMessages(b *testing.B) { excludeFromTimer(b, func() { RevelLog.SetHandler(logger.FuncHandler(func(r *logger.Record) error { return nil })) }) for i := 0; i < b.N; i++ { loadMessages(testDataPath) } } func BenchmarkI18nMessage(b *testing.B) { for i := 0; i < b.N; i++ { Message("nl", "greeting") } } func BenchmarkI18nMessageWithArguments(b *testing.B) { excludeFromTimer(b, func() { RevelLog.SetHandler(logger.FuncHandler(func(r *logger.Record) error { return nil })) }) for i := 0; i < b.N; i++ { Message("en", "arguments.string", "Vincent Hanna") } } func BenchmarkI18nMessageWithFoldingAndArguments(b *testing.B) { excludeFromTimer(b, func() { RevelLog.SetHandler(logger.FuncHandler(func(r *logger.Record) error { return nil })) }) for i := 0; i < b.N; i++ { Message("en", "folded.arguments", 12345) } } // Exclude whatever operations the given function performs from the benchmark timer. func excludeFromTimer(b *testing.B, f func()) { b.StopTimer() f() b.StartTimer() } func loadTestI18nConfig(t *testing.T) { ConfPaths = append(ConfPaths, testConfigPath) testConfig, err := config.LoadContext(testConfigName, ConfPaths) if err != nil { t.Fatalf("Unable to load test config '%s': %s", testConfigName, err.Error()) } Config = testConfig CookiePrefix = Config.StringDefault("cookie.prefix", "REVEL") } func loadTestI18nConfigWithoutLanguageCookieOption(t *testing.T) { loadTestI18nConfig(t) Config.Raw().RemoveOption("DEFAULT", "i18n.cookie") } func loadTestI18nConfigWithUnknowFormatOption(t *testing.T) { loadTestI18nConfig(t) Config.Raw().AddOption("DEFAULT", "i18n.unknown_format", "*** %s ***") } func buildRequestWithCookie(name, value string) *Controller { httpRequest, _ := http.NewRequest("GET", "/", nil) controller := NewTestController(nil, httpRequest) httpRequest.AddCookie(&http.Cookie{ Name: name, Value: value, Path: "", Domain: "", Expires: time.Now(), RawExpires: "", MaxAge: 0, Secure: false, HttpOnly: false, Raw: "", Unparsed: nil, }) return controller } func buildRequestWithAcceptLanguages(acceptLanguages ...string) *Controller { httpRequest, _ := http.NewRequest("GET", "/", nil) controller := NewTestController(nil, httpRequest) request := controller.Request for _, acceptLanguage := range acceptLanguages { request.AcceptLanguages = append(request.AcceptLanguages, AcceptLanguage{acceptLanguage, 1}) } return controller } func buildEmptyRequest() *Controller { httpRequest, _ := http.NewRequest("GET", "/", nil) controller := NewTestController(nil, httpRequest) return controller } revel-1.0.0/intercept.go000066400000000000000000000155611370252312000151540ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package revel import ( "log" "reflect" "sort" ) // An InterceptorFunc is functionality invoked by the framework BEFORE or AFTER // an action. // // An interceptor may optionally return a Result (instead of nil). Depending on // when the interceptor was invoked, the response is different: // 1. BEFORE: No further interceptors are invoked, and neither is the action. // 2. AFTER: Further interceptors are still run. // In all cases, any returned Result will take the place of any existing Result. // // In the BEFORE case, that returned Result is guaranteed to be final, while // in the AFTER case it is possible that a further interceptor could emit its // own Result. // // Interceptors are called in the order that they are added. // // *** // // Two types of interceptors are provided: Funcs and Methods // // Func Interceptors may apply to any / all Controllers. // // func example(*revel.Controller) revel.Result // // Method Interceptors are provided so that properties can be set on application // controllers. // // func (c AppController) example() revel.Result // func (c *AppController) example() revel.Result // type InterceptorFunc func(*Controller) Result type InterceptorMethod interface{} type When int const ( BEFORE When = iota AFTER PANIC FINALLY ) type InterceptTarget int const ( AllControllers InterceptTarget = iota ) type Interception struct { When When function InterceptorFunc method InterceptorMethod callable reflect.Value target reflect.Type interceptAll bool } // Invoke performs the given interception. // val is a pointer to the App Controller. func (i Interception) Invoke(val reflect.Value, target *reflect.Value) reflect.Value { var arg reflect.Value if i.function == nil { // If it's an InterceptorMethod, then we have to pass in the target type. arg = *target } else { // If it's an InterceptorFunc, then the type must be *Controller. // We can find that by following the embedded types up the chain. for val.Type() != controllerPtrType { if val.Kind() == reflect.Ptr { val = val.Elem() } val = val.Field(0) } arg = val } vals := i.callable.Call([]reflect.Value{arg}) return vals[0] } func InterceptorFilter(c *Controller, fc []Filter) { defer invokeInterceptors(FINALLY, c) defer func() { if err := recover(); err != nil { invokeInterceptors(PANIC, c) panic(err) } }() // Invoke the BEFORE interceptors and return early, if we get a result. invokeInterceptors(BEFORE, c) if c.Result != nil { return } fc[0](c, fc[1:]) invokeInterceptors(AFTER, c) } func invokeInterceptors(when When, c *Controller) { var ( app = reflect.ValueOf(c.AppController) result Result ) for _, intc := range getInterceptors(when, app) { resultValue := intc.Interceptor.Invoke(app, &intc.Target) if !resultValue.IsNil() { result = resultValue.Interface().(Result) } if when == BEFORE && result != nil { c.Result = result return } } if result != nil { c.Result = result } } var interceptors []*Interception // InterceptFunc installs a general interceptor. // This can be applied to any Controller. // It must have the signature of: // func example(c *revel.Controller) revel.Result func InterceptFunc(intc InterceptorFunc, when When, target interface{}) { interceptors = append(interceptors, &Interception{ When: when, function: intc, callable: reflect.ValueOf(intc), target: reflect.TypeOf(target), interceptAll: target == AllControllers, }) } // InterceptMethod installs an interceptor method that applies to its own Controller. // func (c AppController) example() revel.Result // func (c *AppController) example() revel.Result func InterceptMethod(intc InterceptorMethod, when When) { methodType := reflect.TypeOf(intc) if methodType.Kind() != reflect.Func || methodType.NumOut() != 1 || methodType.NumIn() != 1 { log.Fatalln("Interceptor method should have signature like", "'func (c *AppController) example() revel.Result' but was", methodType) } interceptors = append(interceptors, &Interception{ When: when, method: intc, callable: reflect.ValueOf(intc), target: methodType.In(0), }) } // This item is used to provide a sortable set to be returned to the caller. This ensures calls order is maintained // type interceptorItem struct { Interceptor *Interception Target reflect.Value Level int } type interceptorItemList []*interceptorItem func (a interceptorItemList) Len() int { return len(a) } func (a interceptorItemList) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a interceptorItemList) Less(i, j int) bool { return a[i].Level < a[j].Level } type reverseInterceptorItemList []*interceptorItem func (a reverseInterceptorItemList) Len() int { return len(a) } func (a reverseInterceptorItemList) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a reverseInterceptorItemList) Less(i, j int) bool { return a[i].Level > a[j].Level } func getInterceptors(when When, val reflect.Value) interceptorItemList { result := interceptorItemList{} for _, intc := range interceptors { if intc.When != when { continue } level, target := findTarget(val, intc.target) if intc.interceptAll || target.IsValid() { result = append(result, &interceptorItem{intc, target, level}) } } // Before is deepest to highest if when == BEFORE { sort.Sort(result) } else { // Everything else is highest to deepest sort.Sort(reverseInterceptorItemList(result)) } return result } // Find the value of the target, starting from val and including embedded types. // Also, convert between any difference in indirection. // If the target couldn't be found, the returned Value will have IsValid() == false func findTarget(val reflect.Value, target reflect.Type) (int, reflect.Value) { // Look through the embedded types (until we reach the *revel.Controller at the top). valueQueue := []reflect.Value{val} level := 0 for len(valueQueue) > 0 { val, valueQueue = valueQueue[0], valueQueue[1:] // Check if val is of a similar type to the target type. if val.Type() == target { return level, val } if val.Kind() == reflect.Ptr && val.Elem().Type() == target { return level, val.Elem() } if target.Kind() == reflect.Ptr && target.Elem() == val.Type() { return level, val.Addr() } // If we reached the *revel.Controller and still didn't find what we were // looking for, give up. if val.Type() == controllerPtrType { continue } // Else, add each anonymous field to the queue. if val.Kind() == reflect.Ptr { val = val.Elem() } for i := 0; i < val.NumField(); i++ { if val.Type().Field(i).Anonymous { valueQueue = append(valueQueue, val.Field(i)) } } level-- } return level, reflect.Value{} } revel-1.0.0/intercept_test.go000066400000000000000000000053771370252312000162170ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package revel import ( "reflect" "testing" ) var funcP = func(c *Controller) Result { return nil } var funcP2 = func(c *Controller) Result { return nil } type InterceptController struct{ *Controller } type InterceptControllerN struct{ InterceptController } type InterceptControllerP struct{ *InterceptController } type InterceptControllerNP struct { *Controller InterceptControllerN InterceptControllerP } func (c InterceptController) methN() Result { return nil } func (c *InterceptController) methP() Result { return nil } func (c InterceptControllerN) methNN() Result { return nil } func (c *InterceptControllerN) methNP() Result { return nil } func (c InterceptControllerP) methPN() Result { return nil } func (c *InterceptControllerP) methPP() Result { return nil } // Methods accessible from InterceptControllerN var MethodN = []interface{}{ InterceptController.methN, (*InterceptController).methP, InterceptControllerN.methNN, (*InterceptControllerN).methNP, } // Methods accessible from InterceptControllerP var MethodP = []interface{}{ InterceptController.methN, (*InterceptController).methP, InterceptControllerP.methPN, (*InterceptControllerP).methPP, } // This checks that all the various kinds of interceptor functions/methods are // properly invoked. func TestInvokeArgType(t *testing.T) { n := InterceptControllerN{InterceptController{&Controller{}}} p := InterceptControllerP{&InterceptController{&Controller{}}} np := InterceptControllerNP{&Controller{}, n, p} testInterceptorController(t, reflect.ValueOf(&n), MethodN) testInterceptorController(t, reflect.ValueOf(&p), MethodP) testInterceptorController(t, reflect.ValueOf(&np), MethodN) testInterceptorController(t, reflect.ValueOf(&np), MethodP) } func testInterceptorController(t *testing.T, appControllerPtr reflect.Value, methods []interface{}) { interceptors = []*Interception{} InterceptFunc(funcP, BEFORE, appControllerPtr.Elem().Interface()) InterceptFunc(funcP2, BEFORE, AllControllers) for _, m := range methods { InterceptMethod(m, BEFORE) } ints := getInterceptors(BEFORE, appControllerPtr) if len(ints) != 6 { t.Fatalf("N: Expected 6 interceptors, got %d.", len(ints)) } testInterception(t, ints[0], reflect.ValueOf(&Controller{})) testInterception(t, ints[1], reflect.ValueOf(&Controller{})) for i := range methods { testInterception(t, ints[i+2], appControllerPtr) } } func testInterception(t *testing.T, intc *interceptorItem, arg reflect.Value) { val := intc.Interceptor.Invoke(arg, &intc.Target) if !val.IsNil() { t.Errorf("Failed (%v): Expected nil got %v", intc, val) } } revel-1.0.0/invoker.go000066400000000000000000000026741370252312000146350ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package revel import ( "io" "reflect" ) var ( controllerPtrType = reflect.TypeOf(&Controller{}) websocketType = reflect.TypeOf((*ServerWebSocket)(nil)).Elem() ) func ActionInvoker(c *Controller, _ []Filter) { // Instantiate the method. methodValue := reflect.ValueOf(c.AppController).MethodByName(c.MethodType.Name) // Collect the values for the method's arguments. var methodArgs []reflect.Value for _, arg := range c.MethodType.Args { // If they accept a websocket connection, treat that arg specially. var boundArg reflect.Value if arg.Type.Implements(websocketType) { boundArg = reflect.ValueOf(c.Request.WebSocket) } else { boundArg = Bind(c.Params, arg.Name, arg.Type) // #756 - If the argument is a closer, defer a Close call, // so we don't risk on leaks. if closer, ok := boundArg.Interface().(io.Closer); ok { defer func() { _ = closer.Close() }() } } methodArgs = append(methodArgs, boundArg) } var resultValue reflect.Value if methodValue.Type().IsVariadic() { resultValue = methodValue.CallSlice(methodArgs)[0] } else { resultValue = methodValue.Call(methodArgs)[0] } if resultValue.Kind() == reflect.Interface && !resultValue.IsNil() { c.Result = resultValue.Interface().(Result) } } revel-1.0.0/invoker_test.go000066400000000000000000000065641370252312000156760ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package revel import ( "net/url" "reflect" "testing" ) // These tests verify that Controllers are initialized properly, given the range // of embedding possibilities.. type P struct{ *Controller } type PN struct{ P } type PNN struct{ PN } // Embedded via two paths type P2 struct{ *Controller } type PP2 struct { *Controller // Need to embed this explicitly to avoid duplicate selector. P P2 PNN } func TestFindControllers(t *testing.T) { controllers = make(map[string]*ControllerType) RegisterController((*P)(nil), nil) RegisterController((*PN)(nil), nil) RegisterController((*PNN)(nil), nil) RegisterController((*PP2)(nil), nil) // Test construction of indexes to each *Controller checkSearchResults(t, P{}, [][]int{{0}}) checkSearchResults(t, PN{}, [][]int{{0, 0}}) checkSearchResults(t, PNN{}, [][]int{{0, 0, 0}}) checkSearchResults(t, PP2{}, [][]int{{0}, {1, 0}, {2, 0}, {3, 0, 0, 0}}) } func checkSearchResults(t *testing.T, obj interface{}, expected [][]int) { actual := findControllers(reflect.TypeOf(obj)) if !reflect.DeepEqual(expected, actual) { t.Errorf("Indexes do not match. expected %v actual %v", expected, actual) } } func TestSetAction(t *testing.T) { controllers = make(map[string]*ControllerType) RegisterController((*P)(nil), []*MethodType{{Name: "Method"}}) RegisterController((*PNN)(nil), []*MethodType{{Name: "Method"}}) RegisterController((*PP2)(nil), []*MethodType{{Name: "Method"}}) // Test that all *revel.Controllers are initialized. c := &Controller{Name: "Test"} if err := c.SetAction("P", "Method"); err != nil { t.Error(err) } else if c.AppController.(*P).Controller != c { t.Errorf("P not initialized") } if err := c.SetAction("PNN", "Method"); err != nil { t.Error(err) } else if c.AppController.(*PNN).Controller != c { t.Errorf("PNN not initialized") } // PP2 has 4 different slots for *Controller. if err := c.SetAction("PP2", "Method"); err != nil { t.Error(err) } else if pp2 := c.AppController.(*PP2); pp2.Controller != c || pp2.P.Controller != c || pp2.P2.Controller != c || pp2.PNN.Controller != c { t.Errorf("PP2 not initialized") } } func BenchmarkSetAction(b *testing.B) { type Mixin1 struct { *Controller x, y int foo string } type Mixin2 struct { *Controller a, b float64 bar string } type Benchmark struct { *Controller Mixin1 Mixin2 user interface{} guy string } RegisterController((*Mixin1)(nil), []*MethodType{{Name: "Method"}}) RegisterController((*Mixin2)(nil), []*MethodType{{Name: "Method"}}) RegisterController((*Benchmark)(nil), []*MethodType{{Name: "Method"}}) c := Controller{ ViewArgs: make(map[string]interface{}), } for i := 0; i < b.N; i++ { if err := c.SetAction("Benchmark", "Method"); err != nil { b.Errorf("Failed to set action: %s", err) return } } } func BenchmarkInvoker(b *testing.B) { startFakeBookingApp() c := NewTestController(nil, showRequest) c.ViewArgs = make(map[string]interface{}) if err := c.SetAction("Hotels", "Show"); err != nil { b.Errorf("Failed to set action: %s", err) return } c.Params = &Params{Values: make(url.Values)} c.Params.Set("id", "3") b.ResetTimer() for i := 0; i < b.N; i++ { ActionInvoker(c, nil) } } revel-1.0.0/libs.go000066400000000000000000000045001370252312000140770ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package revel import ( "crypto/hmac" "crypto/sha1" "encoding/hex" "io" "reflect" "strings" ) // Sign a given string with the app-configured secret key. // If no secret key is set, returns the empty string. // Return the signature in base64 (URLEncoding). func Sign(message string) string { if len(secretKey) == 0 { return "" } mac := hmac.New(sha1.New, secretKey) if _, err := io.WriteString(mac, message); err != nil { utilLog.Error("WriteString failed", "error", err) return "" } return hex.EncodeToString(mac.Sum(nil)) } // Verify returns true if the given signature is correct for the given message. // e.g. it matches what we generate with Sign() func Verify(message, sig string) bool { return hmac.Equal([]byte(sig), []byte(Sign(message))) } // ToBool method converts/assert value into true or false. Default is true. // When converting to boolean, the following values are considered FALSE: // - The integer value is 0 (zero) // - The float value 0.0 (zero) // - The complex value 0.0 (zero) // - For string value, please refer `revel.Atob` method // - An array, map, slice with zero elements // - Boolean value returned as-is // - "nil" value func ToBool(val interface{}) bool { if val == nil { return false } v := reflect.ValueOf(val) switch v.Kind() { case reflect.Bool: return v.Bool() case reflect.String: return Atob(v.String()) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return v.Int() != 0 case reflect.Float32, reflect.Float64: return v.Float() != 0.0 case reflect.Complex64, reflect.Complex128: return v.Complex() != 0.0 case reflect.Array, reflect.Map, reflect.Slice: return v.Len() != 0 } // Return true by default return true } // Atob converts string into boolean. It is in-case sensitive // When converting to boolean, the following values are considered FALSE: // - The "" (empty) string // - The "false" string // - The "f" string // - The "off" string, // - The string "0" & "0.0" func Atob(v string) bool { switch strings.TrimSpace(strings.ToLower(v)) { case "", "false", "off", "f", "0", "0.0": return false } // Return true by default return true } revel-1.0.0/libs_test.go000066400000000000000000000015341370252312000151420ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package revel import "testing" func TestToBooleanForFalse(t *testing.T) { if ToBool(nil) || ToBool([]string{}) || ToBool(map[string]string{}) || ToBool(0) || ToBool(0.0) || ToBool("") || ToBool("false") || ToBool("0") || ToBool("0.0") || ToBool("off") || ToBool("f") { t.Error("Expected 'false' got 'true'") } } func TestToBooleanForTrue(t *testing.T) { if !ToBool([]string{"true"}) || !ToBool(map[string]string{"true": "value"}) || !ToBool(1) || !ToBool(0.1) || !ToBool("not empty") || !ToBool("true") || !ToBool("1") || !ToBool("1.0") || !ToBool("on") || !ToBool("t") { t.Error("Expected 'true' got 'false'") } } revel-1.0.0/logger.go000066400000000000000000000041001370252312000144210ustar00rootroot00000000000000package revel import ( "github.com/revel/revel/logger" ) //Logger var ( // The root log is what all other logs are branched from, meaning if you set the handler for the root // it will adjust all children RootLog = logger.New() // This logger is the application logger, use this for your application log messages - ie jobs and startup, // Use Controller.Log for Controller logging // The requests are logged to this logger with the context of `section:requestlog` AppLog = RootLog.New("module", "app") // This is the logger revel writes to, added log messages will have a context of module:revel in them // It is based off of `RootLog` RevelLog = RootLog.New("module", "revel") // This is the handler for the AppLog, it is stored so that if the AppLog is changed it can be assigned to the // new AppLog appLogHandler *logger.CompositeMultiHandler // This oldLog is the revel logger, historical for revel, The application should use the AppLog or the Controller.oldLog // DEPRECATED oldLog = AppLog.New("section", "deprecated") // System logger SysLog = AppLog.New("section", "system") ) // Initialize the loggers first func init() { //RootLog.SetHandler( // logger.LevelHandler(logger.LogLevel(log15.LvlDebug), // logger.StreamHandler(os.Stdout, logger.TerminalFormatHandler(false, true)))) initLoggers() OnAppStart(initLoggers, -5) } func initLoggers() { appHandle := logger.InitializeFromConfig(BasePath, Config) // Set all the log handlers setAppLog(AppLog, appHandle) } // Set the application log and handler, if handler is nil it will // use the same handler used to configure the application log before func setAppLog(appLog logger.MultiLogger, appHandler *logger.CompositeMultiHandler) { if appLog != nil { AppLog = appLog } if appHandler != nil { appLogHandler = appHandler // Set the app log and the handler for all forked loggers RootLog.SetHandler(appLogHandler) // Set the system log handler - this sets golang writer stream to the // sysLog router logger.SetDefaultLog(SysLog) SysLog.SetStackDepth(5) SysLog.SetHandler(appLogHandler) } } revel-1.0.0/logger/000077500000000000000000000000001370252312000140775ustar00rootroot00000000000000revel-1.0.0/logger/composite_multihandler.go000066400000000000000000000106761370252312000212120ustar00rootroot00000000000000package logger import ( "github.com/mattn/go-colorable" "gopkg.in/natefinch/lumberjack.v2" "io" "os" ) type CompositeMultiHandler struct { DebugHandler LogHandler InfoHandler LogHandler WarnHandler LogHandler ErrorHandler LogHandler CriticalHandler LogHandler } func NewCompositeMultiHandler() (*CompositeMultiHandler, LogHandler) { cw := &CompositeMultiHandler{} return cw, cw } func (h *CompositeMultiHandler) Log(r *Record) (err error) { var handler LogHandler switch r.Level { case LvlInfo: handler = h.InfoHandler case LvlDebug: handler = h.DebugHandler case LvlWarn: handler = h.WarnHandler case LvlError: handler = h.ErrorHandler case LvlCrit: handler = h.CriticalHandler } // Embed the caller function in the context if handler != nil { handler.Log(r) } return } func (h *CompositeMultiHandler) SetHandler(handler LogHandler, replace bool, level LogLevel) { if handler == nil { // Ignore empty handler return } source := &h.DebugHandler switch level { case LvlDebug: source = &h.DebugHandler case LvlInfo: source = &h.InfoHandler case LvlWarn: source = &h.WarnHandler case LvlError: source = &h.ErrorHandler case LvlCrit: source = &h.CriticalHandler } if !replace && *source != nil { // If we are not replacing the source make sure that the level handler is applied first if _, isLevel := (*source).(*LevelFilterHandler); !isLevel { *source = LevelHandler(level, *source) } // If this already was a list add a new logger to it if ll, found := (*source).(*ListLogHandler); found { ll.Add(handler) } else { *source = NewListLogHandler(*source, handler) } } else { *source = handler } } // For the multi handler set the handler, using the LogOptions defined func (h *CompositeMultiHandler) SetHandlers(handler LogHandler, options *LogOptions) { if len(options.Levels) == 0 { options.Levels = LvlAllList } // Set all levels for _, lvl := range options.Levels { h.SetHandler(handler, options.ReplaceExistingHandler, lvl) } } func (h *CompositeMultiHandler) SetJson(writer io.Writer, options *LogOptions) { handler := CallerFileHandler(StreamHandler(writer, JsonFormatEx( options.GetBoolDefault("pretty", false), options.GetBoolDefault("lineSeparated", true), ))) if options.HandlerWrap != nil { handler = options.HandlerWrap.SetChild(handler) } h.SetHandlers(handler, options) } // Use built in rolling function func (h *CompositeMultiHandler) SetJsonFile(filePath string, options *LogOptions) { writer := &lumberjack.Logger{ Filename: filePath, MaxSize: options.GetIntDefault("maxSizeMB", 1024), // megabytes MaxAge: options.GetIntDefault("maxAgeDays", 7), //days MaxBackups: options.GetIntDefault("maxBackups", 7), Compress: options.GetBoolDefault("compress", true), } h.SetJson(writer, options) } func (h *CompositeMultiHandler) SetTerminal(writer io.Writer, options *LogOptions) { streamHandler := StreamHandler( writer, TerminalFormatHandler( options.GetBoolDefault("noColor", false), options.GetBoolDefault("smallDate", true))) if os.Stdout == writer { streamHandler = StreamHandler( colorable.NewColorableStdout(), TerminalFormatHandler( options.GetBoolDefault("noColor", false), options.GetBoolDefault("smallDate", true))) } else if os.Stderr == writer { streamHandler = StreamHandler( colorable.NewColorableStderr(), TerminalFormatHandler( options.GetBoolDefault("noColor", false), options.GetBoolDefault("smallDate", true))) } handler := CallerFileHandler(streamHandler) if options.HandlerWrap != nil { handler = options.HandlerWrap.SetChild(handler) } h.SetHandlers(handler, options) } // Use built in rolling function func (h *CompositeMultiHandler) SetTerminalFile(filePath string, options *LogOptions) { writer := &lumberjack.Logger{ Filename: filePath, MaxSize: options.GetIntDefault("maxSizeMB", 1024), // megabytes MaxAge: options.GetIntDefault("maxAgeDays", 7), //days MaxBackups: options.GetIntDefault("maxBackups", 7), Compress: options.GetBoolDefault("compress", true), } h.SetTerminal(writer, options) } func (h *CompositeMultiHandler) Disable(levels ...LogLevel) { if len(levels) == 0 { levels = LvlAllList } for _, level := range levels { switch level { case LvlDebug: h.DebugHandler = nil case LvlInfo: h.InfoHandler = nil case LvlWarn: h.WarnHandler = nil case LvlError: h.ErrorHandler = nil case LvlCrit: h.CriticalHandler = nil } } } revel-1.0.0/logger/doc.go000066400000000000000000000010721370252312000151730ustar00rootroot00000000000000/* Package logger contains filters and handles for the logging utilities in Revel. These facilities all currently use the logging library called log15 at https://github.com/inconshreveable/log15 Defining handlers happens as follows 1) ALL handlers (log.all.output) replace any existing handlers 2) Output handlers (log.error.output) replace any existing handlers 3) Filter handlers (log.xxx.filter, log.xxx.nfilter) append to existing handlers, note log.all.filter is treated as a filter handler, so it will NOT replace existing ones */ package logger revel-1.0.0/logger/handlers.go000066400000000000000000000125141370252312000162310ustar00rootroot00000000000000package logger import ( "fmt" "io" ) type LevelFilterHandler struct { Level LogLevel h LogHandler } // Filters out records which do not match the level // Uses the `log15.FilterHandler` to perform this task func LevelHandler(lvl LogLevel, h LogHandler) LogHandler { return &LevelFilterHandler{lvl, h} } // The implementation of the Log func (h LevelFilterHandler) Log(r *Record) error { if r.Level == h.Level { return h.h.Log(r) } return nil } // Filters out records which do not match the level // Uses the `log15.FilterHandler` to perform this task func MinLevelHandler(lvl LogLevel, h LogHandler) LogHandler { return FilterHandler(func(r *Record) (pass bool) { return r.Level <= lvl }, h) } // Filters out records which match the level // Uses the `log15.FilterHandler` to perform this task func NotLevelHandler(lvl LogLevel, h LogHandler) LogHandler { return FilterHandler(func(r *Record) (pass bool) { return r.Level != lvl }, h) } func CallerFileHandler(h LogHandler) LogHandler { return FuncHandler(func(r *Record) error { r.Context.Add("caller", fmt.Sprint(r.Call)) return h.Log(r) }) } // Adds in a context called `caller` to the record (contains file name and line number like `foo.go:12`) // Uses the `log15.CallerFuncHandler` to perform this task func CallerFuncHandler(h LogHandler) LogHandler { return CallerFuncHandler(h) } // Filters out records which match the key value pair // Uses the `log15.MatchFilterHandler` to perform this task func MatchHandler(key string, value interface{}, h LogHandler) LogHandler { return MatchFilterHandler(key, value, h) } // MatchFilterHandler returns a Handler that only writes records // to the wrapped Handler if the given key in the logged // context matches the value. For example, to only log records // from your ui package: // // log.MatchFilterHandler("pkg", "app/ui", log.StdoutHandler) // func MatchFilterHandler(key string, value interface{}, h LogHandler) LogHandler { return FilterHandler(func(r *Record) (pass bool) { return r.Context[key] == value }, h) } // If match then A handler is called otherwise B handler is called func MatchAbHandler(key string, value interface{}, a, b LogHandler) LogHandler { return FuncHandler(func(r *Record) error { if r.Context[key] == value { return a.Log(r) } else if b != nil { return b.Log(r) } return nil }) } // The nil handler is used if logging for a specific request needs to be turned off func NilHandler() LogHandler { return FuncHandler(func(r *Record) error { return nil }) } // Match all values in map to log func MatchMapHandler(matchMap map[string]interface{}, a LogHandler) LogHandler { return matchMapHandler(matchMap, false, a) } // Match !(Match all values in map to log) The inverse of MatchMapHandler func NotMatchMapHandler(matchMap map[string]interface{}, a LogHandler) LogHandler { return matchMapHandler(matchMap, true, a) } // Rather then chaining multiple filter handlers, process all here func matchMapHandler(matchMap map[string]interface{}, inverse bool, a LogHandler) LogHandler { return FuncHandler(func(r *Record) error { matchCount := 0 for k, v := range matchMap { value, found := r.Context[k] if !found { return nil } // Test for two failure cases if value == v && inverse || value != v && !inverse { return nil } else { matchCount++ } } if matchCount != len(matchMap) { return nil } return a.Log(r) }) } // Filters out records which do not match the key value pair // Uses the `log15.FilterHandler` to perform this task func NotMatchHandler(key string, value interface{}, h LogHandler) LogHandler { return FilterHandler(func(r *Record) (pass bool) { return r.Context[key] != value }, h) } func MultiHandler(hs ...LogHandler) LogHandler { return FuncHandler(func(r *Record) error { for _, h := range hs { // what to do about failures? h.Log(r) } return nil }) } // StreamHandler writes log records to an io.Writer // with the given format. StreamHandler can be used // to easily begin writing log records to other // outputs. // // StreamHandler wraps itself with LazyHandler and SyncHandler // to evaluate Lazy objects and perform safe concurrent writes. func StreamHandler(wr io.Writer, fmtr LogFormat) LogHandler { h := FuncHandler(func(r *Record) error { _, err := wr.Write(fmtr.Format(r)) return err }) return LazyHandler(SyncHandler(h)) } // Filter handler func FilterHandler(fn func(r *Record) bool, h LogHandler) LogHandler { return FuncHandler(func(r *Record) error { if fn(r) { return h.Log(r) } return nil }) } // List log handler handles a list of LogHandlers type ListLogHandler struct { handlers []LogHandler } // Create a new list of log handlers func NewListLogHandler(h1, h2 LogHandler) *ListLogHandler { ll := &ListLogHandler{handlers: []LogHandler{h1, h2}} return ll } // Log the record func (ll *ListLogHandler) Log(r *Record) (err error) { for _, handler := range ll.handlers { if err == nil { err = handler.Log(r) } else { handler.Log(r) } } return } // Add another log handler func (ll *ListLogHandler) Add(h LogHandler) { if h != nil { ll.handlers = append(ll.handlers, h) } } // Remove a log handler func (ll *ListLogHandler) Del(h LogHandler) { if h != nil { for i, handler := range ll.handlers { if handler == h { ll.handlers = append(ll.handlers[:i], ll.handlers[i+1:]...) } } } } revel-1.0.0/logger/init.go000066400000000000000000000144411370252312000153750ustar00rootroot00000000000000package logger // Get all handlers based on the Config (if available) import ( "fmt" "github.com/revel/config" "log" "os" "path/filepath" "strings" ) func InitializeFromConfig(basePath string, config *config.Context) (c *CompositeMultiHandler) { // If running in test mode suppress anything that is not an error if config != nil && config.BoolDefault(TEST_MODE_FLAG, false) { // Preconfigure all the options config.SetOption("log.info.output", "none") config.SetOption("log.debug.output", "none") config.SetOption("log.warn.output", "none") config.SetOption("log.error.output", "stderr") config.SetOption("log.crit.output", "stderr") } // If the configuration has an all option we can skip some c, _ = NewCompositeMultiHandler() // Filters are assigned first, non filtered items override filters if config != nil && !config.BoolDefault(TEST_MODE_FLAG, false) { initAllLog(c, basePath, config) } initLogLevels(c, basePath, config) if c.CriticalHandler == nil && c.ErrorHandler != nil { c.CriticalHandler = c.ErrorHandler } if config != nil && !config.BoolDefault(TEST_MODE_FLAG, false) { initFilterLog(c, basePath, config) if c.CriticalHandler == nil && c.ErrorHandler != nil { c.CriticalHandler = c.ErrorHandler } initRequestLog(c, basePath, config) } return c } // Init the log.all configuration options func initAllLog(c *CompositeMultiHandler, basePath string, config *config.Context) { if config != nil { extraLogFlag := config.BoolDefault(SPECIAL_USE_FLAG, false) if output, found := config.String("log.all.output"); found { // Set all output for the specified handler if extraLogFlag { log.Printf("Adding standard handler for levels to >%s< ", output) } initHandlerFor(c, output, basePath, NewLogOptions(config, true, nil, LvlAllList...)) } } } // Init the filter options // log.all.filter .... // log.error.filter .... func initFilterLog(c *CompositeMultiHandler, basePath string, config *config.Context) { if config != nil { extraLogFlag := config.BoolDefault(SPECIAL_USE_FLAG, false) for _, logFilter := range logFilterList { // Init for all filters for _, name := range []string{"all", "debug", "info", "warn", "error", "crit", "trace", // TODO trace is deprecated } { optionList := config.Options(logFilter.LogPrefix + name + logFilter.LogSuffix) for _, option := range optionList { splitOptions := strings.Split(option, ".") keyMap := map[string]interface{}{} for x := 3; x < len(splitOptions); x += 2 { keyMap[splitOptions[x]] = splitOptions[x+1] } phandler := logFilter.parentHandler(keyMap) if extraLogFlag { log.Printf("Adding key map handler %s %s output %s", option, name, config.StringDefault(option, "")) fmt.Printf("Adding key map handler %s %s output %s matching %#v\n", option, name, config.StringDefault(option, ""), keyMap) } if name == "all" { initHandlerFor(c, config.StringDefault(option, ""), basePath, NewLogOptions(config, false, phandler)) } else { initHandlerFor(c, config.StringDefault(option, ""), basePath, NewLogOptions(config, false, phandler, toLevel[name])) } } } } } } // Init the log.error, log.warn etc configuration options func initLogLevels(c *CompositeMultiHandler, basePath string, config *config.Context) { for _, name := range []string{"debug", "info", "warn", "error", "crit", "trace", // TODO trace is deprecated } { if config != nil { extraLogFlag := config.BoolDefault(SPECIAL_USE_FLAG, false) output, found := config.String("log." + name + ".output") if found { if extraLogFlag { log.Printf("Adding standard handler %s output %s", name, output) } initHandlerFor(c, output, basePath, NewLogOptions(config, true, nil, toLevel[name])) } // Gets the list of options with said prefix } else { initHandlerFor(c, "stderr", basePath, NewLogOptions(config, true, nil, toLevel[name])) } } } // Init the request log options func initRequestLog(c *CompositeMultiHandler, basePath string, config *config.Context) { // Request logging to a separate output handler // This takes the InfoHandlers and adds a MatchAbHandler handler to it to direct // context with the word "section=requestlog" to that handler. // Note if request logging is not enabled the MatchAbHandler will not be added and the // request log messages will be sent out the INFO handler outputRequest := "stdout" if config != nil { outputRequest = config.StringDefault("log.request.output", "") } oldInfo := c.InfoHandler c.InfoHandler = nil if outputRequest != "" { initHandlerFor(c, outputRequest, basePath, NewLogOptions(config, false, nil, LvlInfo)) } if c.InfoHandler != nil || oldInfo != nil { if c.InfoHandler == nil { c.InfoHandler = oldInfo } else { c.InfoHandler = MatchAbHandler("section", "requestlog", c.InfoHandler, oldInfo) } } } // Returns a handler for the level using the output string // Accept formats for output string are // LogFunctionMap[value] callback function // `stdout` `stderr` `full/file/path/to/location/app.log` `full/file/path/to/location/app.json` func initHandlerFor(c *CompositeMultiHandler, output, basePath string, options *LogOptions) { if options.Ctx != nil { options.SetExtendedOptions( "noColor", !options.Ctx.BoolDefault("log.colorize", true), "smallDate", options.Ctx.BoolDefault("log.smallDate", true), "maxSizeMB", options.Ctx.IntDefault("log.maxsize", 1024*10), "maxAgeDays", options.Ctx.IntDefault("log.maxage", 14), "maxBackups", options.Ctx.IntDefault("log.maxbackups", 14), "compress", !options.Ctx.BoolDefault("log.compressBackups", true), ) } output = strings.TrimSpace(output) if funcHandler, found := LogFunctionMap[output]; found { funcHandler(c, options) } else { switch output { case "": fallthrough case "off": // No handler, discard data default: // Write to file specified if !filepath.IsAbs(output) { output = filepath.Join(basePath, output) } if err := os.MkdirAll(filepath.Dir(output), 0755); err != nil { log.Panic(err) } if strings.HasSuffix(output, "json") { c.SetJsonFile(output, options) } else { // Override defaults for a terminal file options.SetExtendedOptions("noColor", true) options.SetExtendedOptions("smallDate", false) c.SetTerminalFile(output, options) } } } return } revel-1.0.0/logger/init_test.go000066400000000000000000000172621370252312000164400ustar00rootroot00000000000000// Copyright (c) 2012-2018 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package logger_test import ( "github.com/revel/config" "github.com/revel/revel/logger" "github.com/stretchr/testify/assert" "os" "strings" "testing" ) type ( // A counter for the tester testCounter struct { debug, info, warn, error, critical int } // The data to tes testData struct { config []string result testResult tc *testCounter } // The test result testResult struct { debug, info, warn, error, critical int } ) // Single test cases var singleCases = []testData{ {config: []string{"log.crit.output"}, result: testResult{0, 0, 0, 0, 1}}, {config: []string{"log.error.output"}, result: testResult{0, 0, 0, 1, 1}}, {config: []string{"log.warn.output"}, result: testResult{0, 0, 1, 0, 0}}, {config: []string{"log.info.output"}, result: testResult{0, 1, 0, 0, 0}}, {config: []string{"log.debug.output"}, result: testResult{1, 0, 0, 0, 0}}, } // Test singles func TestSingleCases(t *testing.T) { rootLog := logger.New() for _, testCase := range singleCases { testCase.logTest(rootLog, t) testCase.validate(t) } } // Filter test cases var filterCases = []testData{ {config: []string{"log.crit.filter.module.app"}, result: testResult{0, 0, 0, 0, 1}}, {config: []string{"log.crit.filter.module.appa"}, result: testResult{0, 0, 0, 0, 0}}, {config: []string{"log.error.filter.module.app"}, result: testResult{0, 0, 0, 1, 1}}, {config: []string{"log.error.filter.module.appa"}, result: testResult{0, 0, 0, 0, 0}}, {config: []string{"log.warn.filter.module.app"}, result: testResult{0, 0, 1, 0, 0}}, {config: []string{"log.warn.filter.module.appa"}, result: testResult{0, 0, 0, 0, 0}}, {config: []string{"log.info.filter.module.app"}, result: testResult{0, 1, 0, 0, 0}}, {config: []string{"log.info.filter.module.appa"}, result: testResult{0, 0, 0, 0, 0}}, {config: []string{"log.debug.filter.module.app"}, result: testResult{1, 0, 0, 0, 0}}, {config: []string{"log.debug.filter.module.appa"}, result: testResult{0, 0, 0, 0, 0}}, } // Filter test func TestFilterCases(t *testing.T) { rootLog := logger.New("module", "app") for _, testCase := range filterCases { testCase.logTest(rootLog, t) testCase.validate(t) } } // Inverse test cases var nfilterCases = []testData{ {config: []string{"log.crit.nfilter.module.appa"}, result: testResult{0, 0, 0, 0, 1}}, {config: []string{"log.crit.nfilter.modules.appa"}, result: testResult{0, 0, 0, 0, 0}}, {config: []string{"log.crit.nfilter.module.app"}, result: testResult{0, 0, 0, 0, 0}}, {config: []string{"log.error.nfilter.module.appa"}, // Special case, when error is not nill critical inherits from error result: testResult{0, 0, 0, 1, 1}}, {config: []string{"log.error.nfilter.module.app"}, result: testResult{0, 0, 0, 0, 0}}, {config: []string{"log.warn.nfilter.module.appa"}, result: testResult{0, 0, 1, 0, 0}}, {config: []string{"log.warn.nfilter.module.app"}, result: testResult{0, 0, 0, 0, 0}}, {config: []string{"log.info.nfilter.module.appa"}, result: testResult{0, 1, 0, 0, 0}}, {config: []string{"log.info.nfilter.module.app"}, result: testResult{0, 0, 0, 0, 0}}, {config: []string{"log.debug.nfilter.module.appa"}, result: testResult{1, 0, 0, 0, 0}}, {config: []string{"log.debug.nfilter.module.app"}, result: testResult{0, 0, 0, 0, 0}}, } // Inverse test func TestNotFilterCases(t *testing.T) { rootLog := logger.New("module", "app") for _, testCase := range nfilterCases { testCase.logTest(rootLog, t) testCase.validate(t) } } // off test cases var offCases = []testData{ {config: []string{"log.all.output", "log.error.output=off"}, result: testResult{1, 1, 1, 0, 1}}, } // Off test func TestOffCases(t *testing.T) { rootLog := logger.New("module", "app") for _, testCase := range offCases { testCase.logTest(rootLog, t) testCase.validate(t) } } // Duplicate test cases var duplicateCases = []testData{ {config: []string{"log.all.output", "log.error.output", "log.error.filter.module.app"}, result: testResult{1, 1, 1, 2, 1}}, } // test duplicate cases func TestDuplicateCases(t *testing.T) { rootLog := logger.New("module", "app") for _, testCase := range duplicateCases { testCase.logTest(rootLog, t) testCase.validate(t) } } // Contradicting cases var contradictCases = []testData{ {config: []string{"log.all.output", "log.error.output=off", "log.all.output"}, result: testResult{1, 1, 1, 0, 1}}, {config: []string{"log.all.output", "log.error.output=off", "log.debug.filter.module.app"}, result: testResult{2, 1, 1, 0, 1}}, {config: []string{"log.all.filter.module.app", "log.info.output=off", "log.info.filter.module.app"}, result: testResult{1, 2, 1, 1, 1}}, {config: []string{"log.all.output", "log.info.output=off", "log.info.filter.module.app"}, result: testResult{1, 1, 1, 1, 1}}, } // Contradiction test func TestContradictCases(t *testing.T) { rootLog := logger.New("module", "app") for _, testCase := range contradictCases { testCase.logTest(rootLog, t) testCase.validate(t) } } // All test cases var allCases = []testData{ {config: []string{"log.all.filter.module.app"}, result: testResult{1, 1, 1, 1, 1}}, {config: []string{"log.all.output"}, result: testResult{2, 2, 2, 2, 2}}, } // All tests func TestAllCases(t *testing.T) { rootLog := logger.New("module", "app") for i, testCase := range allCases { testCase.logTest(rootLog, t) allCases[i] = testCase } rootLog = logger.New() for i, testCase := range allCases { testCase.logTest(rootLog, t) allCases[i] = testCase } for _, testCase := range allCases { testCase.validate(t) } } func (c *testCounter) Log(r *logger.Record) error { switch r.Level { case logger.LvlDebug: c.debug++ case logger.LvlInfo: c.info++ case logger.LvlWarn: c.warn++ case logger.LvlError: c.error++ case logger.LvlCrit: c.critical++ default: panic("Unknown log level") } return nil } func (td *testData) logTest(rootLog logger.MultiLogger, t *testing.T) { if td.tc == nil { td.tc = &testCounter{} counterInit(td.tc) } newContext := config.NewContext() for _, i := range td.config { iout := strings.Split(i, "=") if len(iout) > 1 { newContext.SetOption(iout[0], iout[1]) } else { newContext.SetOption(i, "test") } } newContext.SetOption("specialUseFlag", "true") handler := logger.InitializeFromConfig("test", newContext) rootLog.SetHandler(handler) td.runLogTest(rootLog) } func (td *testData) runLogTest(log logger.MultiLogger) { log.Debug("test") log.Info("test") log.Warn("test") log.Error("test") log.Crit("test") } func (td *testData) validate(t *testing.T) { t.Logf("Test %#v expected %#v", td.tc, td.result) assert.Equal(t, td.result.debug, td.tc.debug, "Debug failed "+strings.Join(td.config, " ")) assert.Equal(t, td.result.info, td.tc.info, "Info failed "+strings.Join(td.config, " ")) assert.Equal(t, td.result.warn, td.tc.warn, "Warn failed "+strings.Join(td.config, " ")) assert.Equal(t, td.result.error, td.tc.error, "Error failed "+strings.Join(td.config, " ")) assert.Equal(t, td.result.critical, td.tc.critical, "Critical failed "+strings.Join(td.config, " ")) } // Add test to the function map func counterInit(tc *testCounter) { logger.LogFunctionMap["test"] = func(c *logger.CompositeMultiHandler, logOptions *logger.LogOptions) { // Output to the test log and the stdout outHandler := logger.LogHandler( logger.NewListLogHandler(tc, logger.StreamHandler(os.Stdout, logger.TerminalFormatHandler(false, true))), ) if logOptions.HandlerWrap != nil { outHandler = logOptions.HandlerWrap.SetChild(outHandler) } c.SetHandlers(outHandler, logOptions) } } revel-1.0.0/logger/log_function_map.go000066400000000000000000000024141370252312000177520ustar00rootroot00000000000000package logger import ( "os" ) // The log function map can be added to, so that you can specify your own logging mechanism // it has defaults for off, stdout, stderr var LogFunctionMap = map[string]func(*CompositeMultiHandler, *LogOptions){ // Do nothing - set the logger off "off": func(c *CompositeMultiHandler, logOptions *LogOptions) { // Only drop the results if there is a parent handler defined if logOptions.HandlerWrap != nil { for _, l := range logOptions.Levels { c.SetHandler(logOptions.HandlerWrap.SetChild(NilHandler()), logOptions.ReplaceExistingHandler, l) } } else { // Clear existing handler c.SetHandlers(NilHandler(), logOptions) } }, // Do nothing - set the logger off "": func(*CompositeMultiHandler, *LogOptions) {}, // Set the levels to stdout, replace existing "stdout": func(c *CompositeMultiHandler, logOptions *LogOptions) { if logOptions.Ctx != nil { logOptions.SetExtendedOptions( "noColor", !logOptions.Ctx.BoolDefault("log.colorize", true), "smallDate", logOptions.Ctx.BoolDefault("log.smallDate", true)) } c.SetTerminal(os.Stdout, logOptions) }, // Set the levels to stderr output to terminal "stderr": func(c *CompositeMultiHandler, logOptions *LogOptions) { c.SetTerminal(os.Stderr, logOptions) }, } revel-1.0.0/logger/logger.go000066400000000000000000000133001370252312000157020ustar00rootroot00000000000000package logger import ( "fmt" "github.com/revel/config" "time" ) // The LogHandler defines the interface to handle the log records type ( // The Multilogger reduces the number of exposed defined logging variables, // and allows the output to be easily refined MultiLogger interface { // New returns a new Logger that has this logger's context plus the given context New(ctx ...interface{}) MultiLogger // SetHandler updates the logger to write records to the specified handler. SetHandler(h LogHandler) // Set the stack depth for the logger SetStackDepth(int) MultiLogger // Log a message at the given level with context key/value pairs Debug(msg string, ctx ...interface{}) // Log a message at the given level formatting message with the parameters Debugf(msg string, params ...interface{}) // Log a message at the given level with context key/value pairs Info(msg string, ctx ...interface{}) // Log a message at the given level formatting message with the parameters Infof(msg string, params ...interface{}) // Log a message at the given level with context key/value pairs Warn(msg string, ctx ...interface{}) // Log a message at the given level formatting message with the parameters Warnf(msg string, params ...interface{}) // Log a message at the given level with context key/value pairs Error(msg string, ctx ...interface{}) // Log a message at the given level formatting message with the parameters Errorf(msg string, params ...interface{}) // Log a message at the given level with context key/value pairs Crit(msg string, ctx ...interface{}) // Log a message at the given level formatting message with the parameters Critf(msg string, params ...interface{}) // Log a message at the given level with context key/value pairs and exits Fatal(msg string, ctx ...interface{}) // Log a message at the given level formatting message with the parameters and exits Fatalf(msg string, params ...interface{}) // Log a message at the given level with context key/value pairs and panics Panic(msg string, ctx ...interface{}) // Log a message at the given level formatting message with the parameters and panics Panicf(msg string, params ...interface{}) } // The log handler interface LogHandler interface { Log(*Record) error //log15.Handler } // The log stack handler interface LogStackHandler interface { LogHandler GetStack() int } // The log handler interface which has child logs ParentLogHandler interface { SetChild(handler LogHandler) LogHandler } // The log format interface LogFormat interface { Format(r *Record) []byte } // The log level type LogLevel int // Used for the callback to LogFunctionMap LogOptions struct { Ctx *config.Context ReplaceExistingHandler bool HandlerWrap ParentLogHandler Levels []LogLevel ExtendedOptions map[string]interface{} } // The log record Record struct { Message string // The message Time time.Time // The time Level LogLevel //The level Call CallStack // The call stack if built Context ContextMap // The context } // The lazy structure to implement a function to be invoked only if needed Lazy struct { Fn interface{} // the function } // Currently the only requirement for the callstack is to support the Formatter method // which stack.Call does so we use that CallStack interface { fmt.Formatter // Requirement } ) // FormatFunc returns a new Format object which uses // the given function to perform record formatting. func FormatFunc(f func(*Record) []byte) LogFormat { return formatFunc(f) } type formatFunc func(*Record) []byte func (f formatFunc) Format(r *Record) []byte { return f(r) } func NewRecord(message string, level LogLevel) *Record { return &Record{Message: message, Context: ContextMap{}, Level: level} } const ( LvlCrit LogLevel = iota // Critical LvlError // Error LvlWarn // Warning LvlInfo // Information LvlDebug // Debug ) // A list of all the log levels var LvlAllList = []LogLevel{LvlDebug, LvlInfo, LvlWarn, LvlError, LvlCrit} // Implements the ParentLogHandler type parentLogHandler struct { setChild func(handler LogHandler) LogHandler } // Create a new parent log handler func NewParentLogHandler(callBack func(child LogHandler) LogHandler) ParentLogHandler { return &parentLogHandler{callBack} } // Sets the child of the log handler func (p *parentLogHandler) SetChild(child LogHandler) LogHandler { return p.setChild(child) } // Create a new log options func NewLogOptions(cfg *config.Context, replaceHandler bool, phandler ParentLogHandler, lvl ...LogLevel) (logOptions *LogOptions) { logOptions = &LogOptions{ Ctx: cfg, ReplaceExistingHandler: replaceHandler, HandlerWrap: phandler, Levels: lvl, ExtendedOptions: map[string]interface{}{}, } return } // Assumes options will be an even number and have a string, value syntax func (l *LogOptions) SetExtendedOptions(options ...interface{}) { for x := 0; x < len(options); x += 2 { l.ExtendedOptions[options[x].(string)] = options[x+1] } } // Gets a string option with default func (l *LogOptions) GetStringDefault(option, value string) string { if v, found := l.ExtendedOptions[option]; found { return v.(string) } return value } // Gets an int option with default func (l *LogOptions) GetIntDefault(option string, value int) int { if v, found := l.ExtendedOptions[option]; found { return v.(int) } return value } // Gets a boolean option with default func (l *LogOptions) GetBoolDefault(option string, value bool) bool { if v, found := l.ExtendedOptions[option]; found { return v.(bool) } return value } revel-1.0.0/logger/revel_logger.go000066400000000000000000000071331370252312000171060ustar00rootroot00000000000000package logger import ( "fmt" "github.com/revel/log15" "log" "os" ) // This type implements the MultiLogger type RevelLogger struct { log15.Logger } // Set the systems default logger // Default logs will be captured and handled by revel at level info func SetDefaultLog(fromLog MultiLogger) { log.SetOutput(loggerRewrite{Logger: fromLog, Level: log15.LvlInfo, hideDeprecated: true}) // No need to show date and time, that will be logged with revel log.SetFlags(0) } func (rl *RevelLogger) Debugf(msg string, param ...interface{}) { rl.Debug(fmt.Sprintf(msg, param...)) } // Print a formatted info message func (rl *RevelLogger) Infof(msg string, param ...interface{}) { rl.Info(fmt.Sprintf(msg, param...)) } // Print a formatted warn message func (rl *RevelLogger) Warnf(msg string, param ...interface{}) { rl.Warn(fmt.Sprintf(msg, param...)) } // Print a formatted error message func (rl *RevelLogger) Errorf(msg string, param ...interface{}) { rl.Error(fmt.Sprintf(msg, param...)) } // Print a formatted critical message func (rl *RevelLogger) Critf(msg string, param ...interface{}) { rl.Crit(fmt.Sprintf(msg, param...)) } // Print a formatted fatal message func (rl *RevelLogger) Fatalf(msg string, param ...interface{}) { rl.Fatal(fmt.Sprintf(msg, param...)) } // Print a formatted panic message func (rl *RevelLogger) Panicf(msg string, param ...interface{}) { rl.Panic(fmt.Sprintf(msg, param...)) } // Print a critical message and call os.Exit(1) func (rl *RevelLogger) Fatal(msg string, ctx ...interface{}) { rl.Crit(msg, ctx...) os.Exit(1) } // Print a critical message and panic func (rl *RevelLogger) Panic(msg string, ctx ...interface{}) { rl.Crit(msg, ctx...) panic(msg) } // Override log15 method func (rl *RevelLogger) New(ctx ...interface{}) MultiLogger { old := &RevelLogger{Logger: rl.Logger.New(ctx...)} return old } // Set the stack level to check for the caller func (rl *RevelLogger) SetStackDepth(amount int) MultiLogger { rl.Logger.SetStackDepth(amount) // Ignore the logger returned return rl } // Create a new logger func New(ctx ...interface{}) MultiLogger { r := &RevelLogger{Logger: log15.New(ctx...)} //r.SetStackDepth(2) return r } // Set the handler in the Logger func (rl *RevelLogger) SetHandler(h LogHandler) { rl.Logger.SetHandler(callHandler(h.Log)) } // The function wrapper to implement the callback type callHandler func(r *Record) error // Log implementation, reads the record and extracts the details from the log record // Hiding the implementation. func (c callHandler) Log(log *log15.Record) error { ctx := log.Ctx var ctxMap ContextMap if len(ctx) > 0 { ctxMap = make(ContextMap, len(ctx)/2) for i := 0; i < len(ctx); i += 2 { v := ctx[i] key, ok := v.(string) if !ok { key = fmt.Sprintf("LOGGER_INVALID_KEY %v", v) } var value interface{} if len(ctx) > i+1 { value = ctx[i+1] } else { value = "LOGGER_VALUE_MISSING" } ctxMap[key] = value } } else { ctxMap = make(ContextMap, 0) } r := &Record{Message: log.Msg, Context: ctxMap, Time: log.Time, Level: LogLevel(log.Lvl), Call: CallStack(log.Call)} return c(r) } // Internally used contextMap, allows conversion of map to map[string]string type ContextMap map[string]interface{} // Convert the context map to be string only values, any non string values are ignored func (m ContextMap) StringMap() (newMap map[string]string) { if m != nil { newMap = map[string]string{} for key, value := range m { if svalue, isstring := value.(string); isstring { newMap[key] = svalue } } } return } func (m ContextMap) Add(key string, value interface{}) { m[key] = value } revel-1.0.0/logger/terminal_format.go000066400000000000000000000126351370252312000176200ustar00rootroot00000000000000package logger import ( "bytes" "encoding/json" "fmt" "reflect" "strconv" "sync" "time" ) const ( timeFormat = "2006-01-02T15:04:05-0700" termTimeFormat = "2006/01/02 15:04:05" termSmallTimeFormat = "15:04:05" floatFormat = 'f' errorKey = "REVEL_ERROR" ) var ( levelString = map[LogLevel]string{LvlDebug: "DEBUG", LvlInfo: "INFO", LvlWarn: "WARN", LvlError: "ERROR", LvlCrit: "CRIT"} ) // Outputs to the terminal in a format like below // INFO 09:11:32 server-engine.go:169: Request Stats func TerminalFormatHandler(noColor bool, smallDate bool) LogFormat { dateFormat := termTimeFormat if smallDate { dateFormat = termSmallTimeFormat } return FormatFunc(func(r *Record) []byte { // Bash coloring http://misc.flogisoft.com/bash/tip_colors_and_formatting var color = 0 switch r.Level { case LvlCrit: // Magenta color = 35 case LvlError: // Red color = 31 case LvlWarn: // Yellow color = 33 case LvlInfo: // Green color = 32 case LvlDebug: // Cyan color = 36 } b := &bytes.Buffer{} caller, _ := r.Context["caller"].(string) module, _ := r.Context["module"].(string) if noColor == false && color > 0 { if len(module) > 0 { fmt.Fprintf(b, "\x1b[%dm%-5s\x1b[0m %s %6s %13s: %-40s ", color, levelString[r.Level], r.Time.Format(dateFormat), module, caller, r.Message) } else { fmt.Fprintf(b, "\x1b[%dm%-5s\x1b[0m %s %13s: %-40s ", color, levelString[r.Level], r.Time.Format(dateFormat), caller, r.Message) } } else { fmt.Fprintf(b, "%-5s %s %6s %13s: %-40s", levelString[r.Level], r.Time.Format(dateFormat), module, caller, r.Message) } i := 0 for k, v := range r.Context { if i != 0 { b.WriteByte(' ') } i++ if k == "module" || k == "caller" { continue } v := formatLogfmtValue(v) // TODO: we should probably check that all of your key bytes aren't invalid if noColor == false && color > 0 { fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m=%s", color, k, v) } else { b.WriteString(k) b.WriteByte('=') b.WriteString(v) } } b.WriteByte('\n') return b.Bytes() }) } // formatValue formats a value for serialization func formatLogfmtValue(value interface{}) string { if value == nil { return "nil" } if t, ok := value.(time.Time); ok { // Performance optimization: No need for escaping since the provided // timeFormat doesn't have any escape characters, and escaping is // expensive. return t.Format(termTimeFormat) } value = formatShared(value) switch v := value.(type) { case bool: return strconv.FormatBool(v) case float32: return strconv.FormatFloat(float64(v), floatFormat, 3, 64) case float64: return strconv.FormatFloat(v, floatFormat, 7, 64) case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: return fmt.Sprintf("%d", value) case string: return escapeString(v) default: return escapeString(fmt.Sprintf("%+v", value)) } } // Format the value in json format func formatShared(value interface{}) (result interface{}) { defer func() { if err := recover(); err != nil { if v := reflect.ValueOf(value); v.Kind() == reflect.Ptr && v.IsNil() { result = "nil" } else { panic(err) } } }() switch v := value.(type) { case time.Time: return v.Format(timeFormat) case error: return v.Error() case fmt.Stringer: return v.String() default: return v } } // A reusuable buffer for outputting data var stringBufPool = sync.Pool{ New: func() interface{} { return new(bytes.Buffer) }, } // Escape the string when needed func escapeString(s string) string { needsQuotes := false needsEscape := false for _, r := range s { if r <= ' ' || r == '=' || r == '"' { needsQuotes = true } if r == '\\' || r == '"' || r == '\n' || r == '\r' || r == '\t' { needsEscape = true } } if needsEscape == false && needsQuotes == false { return s } e := stringBufPool.Get().(*bytes.Buffer) e.WriteByte('"') for _, r := range s { switch r { case '\\', '"': e.WriteByte('\\') e.WriteByte(byte(r)) case '\n': e.WriteString("\\n") case '\r': e.WriteString("\\r") case '\t': e.WriteString("\\t") default: e.WriteRune(r) } } e.WriteByte('"') var ret string if needsQuotes { ret = e.String() } else { ret = string(e.Bytes()[1 : e.Len()-1]) } e.Reset() stringBufPool.Put(e) return ret } // JsonFormatEx formats log records as JSON objects. If pretty is true, // records will be pretty-printed. If lineSeparated is true, records // will be logged with a new line between each record. func JsonFormatEx(pretty, lineSeparated bool) LogFormat { jsonMarshal := json.Marshal if pretty { jsonMarshal = func(v interface{}) ([]byte, error) { return json.MarshalIndent(v, "", " ") } } return FormatFunc(func(r *Record) []byte { props := make(map[string]interface{}) props["t"] = r.Time props["lvl"] = levelString[r.Level] props["msg"] = r.Message for k, v := range r.Context { props[k] = formatJsonValue(v) } b, err := jsonMarshal(props) if err != nil { b, _ = jsonMarshal(map[string]string{ errorKey: err.Error(), }) return b } if lineSeparated { b = append(b, '\n') } return b }) } func formatJsonValue(value interface{}) interface{} { value = formatShared(value) switch value.(type) { case int, int8, int16, int32, int64, float32, float64, uint, uint8, uint16, uint32, uint64, string: return value default: return fmt.Sprintf("%+v", value) } } revel-1.0.0/logger/utils.go000066400000000000000000000061571370252312000155770ustar00rootroot00000000000000package logger import ( "github.com/revel/log15" "gopkg.in/stack.v0" "log" ) // Utility package to make existing logging backwards compatible var ( // Convert the string to LogLevel toLevel = map[string]LogLevel{"debug": LogLevel(log15.LvlDebug), "info": LogLevel(log15.LvlInfo), "request": LogLevel(log15.LvlInfo), "warn": LogLevel(log15.LvlWarn), "error": LogLevel(log15.LvlError), "crit": LogLevel(log15.LvlCrit), "trace": LogLevel(log15.LvlDebug), // TODO trace is deprecated, replaced by debug } ) const ( // The test mode flag overrides the default log level and shows only errors TEST_MODE_FLAG = "testModeFlag" // The special use flag enables showing messages when the logger is setup SPECIAL_USE_FLAG = "specialUseFlag" ) // Returns the logger for the name func GetLogger(name string, logger MultiLogger) (l *log.Logger) { switch name { case "trace": // TODO trace is deprecated, replaced by debug l = log.New(loggerRewrite{Logger: logger, Level: log15.LvlDebug}, "", 0) case "debug": l = log.New(loggerRewrite{Logger: logger, Level: log15.LvlDebug}, "", 0) case "info": l = log.New(loggerRewrite{Logger: logger, Level: log15.LvlInfo}, "", 0) case "warn": l = log.New(loggerRewrite{Logger: logger, Level: log15.LvlWarn}, "", 0) case "error": l = log.New(loggerRewrite{Logger: logger, Level: log15.LvlError}, "", 0) case "request": l = log.New(loggerRewrite{Logger: logger, Level: log15.LvlInfo}, "", 0) } return l } // Used by the initFilterLog to handle the filters var logFilterList = []struct { LogPrefix, LogSuffix string parentHandler func(map[string]interface{}) ParentLogHandler }{{ "log.", ".filter", func(keyMap map[string]interface{}) ParentLogHandler { return NewParentLogHandler(func(child LogHandler) LogHandler { return MatchMapHandler(keyMap, child) }) }, }, { "log.", ".nfilter", func(keyMap map[string]interface{}) ParentLogHandler { return NewParentLogHandler(func(child LogHandler) LogHandler { return NotMatchMapHandler(keyMap, child) }) }, }} // This structure and method will handle the old output format and log it to the new format type loggerRewrite struct { Logger MultiLogger Level log15.Lvl hideDeprecated bool } // The message indicating that a logger is using a deprecated log mechanism var log_deprecated = []byte("* LOG DEPRECATED * ") // Implements the Write of the logger func (lr loggerRewrite) Write(p []byte) (n int, err error) { if !lr.hideDeprecated { p = append(log_deprecated, p...) } n = len(p) if len(p) > 0 && p[n-1] == '\n' { p = p[:n-1] n-- } switch lr.Level { case log15.LvlInfo: lr.Logger.Info(string(p)) case log15.LvlDebug: lr.Logger.Debug(string(p)) case log15.LvlWarn: lr.Logger.Warn(string(p)) case log15.LvlError: lr.Logger.Error(string(p)) case log15.LvlCrit: lr.Logger.Crit(string(p)) } return } // For logging purposes the call stack can be used to record the stack trace of a bad error // simply pass it as a context field in your log statement like // `controller.Log.Crit("This should not occur","stack",revel.NewCallStack())` func NewCallStack() interface{} { return stack.Trace() } revel-1.0.0/logger/wrap_handlers.go000066400000000000000000000051021370252312000172550ustar00rootroot00000000000000package logger // FuncHandler returns a Handler that logs records with the given // function. import ( "fmt" "reflect" "sync" "time" ) // Function handler wraps the declared function and returns the handler for it func FuncHandler(fn func(r *Record) error) LogHandler { return funcHandler(fn) } // The type decleration for the function type funcHandler func(r *Record) error // The implementation of the Log func (h funcHandler) Log(r *Record) error { return h(r) } // This function allows you to do a full declaration for the log, // it is recommended you use FuncHandler instead func HandlerFunc(log func(message string, time time.Time, level LogLevel, call CallStack, context ContextMap) error) LogHandler { return remoteHandler(log) } // The type used for the HandlerFunc type remoteHandler func(message string, time time.Time, level LogLevel, call CallStack, context ContextMap) error // The Log implementation func (c remoteHandler) Log(record *Record) error { return c(record.Message, record.Time, record.Level, record.Call, record.Context) } // SyncHandler can be wrapped around a handler to guarantee that // only a single Log operation can proceed at a time. It's necessary // for thread-safe concurrent writes. func SyncHandler(h LogHandler) LogHandler { var mu sync.Mutex return FuncHandler(func(r *Record) error { defer mu.Unlock() mu.Lock() return h.Log(r) }) } // LazyHandler writes all values to the wrapped handler after evaluating // any lazy functions in the record's context. It is already wrapped // around StreamHandler and SyslogHandler in this library, you'll only need // it if you write your own Handler. func LazyHandler(h LogHandler) LogHandler { return FuncHandler(func(r *Record) error { for k, v := range r.Context { if lz, ok := v.(Lazy); ok { value, err := evaluateLazy(lz) if err != nil { r.Context[errorKey] = "bad lazy " + k } else { v = value } } } return h.Log(r) }) } func evaluateLazy(lz Lazy) (interface{}, error) { t := reflect.TypeOf(lz.Fn) if t.Kind() != reflect.Func { return nil, fmt.Errorf("INVALID_LAZY, not func: %+v", lz.Fn) } if t.NumIn() > 0 { return nil, fmt.Errorf("INVALID_LAZY, func takes args: %+v", lz.Fn) } if t.NumOut() == 0 { return nil, fmt.Errorf("INVALID_LAZY, no func return val: %+v", lz.Fn) } value := reflect.ValueOf(lz.Fn) results := value.Call([]reflect.Value{}) if len(results) == 1 { return results[0].Interface(), nil } else { values := make([]interface{}, len(results)) for i, v := range results { values[i] = v.Interface() } return values, nil } } revel-1.0.0/model/000077500000000000000000000000001370252312000137205ustar00rootroot00000000000000revel-1.0.0/model/revel_container.go000066400000000000000000000002451370252312000174270ustar00rootroot00000000000000package model // The single instance object that has the config populated to it type ( RevelContainer struct { Controller RevelController Paths RevelPaths } )revel-1.0.0/model/revel_controller.go000066400000000000000000000012671370252312000176350ustar00rootroot00000000000000package model import "github.com/revel/revel/utils" type RevelController struct { Reuse bool // True if the controllers are reused Set via revel.controller.reuse Stack *utils.SimpleLockStack // size set by revel.controller.stack, revel.controller.maxstack CachedMap map[string]*utils.SimpleLockStack // The map of reusable controllers CachedStackSize int // The default size of each stack in CachedMap Set via revel.cache.controller.stack CachedStackMaxSize int // The max size of each stack in CachedMap Set via revel.cache.controller.maxstack } revel-1.0.0/model/revel_paths.go000066400000000000000000000005371370252312000165700ustar00rootroot00000000000000package model type RevelPaths struct { Import string Source string Base string Code []string // Consolidated code paths Template []string // Consolidated template paths Config []string // Consolidated configuration paths ModuleMap map[string]*RevelUnit // The module path map } revel-1.0.0/model/revel_unit.go000066400000000000000000000011421370252312000164210ustar00rootroot00000000000000package model const ( APP RevelUnitType = 1 // App always overrides all MODULE RevelUnitType = 2 // Module is next REVEL RevelUnitType = 3 // Revel is last ) type ( RevelUnit struct { Name string // The friendly name for the unit Config string // The config file contents Type RevelUnitType // The type of the unit Messages string // The messages BasePath string // The filesystem path of the unit ImportPath string // The import path for the package Container *RevelContainer } RevelUnitList []*RevelUnit RevelUnitType int ) revel-1.0.0/module.go000066400000000000000000000153471370252312000144460ustar00rootroot00000000000000package revel import ( "fmt" "path/filepath" "sort" "strings" "github.com/go-stack/stack" "github.com/revel/revel/logger" ) // Module specific functions type Module struct { Name, ImportPath, Path string ControllerTypeList []*ControllerType Log logger.MultiLogger initializedModules map[string]ModuleCallbackInterface } // Modules can be called back after they are loaded in revel by using this interface. type ModuleCallbackInterface func(*Module) // The namespace separator constant const namespaceSeperator = `\` // (note cannot be . or : as this is already used for routes) var ( Modules []*Module // The list of modules in use anyModule = &Module{} // Wildcard search for controllers for a module (for backward compatible lookups) appModule = &Module{Name: "App", initializedModules: map[string]ModuleCallbackInterface{}, Log: AppLog} // The app module moduleLog = RevelLog.New("section", "module") ) // Called by a module init() function, caller will receive the *Module object created for that module // This would be useful for assigning a logger for logging information in the module (since the module context would be correct) func RegisterModuleInit(callback ModuleCallbackInterface) { // Store the module that called this so we can do a callback when the app is initialized // The format %+k is from go-stack/Call.Format and returns the package path key := fmt.Sprintf("%+k", stack.Caller(1)) appModule.initializedModules[key] = callback if Initialized { RevelLog.Error("Application already initialized, initializing using app module", "key", key) callback(appModule) } } // Called on startup to make a callback so that modules can be initialized through the `RegisterModuleInit` function func init() { AddInitEventHandler(func(typeOf Event, value interface{}) (responseOf EventResponse) { if typeOf == REVEL_BEFORE_MODULES_LOADED { Modules = []*Module{appModule} appModule.Path = filepath.ToSlash(AppPath) appModule.ImportPath = filepath.ToSlash(ImportPath) } return }) } // Returns the namespace for the module in the format `module_name|` func (m *Module) Namespace() (namespace string) { namespace = m.Name + namespaceSeperator return } // Returns the named controller and action that is in this module func (m *Module) ControllerByName(name, action string) (ctype *ControllerType) { comparison := name if strings.Index(name, namespaceSeperator) < 0 { comparison = m.Namespace() + name } for _, c := range m.ControllerTypeList { if c.Name() == comparison { ctype = c break } } return } // Adds the controller type to this module func (m *Module) AddController(ct *ControllerType) { m.ControllerTypeList = append(m.ControllerTypeList, ct) } // Based on the full path given return the relevant module // Only to be used on initialization func ModuleFromPath(packagePath string, addGopathToPath bool) (module *Module) { packagePath = filepath.ToSlash(packagePath) // The module paths will match the configuration module paths, so we will use those to determine them // Since the revel.Init is called first, then revel.Config exists and can be used to determine the module path // See if the path exists in the module based for i := range Modules { if strings.Index(packagePath, Modules[i].ImportPath)==0 { // This is a prefix, so the module is this module module = Modules[i] break } if module != nil { break } } // Default to the app module if not found if module == nil { module = appModule } return } // ModuleByName returns the module of the given name, if loaded, case insensitive. func ModuleByName(name string) (*Module, bool) { // If the name ends with the namespace separator remove it if name[len(name)-1] == []byte(namespaceSeperator)[0] { name = name[:len(name)-1] } name = strings.ToLower(name) if name == strings.ToLower(appModule.Name) { return appModule, true } for _, module := range Modules { if strings.ToLower(module.Name) == name { return module, true } } return nil, false } // Loads the modules specified in the config func loadModules() { keys := []string{} for _, key := range Config.Options("module.") { keys = append(keys, key) } // Reorder module order by key name, a poor mans sort but at least it is consistent sort.Strings(keys) for _, key := range keys { moduleLog.Debug("Sorted keys", "keys", key) } for _, key := range keys { moduleImportPath := Config.StringDefault(key, "") if moduleImportPath == "" { continue } modulePath, err := ResolveImportPath(moduleImportPath) if err != nil { moduleLog.Error("Failed to load module. Import of path failed", "modulePath", moduleImportPath, "error", err) } // Drop anything between module.???. subKey := key[len("module."):] if index := strings.Index(subKey, "."); index > -1 { subKey = subKey[index+1:] } addModule(subKey, moduleImportPath, modulePath) } // Modules loaded, now show module path for key, callback := range appModule.initializedModules { if m := ModuleFromPath(key, false); m != nil { callback(m) } else { RevelLog.Error("Callback for non registered module initializing with application module", "modulePath", key) callback(appModule) } } } // called by `loadModules`, creates a new `Module` instance and appends it to the `Modules` list func addModule(name, importPath, modulePath string) { if _, found := ModuleByName(name); found { moduleLog.Panic("Attempt to import duplicate module %s path %s aborting startup", "name", name, "path", modulePath) } Modules = append(Modules, &Module{Name: name, ImportPath: filepath.ToSlash(importPath), Path: filepath.ToSlash(modulePath), Log: RootLog.New("module", name)}) if codePath := filepath.Join(modulePath, "app"); DirExists(codePath) { CodePaths = append(CodePaths, codePath) if viewsPath := filepath.Join(modulePath, "app", "views"); DirExists(viewsPath) { TemplatePaths = append(TemplatePaths, viewsPath) } } moduleLog.Debug("Loaded module ", "module", filepath.Base(modulePath)) // Hack: There is presently no way for the testrunner module to add the // "test" subdirectory to the CodePaths. So this does it instead. if importPath == Config.StringDefault("module.testrunner", "github.com/revel/modules/testrunner") { joinedPath := filepath.Join(BasePath, "tests") moduleLog.Debug("Found testrunner module, adding `tests` path ", "path", joinedPath) CodePaths = append(CodePaths, joinedPath) } if testsPath := filepath.Join(modulePath, "tests"); DirExists(testsPath) { moduleLog.Debug("Found tests path ", "path", testsPath) CodePaths = append(CodePaths, testsPath) } } revel-1.0.0/namespace.go000066400000000000000000000023241370252312000151040ustar00rootroot00000000000000package revel import ( "bytes" "regexp" ) // Module matching template syntax allows for modules to replace this text with the name of the module declared on import // this allows the reverse router to use correct syntax // Match _LOCAL_.static or _LOCAL_| var namespaceReplacement = regexp.MustCompile(`(_LOCAL_)(\.(.*?))?\\`) // Function to replace the bytes data that may match the _LOCAL_ namespace specifier, // the replacement will be the current module.Name func namespaceReplace(fileBytes []byte, module *Module) []byte { newBytes, lastIndex := &bytes.Buffer{}, 0 matches := namespaceReplacement.FindAllSubmatchIndex(fileBytes, -1) for _, match := range matches { // Write up to first bytes newBytes.Write(fileBytes[lastIndex:match[0]]) // skip ahead index to match[1] lastIndex = match[3] if match[4] > 0 { // This match includes the module name as imported by the module // We could transform the module name if it is different.. // For now leave it the same // so _LOCAL_.static| becomes static| lastIndex++ } else { // Inject the module name newBytes.Write([]byte(module.Name)) } } // Write remainder of document newBytes.Write(fileBytes[lastIndex:]) return newBytes.Bytes() } revel-1.0.0/none000066400000000000000000000005401370252312000135010ustar00rootroot00000000000000INFO 2020/07/11 22:52:29 revel revel.go:124: Paths app=/tmp/release/v1.0.0/go/src/github.com/revel/revel/testdata/app views=/tmp/release/v1.0.0/go/src/github.com/revel/revel/testdata/app/views revel=/tmp/release/v1.0.0/go/src/github.com/revel/revel base=/tmp/release/v1.0.0/go/src/github.com/revel/revel/testdata revel-1.0.0/panic.go000066400000000000000000000026441370252312000142470ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package revel import ( "fmt" "net/http" "runtime/debug" ) // PanicFilter wraps the action invocation in a protective defer blanket that // converts panics into 500 error pages. func PanicFilter(c *Controller, fc []Filter) { defer func() { if err := recover(); err != nil { handleInvocationPanic(c, err) } }() fc[0](c, fc[1:]) } // This function handles a panic in an action invocation. // It cleans up the stack trace, logs it, and displays an error page. func handleInvocationPanic(c *Controller, err interface{}) { error := NewErrorFromPanic(err) if error != nil { utilLog.Error("PanicFilter: Caught panic", "error", err, "stack", error.Stack) if DevMode { fmt.Println(err) fmt.Println(error.Stack) } } else { utilLog.Error("PanicFilter: Caught panic, unable to determine stack location", "error", err, "stack", string(debug.Stack())) if DevMode { fmt.Println(err) fmt.Println("stack", string(debug.Stack())) } } if error == nil && DevMode { // Only show the sensitive information in the debug stack trace in development mode, not production c.Response.SetStatus(http.StatusInternalServerError) _, _ = c.Response.GetWriter().Write(debug.Stack()) return } c.Result = c.RenderError(error) } revel-1.0.0/params.go000066400000000000000000000120361370252312000144340ustar00rootroot00000000000000// Copyright (c) 2012-2017 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package revel import ( "encoding/json" "errors" "io/ioutil" "mime/multipart" "net/url" "os" "reflect" ) // Params provides a unified view of the request params. // Includes: // - URL query string // - Form values // - File uploads // // Warning: param maps other than Values may be nil if there were none. type Params struct { url.Values // A unified view of all the individual param maps below. // Set by the router Fixed url.Values // Fixed parameters from the route, e.g. App.Action("fixed param") Route url.Values // Parameters extracted from the route, e.g. /customers/{id} // Set by the ParamsFilter Query url.Values // Parameters from the query string, e.g. /index?limit=10 Form url.Values // Parameters from the request body. Files map[string][]*multipart.FileHeader // Files uploaded in a multipart form tmpFiles []*os.File // Temp files used during the request. JSON []byte // JSON data from request body } var paramsLogger = RevelLog.New("section", "params") // ParseParams parses the `http.Request` params into `revel.Controller.Params` func ParseParams(params *Params, req *Request) { params.Query = req.GetQuery() // Parse the body depending on the content type. switch req.ContentType { case "application/x-www-form-urlencoded": // Typical form. var err error if params.Form, err = req.GetForm(); err != nil { paramsLogger.Warn("ParseParams: Error parsing request body", "error", err) } case "multipart/form-data": // Multipart form. if mp, err := req.GetMultipartForm(); err != nil { paramsLogger.Warn("ParseParams: parsing request body:", "error", err) } else { params.Form = mp.GetValues() params.Files = mp.GetFiles() } case "application/json": fallthrough case "text/json": if body := req.GetBody(); body != nil { if content, err := ioutil.ReadAll(body); err == nil { // We wont bind it until we determine what we are binding too params.JSON = content } else { paramsLogger.Error("ParseParams: Failed to ready request body bytes", "error", err) } } else { paramsLogger.Info("ParseParams: Json post received with empty body") } } params.Values = params.calcValues() } // Bind looks for the named parameter, converts it to the requested type, and // writes it into "dest", which must be settable. If the value can not be // parsed, "dest" is set to the zero value. func (p *Params) Bind(dest interface{}, name string) { value := reflect.ValueOf(dest) if value.Kind() != reflect.Ptr { paramsLogger.Panic("Bind: revel/params: non-pointer passed to Bind: " + name) } value = value.Elem() if !value.CanSet() { paramsLogger.Panic("Bind: revel/params: non-settable variable passed to Bind: " + name) } // Remove the json from the Params, this will stop the binder from attempting // to use the json data to populate the destination interface. We do not want // to do this on a named bind directly against the param, it is ok to happen when // the action is invoked. jsonData := p.JSON p.JSON = nil value.Set(Bind(p, name, value.Type())) p.JSON = jsonData } // Bind binds the JSON data to the dest. func (p *Params) BindJSON(dest interface{}) error { value := reflect.ValueOf(dest) if value.Kind() != reflect.Ptr { paramsLogger.Warn("BindJSON: Not a pointer") return errors.New("BindJSON not a pointer") } if err := json.Unmarshal(p.JSON, dest); err != nil { paramsLogger.Warn("BindJSON: Unable to unmarshal request:", "error", err) return err } return nil } // calcValues returns a unified view of the component param maps. func (p *Params) calcValues() url.Values { numParams := len(p.Query) + len(p.Fixed) + len(p.Route) + len(p.Form) // If there were no params, return an empty map. if numParams == 0 { return make(url.Values, 0) } // If only one of the param sources has anything, return that directly. switch numParams { case len(p.Query): return p.Query case len(p.Route): return p.Route case len(p.Fixed): return p.Fixed case len(p.Form): return p.Form } // Copy everything into a param map, // order of priority is least to most trusted values := make(url.Values, numParams) // ?query string parameters are first for k, v := range p.Query { values[k] = append(values[k], v...) } // form parameters append for k, v := range p.Form { values[k] = append(values[k], v...) } // :/path parameters overwrite for k, v := range p.Route { values[k] = v } // fixed route parameters overwrite for k, v := range p.Fixed { values[k] = v } return values } func ParamsFilter(c *Controller, fc []Filter) { ParseParams(c.Params, c.Request) // Clean up from the request. defer func() { for _, tmpFile := range c.Params.tmpFiles { err := os.Remove(tmpFile.Name()) if err != nil { paramsLogger.Warn("ParamsFilter: Could not remove upload temp file:", err) } } }() fc[0](c, fc[1:]) } revel-1.0.0/params_test.go000066400000000000000000000113721370252312000154750ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package revel import ( "bytes" "fmt" "io/ioutil" "net/http" "net/url" "reflect" "testing" ) // Params: Testing Multipart forms const ( MultipartBoundary = "A" MultipartFormData = `--A Content-Disposition: form-data; name="text1" data1 --A Content-Disposition: form-data; name="text2" data2 --A Content-Disposition: form-data; name="text2" data3 --A Content-Disposition: form-data; name="file1"; filename="test.txt" Content-Type: text/plain content1 --A Content-Disposition: form-data; name="file2[]"; filename="test.txt" Content-Type: text/plain content2 --A Content-Disposition: form-data; name="file2[]"; filename="favicon.ico" Content-Type: image/x-icon xyz --A Content-Disposition: form-data; name="file3[0]"; filename="test.txt" Content-Type: text/plain content3 --A Content-Disposition: form-data; name="file3[1]"; filename="favicon.ico" Content-Type: image/x-icon zzz --A-- ` ) // The values represented by the form data. type fh struct { filename string content []byte } var ( expectedValues = map[string][]string{ "text1": {"data1"}, "text2": {"data2", "data3"}, } expectedFiles = map[string][]fh{ "file1": {fh{"test.txt", []byte("content1")}}, "file2[]": {fh{"test.txt", []byte("content2")}, fh{"favicon.ico", []byte("xyz")}}, "file3[0]": {fh{"test.txt", []byte("content3")}}, "file3[1]": {fh{"favicon.ico", []byte("zzz")}}, } ) func getMultipartRequest() *http.Request { req, _ := http.NewRequest("POST", "http://localhost/path", bytes.NewBufferString(MultipartFormData)) req.Header.Set( "Content-Type", fmt.Sprintf("multipart/form-data; boundary=%s", MultipartBoundary)) req.Header.Set( "Content-Length", fmt.Sprintf("%d", len(MultipartFormData))) return req } func BenchmarkParams(b *testing.B) { c := NewTestController(nil, showRequest) c.Params = &Params{} for i := 0; i < b.N; i++ { ParamsFilter(c, NilChain) } } func TestMultipartForm(t *testing.T) { c := NewTestController(nil, getMultipartRequest()) c.Params = &Params{} ParamsFilter(c, NilChain) if !reflect.DeepEqual(expectedValues, map[string][]string(c.Params.Values)) { t.Errorf("Param values: (expected) %v != %v (actual)", expectedValues, map[string][]string(c.Params.Values)) } actualFiles := make(map[string][]fh) for key, fileHeaders := range c.Params.Files { for _, fileHeader := range fileHeaders { file, _ := fileHeader.Open() content, _ := ioutil.ReadAll(file) actualFiles[key] = append(actualFiles[key], fh{fileHeader.Filename, content}) } } if !reflect.DeepEqual(expectedFiles, actualFiles) { t.Errorf("Param files: (expected) %v != %v (actual)", expectedFiles, actualFiles) } } func TestBind(t *testing.T) { params := Params{ Values: url.Values{ "x": {"5"}, }, } var x int params.Bind(&x, "x") if x != 5 { t.Errorf("Failed to bind x. Value: %d", x) } } func TestResolveAcceptLanguage(t *testing.T) { request := buildHTTPRequestWithAcceptLanguage("") if result := ResolveAcceptLanguage(request); result != nil { t.Errorf("Expected Accept-Language to resolve to an empty string but it was '%s'", result) } request = buildHTTPRequestWithAcceptLanguage("en-GB,en;q=0.8,nl;q=0.6") if result := ResolveAcceptLanguage(request); len(result) != 3 { t.Errorf("Unexpected Accept-Language values length of %d (expected %d)", len(result), 3) } else { if result[0].Language != "en-GB" { t.Errorf("Expected '%s' to be most qualified but instead it's '%s'", "en-GB", result[0].Language) } if result[1].Language != "en" { t.Errorf("Expected '%s' to be most qualified but instead it's '%s'", "en", result[1].Language) } if result[2].Language != "nl" { t.Errorf("Expected '%s' to be most qualified but instead it's '%s'", "nl", result[2].Language) } } request = buildHTTPRequestWithAcceptLanguage("en;q=0.8,nl;q=0.6,en-AU;q=malformed") if result := ResolveAcceptLanguage(request); len(result) != 3 { t.Errorf("Unexpected Accept-Language values length of %d (expected %d)", len(result), 3) } else { if result[0].Language != "en-AU" { t.Errorf("Expected '%s' to be most qualified but instead it's '%s'", "en-AU", result[0].Language) } } } func BenchmarkResolveAcceptLanguage(b *testing.B) { for i := 0; i < b.N; i++ { request := buildHTTPRequestWithAcceptLanguage("en-GB,en;q=0.8,nl;q=0.6,fr;q=0.5,de-DE;q=0.4,no-NO;q=0.4,ru;q=0.2") ResolveAcceptLanguage(request) } } func buildHTTPRequestWithAcceptLanguage(acceptLanguage string) *Request { request, _ := http.NewRequest("POST", "http://localhost/path", nil) request.Header.Set("Accept-Language", acceptLanguage) c := NewTestController(nil, request) return c.Request } revel-1.0.0/results.go000066400000000000000000000347031370252312000146570ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package revel import ( "bytes" "encoding/json" "encoding/xml" "errors" "fmt" "io" "io/ioutil" "net/http" "reflect" "strconv" "strings" "time" ) type Result interface { Apply(req *Request, resp *Response) } // ErrorResult structure used to handles all kinds of error codes (500, 404, ..). // It renders the relevant error page (errors/CODE.format, e.g. errors/500.json). // If RunMode is "dev", this results in a friendly error page. type ErrorResult struct { ViewArgs map[string]interface{} Error error } var resultsLog = RevelLog.New("section", "results") func (r ErrorResult) Apply(req *Request, resp *Response) { format := req.Format status := resp.Status if status == 0 { status = http.StatusInternalServerError } contentType := ContentTypeByFilename("xxx." + format) if contentType == DefaultFileContentType { contentType = "text/plain" } lang, _ := r.ViewArgs[CurrentLocaleViewArg].(string) // Get the error template. var err error templatePath := fmt.Sprintf("errors/%d.%s", status, format) tmpl, err := MainTemplateLoader.TemplateLang(templatePath, lang) // This func shows a plaintext error message, in case the template rendering // doesn't work. showPlaintext := func(err error) { PlaintextErrorResult{fmt.Errorf("Server Error:\n%s\n\n"+ "Additionally, an error occurred when rendering the error page:\n%s", r.Error, err)}.Apply(req, resp) } if tmpl == nil { if err == nil { err = fmt.Errorf("Couldn't find template %s", templatePath) } templateLog.Warn("Got an error rendering template", "error", err, "template", templatePath, "lang", lang) showPlaintext(err) return } // If it's not a revel error, wrap it in one. var revelError *Error switch e := r.Error.(type) { case *Error: revelError = e case error: revelError = &Error{ Title: "Server Error", Description: e.Error(), } } if revelError == nil { panic("no error provided") } if r.ViewArgs == nil { r.ViewArgs = make(map[string]interface{}) } r.ViewArgs["RunMode"] = RunMode r.ViewArgs["DevMode"] = DevMode r.ViewArgs["Error"] = revelError r.ViewArgs["Router"] = MainRouter resultsLog.Info("Rendering error template", "template", templatePath, "error", revelError) // Render it. var b bytes.Buffer err = tmpl.Render(&b, r.ViewArgs) // If there was an error, print it in plain text. if err != nil { templateLog.Warn("Got an error rendering template", "error", err, "template", templatePath, "lang", lang) showPlaintext(err) return } // need to check if we are on a websocket here // net/http panics if we write to a hijacked connection if req.Method == "WS" { if err := req.WebSocket.MessageSendJSON(fmt.Sprint(revelError)); err != nil { resultsLog.Error("Apply: Send failed", "error", err) } } else { resp.WriteHeader(status, contentType) if _, err := b.WriteTo(resp.GetWriter()); err != nil { resultsLog.Error("Apply: Response WriteTo failed:", "error", err) } } } type PlaintextErrorResult struct { Error error } // Apply method is used when the template loader or error template is not available. func (r PlaintextErrorResult) Apply(req *Request, resp *Response) { resp.WriteHeader(http.StatusInternalServerError, "text/plain; charset=utf-8") if _, err := resp.GetWriter().Write([]byte(r.Error.Error())); err != nil { resultsLog.Error("Apply: Write error:", "error", err) } } // RenderTemplateResult action methods returns this result to request // a template be rendered. type RenderTemplateResult struct { Template Template ViewArgs map[string]interface{} } func (r *RenderTemplateResult) Apply(req *Request, resp *Response) { // Handle panics when rendering templates. defer func() { if err := recover(); err != nil { resultsLog.Error("Apply: panic recovery", "error", err) PlaintextErrorResult{fmt.Errorf("Template Execution Panic in %s:\n%s", r.Template.Name(), err)}.Apply(req, resp) } }() chunked := Config.BoolDefault("results.chunked", false) // If it's a HEAD request, throw away the bytes. out := io.Writer(resp.GetWriter()) if req.Method == "HEAD" { out = ioutil.Discard } // In a prod mode, write the status, render, and hope for the best. // (In a dev mode, always render to a temporary buffer first to avoid having // error pages distorted by HTML already written) if chunked && !DevMode { resp.WriteHeader(http.StatusOK, "text/html; charset=utf-8") if err := r.renderOutput(out); err != nil { r.renderError(err, req, resp) } return } // Render the template into a temporary buffer, to see if there was an error // rendering the template. If not, then copy it into the response buffer. // Otherwise, template render errors may result in unpredictable HTML (and // would carry a 200 status code) b, err := r.ToBytes() if err != nil { r.renderError(err, req, resp) return } if !chunked { resp.Out.Header().Set("Content-Length", strconv.Itoa(b.Len())) } resp.WriteHeader(http.StatusOK, "text/html; charset=utf-8") if _, err := b.WriteTo(out); err != nil { resultsLog.Error("Apply: Response write failed", "error", err) } } // Return a byte array and or an error object if the template failed to render func (r *RenderTemplateResult) ToBytes() (b *bytes.Buffer, err error) { defer func() { if rerr := recover(); rerr != nil { resultsLog.Error("ApplyBytes: panic recovery", "recover-error", rerr) err = fmt.Errorf("Template Execution Panic in %s:\n%s", r.Template.Name(), rerr) } }() b = &bytes.Buffer{} if err = r.renderOutput(b); err == nil { if Config.BoolDefault("results.trim.html", false) { b = r.compressHtml(b) } } return } // Output the template to the writer, catch any panics and return as an error func (r *RenderTemplateResult) renderOutput(wr io.Writer) (err error) { defer func() { if rerr := recover(); rerr != nil { resultsLog.Error("ApplyBytes: panic recovery", "recover-error", rerr) err = fmt.Errorf("Template Execution Panic in %s:\n%s", r.Template.Name(), rerr) } }() err = r.Template.Render(wr, r.ViewArgs) return } // Trimming the HTML will do the following: // * Remove all leading & trailing whitespace on every line // * Remove all empty lines // * Attempt to keep formatting inside
 tags
//
// This is safe unless white-space: pre; is used in css for formatting.
// Since there is no way to detect that, you will have to keep trimming off in these cases.
func (r *RenderTemplateResult) compressHtml(b *bytes.Buffer) (b2 *bytes.Buffer) {

	// Allocate length of original buffer, so we can write everything without allocating again
	b2.Grow(b.Len())
	insidePre := false
	for {
		text, err := b.ReadString('\n')
		// Convert to lower case for finding 
 tags.
		tl := strings.ToLower(text)
		if strings.Contains(tl, "
") {
			insidePre = true
		}
		// Trim if not inside a 
 statement
		if !insidePre {
			// Cut trailing/leading whitespace
			text = strings.Trim(text, " \t\r\n")
			if len(text) > 0 {
				if _, err = b2.WriteString(text); err != nil {
					resultsLog.Error("Apply: ", "error", err)
				}
				if _, err = b2.WriteString("\n"); err != nil {
					resultsLog.Error("Apply: ", "error", err)
				}
			}
		} else {
			if _, err = b2.WriteString(text); err != nil {
				resultsLog.Error("Apply: ", "error", err)
			}
		}
		if strings.Contains(tl, "
") { insidePre = false } // We are finished if err != nil { break } } return } // Render the error in the response func (r *RenderTemplateResult) renderError(err error, req *Request, resp *Response) { compileError, found := err.(*Error) if !found { var templateContent []string templateName, line, description := ParseTemplateError(err) if templateName == "" { templateLog.Info("Cannot determine template name to render error", "error", err) templateName = r.Template.Name() templateContent = r.Template.Content() } else { lang, _ := r.ViewArgs[CurrentLocaleViewArg].(string) if tmpl, err := MainTemplateLoader.TemplateLang(templateName, lang); err == nil { templateContent = tmpl.Content() } else { templateLog.Info("Unable to retreive template ", "error", err) } } compileError = &Error{ Title: "Template Execution Error", Path: templateName, Description: description, Line: line, SourceLines: templateContent, } } resp.Status = 500 resultsLog.Errorf("render: Template Execution Error (in %s): %s", compileError.Path, compileError.Description) ErrorResult{r.ViewArgs, compileError}.Apply(req, resp) } type RenderHTMLResult struct { html string } func (r RenderHTMLResult) Apply(req *Request, resp *Response) { resp.WriteHeader(http.StatusOK, "text/html; charset=utf-8") if _, err := resp.GetWriter().Write([]byte(r.html)); err != nil { resultsLog.Error("Apply: Response write failed", "error", err) } } type RenderJSONResult struct { obj interface{} callback string } func (r RenderJSONResult) Apply(req *Request, resp *Response) { var b []byte var err error if Config.BoolDefault("results.pretty", false) { b, err = json.MarshalIndent(r.obj, "", " ") } else { b, err = json.Marshal(r.obj) } if err != nil { ErrorResult{Error: err}.Apply(req, resp) return } if r.callback == "" { resp.WriteHeader(http.StatusOK, "application/json; charset=utf-8") if _, err = resp.GetWriter().Write(b); err != nil { resultsLog.Error("Apply: Response write failed:", "error", err) } return } resp.WriteHeader(http.StatusOK, "application/javascript; charset=utf-8") if _, err = resp.GetWriter().Write([]byte(r.callback + "(")); err != nil { resultsLog.Error("Apply: Response write failed", "error", err) } if _, err = resp.GetWriter().Write(b); err != nil { resultsLog.Error("Apply: Response write failed", "error", err) } if _, err = resp.GetWriter().Write([]byte(");")); err != nil { resultsLog.Error("Apply: Response write failed", "error", err) } } type RenderXMLResult struct { obj interface{} } func (r RenderXMLResult) Apply(req *Request, resp *Response) { var b []byte var err error if Config.BoolDefault("results.pretty", false) { b, err = xml.MarshalIndent(r.obj, "", " ") } else { b, err = xml.Marshal(r.obj) } if err != nil { ErrorResult{Error: err}.Apply(req, resp) return } resp.WriteHeader(http.StatusOK, "application/xml; charset=utf-8") if _, err = resp.GetWriter().Write(b); err != nil { resultsLog.Error("Apply: Response write failed", "error", err) } } type RenderTextResult struct { text string } func (r RenderTextResult) Apply(req *Request, resp *Response) { resp.WriteHeader(http.StatusOK, "text/plain; charset=utf-8") if _, err := resp.GetWriter().Write([]byte(r.text)); err != nil { resultsLog.Error("Apply: Response write failed", "error", err) } } type ContentDisposition string var ( NoDisposition ContentDisposition = "" Attachment ContentDisposition = "attachment" Inline ContentDisposition = "inline" ) type BinaryResult struct { Reader io.Reader Name string Length int64 Delivery ContentDisposition ModTime time.Time } func (r *BinaryResult) Apply(req *Request, resp *Response) { if r.Delivery != NoDisposition { disposition := string(r.Delivery) if r.Name != "" { disposition += fmt.Sprintf(`; filename="%s"`, r.Name) } resp.Out.internalHeader.Set("Content-Disposition", disposition) } if resp.ContentType != "" { resp.Out.internalHeader.Set("Content-Type", resp.ContentType) } else { contentType := ContentTypeByFilename(r.Name) resp.Out.internalHeader.Set("Content-Type", contentType) } if content, ok := r.Reader.(io.ReadSeeker); ok && r.Length < 0 { // get the size from the stream // go1.6 compatibility change, go1.6 does not define constants io.SeekStart //if size, err := content.Seek(0, io.SeekEnd); err == nil { // if _, err = content.Seek(0, io.SeekStart); err == nil { if size, err := content.Seek(0, 2); err == nil { if _, err = content.Seek(0, 0); err == nil { r.Length = size } } } // Write stream writes the status code to the header as well if ws := resp.GetStreamWriter(); ws != nil { if err := ws.WriteStream(r.Name, r.Length, r.ModTime, r.Reader); err != nil { resultsLog.Error("Apply: Response write failed", "error", err) } } // Close the Reader if we can if v, ok := r.Reader.(io.Closer); ok { _ = v.Close() } } type RedirectToURLResult struct { url string } func (r *RedirectToURLResult) Apply(req *Request, resp *Response) { resp.Out.internalHeader.Set("Location", r.url) resp.WriteHeader(http.StatusFound, "") } type RedirectToActionResult struct { val interface{} args []interface{} } func (r *RedirectToActionResult) Apply(req *Request, resp *Response) { url, err := getRedirectURL(r.val, r.args) if err != nil { req.controller.Log.Error("Apply: Couldn't resolve redirect", "error", err) ErrorResult{Error: err}.Apply(req, resp) return } resp.Out.internalHeader.Set("Location", url) resp.WriteHeader(http.StatusFound, "") } func getRedirectURL(item interface{}, args []interface{}) (string, error) { // Handle strings if url, ok := item.(string); ok { return url, nil } // Handle funcs val := reflect.ValueOf(item) typ := reflect.TypeOf(item) if typ.Kind() == reflect.Func && typ.NumIn() > 0 { // Get the Controller Method recvType := typ.In(0) method := FindMethod(recvType, val) if method == nil { return "", errors.New("couldn't find method") } // Construct the action string (e.g. "Controller.Method") if recvType.Kind() == reflect.Ptr { recvType = recvType.Elem() } module := ModuleFromPath(recvType.PkgPath(), true) action := module.Namespace() + recvType.Name() + "." + method.Name // Fetch the action path to get the defaults pathData, found := splitActionPath(nil, action, true) if !found { return "", fmt.Errorf("Unable to redirect '%s', expected 'Controller.Action'", action) } // Build the map for the router to reverse // Unbind the arguments. argsByName := make(map[string]string) // Bind any static args first fixedParams := len(pathData.FixedParamsByName) methodType := pathData.TypeOfController.Method(pathData.MethodName) for i, argValue := range args { Unbind(argsByName, methodType.Args[i+fixedParams].Name, argValue) } actionDef := MainRouter.Reverse(action, argsByName) if actionDef == nil { return "", errors.New("no route for action " + action) } return actionDef.String(), nil } // Out of guesses return "", errors.New("didn't recognize type: " + typ.String()) } revel-1.0.0/results_test.go000066400000000000000000000040151370252312000157070ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package revel import ( "fmt" "net/http/httptest" "strings" "testing" ) // Added test case for redirection testing for strings func TestRedirect(t *testing.T) { startFakeBookingApp() redirect := RedirectToURLResult{fmt.Sprintf("/hotels/index/foo")} resp := httptest.NewRecorder() c := NewTestController(resp, showRequest) redirect.Apply(c.Request, c.Response) if resp.Header().Get("Location") != "/hotels/index/foo" { t.Errorf("Failed to set redirect header correctly. : %s", resp.Header().Get("Location")) } } // Test that the render response is as expected. func TestBenchmarkRender(t *testing.T) { startFakeBookingApp() resp := httptest.NewRecorder() c := NewTestController(resp, showRequest) if err := c.SetAction("Hotels", "Show"); err != nil { t.Errorf("SetAction failed: %s", err) } result := Hotels{c}.Show(3) result.Apply(c.Request, c.Response) if !strings.Contains(resp.Body.String(), "300 Main St.") { t.Errorf("Failed to find hotel address in action response:\n%s", resp.Body) } } func BenchmarkRenderChunked(b *testing.B) { startFakeBookingApp() resp := httptest.NewRecorder() resp.Body = nil c := NewTestController(resp, showRequest) if err := c.SetAction("Hotels", "Show"); err != nil { b.Errorf("SetAction failed: %s", err) } Config.SetOption("results.chunked", "true") b.ResetTimer() hotels := Hotels{c} for i := 0; i < b.N; i++ { hotels.Show(3).Apply(c.Request, c.Response) } } func BenchmarkRenderNotChunked(b *testing.B) { startFakeBookingApp() resp := httptest.NewRecorder() resp.Body = nil c := NewTestController(resp, showRequest) if err := c.SetAction("Hotels", "Show"); err != nil { b.Errorf("SetAction failed: %s", err) } Config.SetOption("results.chunked", "false") b.ResetTimer() hotels := Hotels{c} for i := 0; i < b.N; i++ { hotels.Show(3).Apply(c.Request, c.Response) } } revel-1.0.0/revel.go000066400000000000000000000244361370252312000142750ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package revel import ( "go/build" "net/http" "path/filepath" "strings" "encoding/json" "fmt" "github.com/revel/config" "github.com/revel/revel/logger" "github.com/revel/revel/model" ) const ( // RevelImportPath Revel framework import path RevelImportPath = "github.com/revel/revel" ) const ( TEST_MODE_FLAG = "testModeFlag" SPECIAL_USE_FLAG = "specialUseFlag" ) // App details var ( RevelConfig *model.RevelContainer AppName string // e.g. "sample" AppRoot string // e.g. "/app1" BasePath string // e.g. "$GOPATH/src/corp/sample" AppPath string // e.g. "$GOPATH/src/corp/sample/app" ViewsPath string // e.g. "$GOPATH/src/corp/sample/app/views" ImportPath string // e.g. "corp/sample" SourcePath string // e.g. "$GOPATH/src" Config *config.Context RunMode string // Application-defined (by default, "dev" or "prod") DevMode bool // if true, RunMode is a development mode. // Revel installation details RevelPath string // e.g. "$GOPATH/src/github.com/revel/revel" // Where to look for templates // Ordered by priority. (Earlier paths take precedence over later paths.) CodePaths []string // Code base directories, for modules and app TemplatePaths []string // Template path directories manually added // ConfPaths where to look for configurations // Config load order // 1. framework (revel/conf/*) // 2. application (conf/*) // 3. user supplied configs (...) - User configs can override/add any from above ConfPaths []string // Server config. // // Alert: This is how the app is configured, which may be different from // the current process reality. For example, if the app is configured for // port 9000, HTTPPort will always be 9000, even though in dev mode it is // run on a random port and proxied. HTTPPort int // e.g. 9000 HTTPAddr string // e.g. "", "127.0.0.1" HTTPSsl bool // e.g. true if using ssl HTTPSslCert string // e.g. "/path/to/cert.pem" HTTPSslKey string // e.g. "/path/to/key.pem" // All cookies dropped by the framework begin with this prefix. CookiePrefix string // Cookie domain CookieDomain string // Cookie flags CookieSecure bool CookieSameSite http.SameSite // Revel request access log, not exposed from package. // However output settings can be controlled from app.conf // True when revel engine has been initialized (Init has returned) Initialized bool // Private secretKey []byte // Key used to sign cookies. An empty key disables signing. packaged bool // If true, this is running from a pre-built package. initEventList = []EventHandler{} // Event handler list for receiving events packagePathMap = map[string]string{} // The map of the directories needed ) // Init initializes Revel -- it provides paths for getting around the app. // // Params: // mode - the run mode, which determines which app.conf settings are used. // importPath - the Go import path of the application. // srcPath - the path to the source directory, containing Revel and the app. // If not specified (""), then a functioning Go installation is required. func Init(inputmode, importPath, srcPath string) { RevelConfig = &model.RevelContainer{} // Ignore trailing slashes. ImportPath = strings.TrimRight(importPath, "/") SourcePath = srcPath RunMode = updateLog(inputmode) // If the SourcePath is not specified, find it using build.Import. var revelSourcePath string // may be different from the app source path if SourcePath == "" { revelSourcePath, SourcePath = findSrcPaths(importPath) BasePath = SourcePath } else { // If the SourcePath was specified, assume both Revel and the app are within it. SourcePath = filepath.Clean(SourcePath) revelSourcePath = filepath.Join(SourcePath, filepath.FromSlash(RevelImportPath)) BasePath = filepath.Join(SourcePath, filepath.FromSlash(importPath)) packaged = true } RevelPath = revelSourcePath //filepath.Join(revelSourcePath, filepath.FromSlash(RevelImportPath)) AppPath = filepath.Join(BasePath, "app") ViewsPath = filepath.Join(AppPath, "views") RevelLog.Info("Paths","revel", RevelPath,"base", BasePath,"app", AppPath,"views", ViewsPath) CodePaths = []string{AppPath} if ConfPaths == nil { ConfPaths = []string{} } // Config load order // 1. framework (revel/conf/*) // 2. application (conf/*) // 3. user supplied configs (...) - User configs can override/add any from above ConfPaths = append( []string{ filepath.Join(RevelPath, "conf"), filepath.Join(BasePath, "conf"), }, ConfPaths...) TemplatePaths = []string{ ViewsPath, filepath.Join(RevelPath, "templates"), } // Load app.conf var err error Config, err = config.LoadContext("app.conf", ConfPaths) if err != nil || Config == nil { RevelLog.Fatal("Failed to load app.conf:", "error", err) } // After application config is loaded update the logger updateLog(inputmode) // Configure properties from app.conf DevMode = Config.BoolDefault("mode.dev", false) HTTPPort = Config.IntDefault("http.port", 9000) HTTPAddr = Config.StringDefault("http.addr", "") HTTPSsl = Config.BoolDefault("http.ssl", false) HTTPSslCert = Config.StringDefault("http.sslcert", "") HTTPSslKey = Config.StringDefault("http.sslkey", "") if HTTPSsl { if HTTPSslCert == "" { RevelLog.Fatal("No http.sslcert provided.") } if HTTPSslKey == "" { RevelLog.Fatal("No http.sslkey provided.") } } AppName = Config.StringDefault("app.name", "(not set)") AppRoot = Config.StringDefault("app.root", "") CookiePrefix = Config.StringDefault("cookie.prefix", "REVEL") CookieDomain = Config.StringDefault("cookie.domain", "") CookieSecure = Config.BoolDefault("cookie.secure", HTTPSsl) switch Config.StringDefault("cookie.samesite", "") { case "lax": CookieSameSite = http.SameSiteLaxMode case "strict": CookieSameSite = http.SameSiteStrictMode case "none": CookieSameSite = http.SameSiteNoneMode default: CookieSameSite = http.SameSiteDefaultMode } if secretStr := Config.StringDefault("app.secret", ""); secretStr != "" { SetSecretKey([]byte(secretStr)) } RaiseEvent(REVEL_BEFORE_MODULES_LOADED, nil) loadModules() RaiseEvent(REVEL_AFTER_MODULES_LOADED, nil) Initialized = true RevelLog.Info("Initialized Revel", "Version", Version, "BuildDate", BuildDate, "MinimumGoVersion", MinimumGoVersion) } // The input mode can be as simple as "prod" or it can be a JSON string like // {"mode":"%s","testModeFlag":true} // When this function is called it returns the true "inputmode" extracted from the parameter // and it sets the log context appropriately func updateLog(inputmode string) (returnMode string) { if inputmode == "" { returnMode = config.DefaultSection return } else { returnMode = inputmode } // Check to see if the mode is a json object modemap := map[string]interface{}{} var testModeFlag, specialUseFlag bool if err := json.Unmarshal([]byte(inputmode), &modemap); err == nil { returnMode = modemap["mode"].(string) if testmode, found := modemap[TEST_MODE_FLAG]; found { testModeFlag, _ = testmode.(bool) } if specialUse, found := modemap[SPECIAL_USE_FLAG]; found { specialUseFlag, _ = specialUse.(bool) } if packagePathMapI, found := modemap["packagePathMap"]; found { for k,v := range packagePathMapI.(map[string]interface{}) { packagePathMap[k]=v.(string) } } } var newContext *config.Context // If the Config is nil, set the logger to minimal log messages by adding the option if Config == nil { newContext = config.NewContext() newContext.SetOption(TEST_MODE_FLAG, fmt.Sprint(true)) } else { // Ensure that the selected runmode appears in app.conf. // If empty string is passed as the mode, treat it as "DEFAULT" if !Config.HasSection(returnMode) { RevelLog.Fatal("app.conf: Run mode not found:","runmode", returnMode) } Config.SetSection(returnMode) newContext = Config } // Only set the testmode flag if it doesnt exist if _, found := newContext.Bool(TEST_MODE_FLAG); !found { newContext.SetOption(TEST_MODE_FLAG, fmt.Sprint(testModeFlag)) } if _, found := newContext.Bool(SPECIAL_USE_FLAG); !found { newContext.SetOption(SPECIAL_USE_FLAG, fmt.Sprint(specialUseFlag)) } appHandle := logger.InitializeFromConfig(BasePath, newContext) // Set all the log handlers setAppLog(AppLog, appHandle) return } // Set the secret key func SetSecretKey(newKey []byte) error { secretKey = newKey return nil } // ResolveImportPath returns the filesystem path for the given import path. // Returns an error if the import path could not be found. func ResolveImportPath(importPath string) (string, error) { if packaged { return filepath.Join(SourcePath, importPath), nil } if path,found := packagePathMap[importPath];found { return path, nil } modPkg, err := build.Import(importPath, RevelPath, build.FindOnly) if err != nil { return "", err } return modPkg.Dir, nil } // CheckInit method checks `revel.Initialized` if not initialized it panics func CheckInit() { if !Initialized { RevelLog.Panic("CheckInit: Revel has not been initialized!") } } // findSrcPaths uses the "go/build" package to find the source root for Revel // and the app. func findSrcPaths(importPath string) (revelSourcePath, appSourcePath string) { if importFsPath,found := packagePathMap[importPath];found { return packagePathMap[RevelImportPath],importFsPath } var ( gopaths = filepath.SplitList(build.Default.GOPATH) goroot = build.Default.GOROOT ) if len(gopaths) == 0 { RevelLog.Fatal("GOPATH environment variable is not set. " + "Please refer to http://golang.org/doc/code.html to configure your Go environment.") } if ContainsString(gopaths, goroot) { RevelLog.Fatalf("GOPATH (%s) must not include your GOROOT (%s). "+ "Please refer to http://golang.org/doc/code.html to configure your Go environment.", gopaths, goroot) } appPkg, err := build.Import(importPath, "", build.FindOnly) if err != nil { RevelLog.Panic("Failed to import "+importPath+" with error:", "error", err) } revelPkg, err := build.Import(RevelImportPath, appPkg.Dir, build.FindOnly) if err != nil { RevelLog.Fatal("Failed to find Revel with error:", "error", err) } return revelPkg.Dir, appPkg.Dir } revel-1.0.0/revel_hooks.go000066400000000000000000000050161370252312000154710ustar00rootroot00000000000000package revel import ( "sort" ) // The list of startup hooks type ( // The startup hooks structure RevelHook struct { order int // The order f func() // The function to call } RevelHooks []RevelHook ) var ( // The local instance of the list startupHooks RevelHooks // The instance of the list shutdownHooks RevelHooks ) // Called to run the hooks func (r RevelHooks) Run() { serverLogger.Infof("There is %d hooks need to run ...", len(r)) sort.Sort(r) for i, hook := range r { utilLog.Infof("Run the %d hook ...", i+1) hook.f() } } // Adds a new function hook, using the order func (r RevelHooks) Add(fn func(), order ...int) RevelHooks { o := 1 if len(order) > 0 { o = order[0] } return append(r, RevelHook{order: o, f: fn}) } // Sorting function func (slice RevelHooks) Len() int { return len(slice) } // Sorting function func (slice RevelHooks) Less(i, j int) bool { return slice[i].order < slice[j].order } // Sorting function func (slice RevelHooks) Swap(i, j int) { slice[i], slice[j] = slice[j], slice[i] } // OnAppStart registers a function to be run at app startup. // // The order you register the functions will be the order they are run. // You can think of it as a FIFO queue. // This process will happen after the config file is read // and before the server is listening for connections. // // Ideally, your application should have only one call to init() in the file init.go. // The reason being that the call order of multiple init() functions in // the same package is undefined. // Inside of init() call revel.OnAppStart() for each function you wish to register. // // Example: // // // from: yourapp/app/controllers/somefile.go // func InitDB() { // // do DB connection stuff here // } // // func FillCache() { // // fill a cache from DB // // this depends on InitDB having been run // } // // // from: yourapp/app/init.go // func init() { // // set up filters... // // // register startup functions // revel.OnAppStart(InitDB) // revel.OnAppStart(FillCache) // } // // This can be useful when you need to establish connections to databases or third-party services, // setup app components, compile assets, or any thing you need to do between starting Revel and accepting connections. // func OnAppStart(f func(), order ...int) { startupHooks = startupHooks.Add(f, order...) } // Called to add a new stop hook func OnAppStop(f func(), order ...int) { shutdownHooks = shutdownHooks.Add(f, order...) } revel-1.0.0/revel_test.go000066400000000000000000000003731370252312000153260ustar00rootroot00000000000000package revel import ( "net/http" ) func NewTestController(w http.ResponseWriter, r *http.Request) *Controller { context := NewGoContext(nil) context.Request.SetRequest(r) context.Response.SetResponse(w) c := NewController(context) return c } revel-1.0.0/router.go000066400000000000000000000704751370252312000145040ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package revel import ( "encoding/csv" "fmt" "io" "io/ioutil" "net/url" "path/filepath" "regexp" "strings" "os" "sync" "github.com/revel/pathtree" "github.com/revel/revel/logger" "errors" ) const ( httpStatusCode = "404" ) type Route struct { ModuleSource *Module // Module name of route Method string // e.g. GET Path string // e.g. /app/:id Action string // e.g. "Application.ShowApp", "404" ControllerNamespace string // e.g. "testmodule.", ControllerName string // e.g. "Application", "" MethodName string // e.g. "ShowApp", "" FixedParams []string // e.g. "arg1","arg2","arg3" (CSV formatting) TreePath string // e.g. "/GET/app/:id" TypeOfController *ControllerType // The controller type (if route is not wild carded) routesPath string // e.g. /Users/robfig/gocode/src/myapp/conf/routes line int // e.g. 3 } type RouteMatch struct { Action string // e.g. 404 ControllerName string // e.g. Application MethodName string // e.g. ShowApp FixedParams []string Params map[string][]string // e.g. {id: 123} TypeOfController *ControllerType // The controller type ModuleSource *Module // The module } type ActionPathData struct { Key string // The unique key ControllerNamespace string // The controller namespace ControllerName string // The controller name MethodName string // The method name Action string // The action ModuleSource *Module // The module Route *Route // The route FixedParamsByName map[string]string // The fixed parameters TypeOfController *ControllerType // The controller type } var ( // Used to store decoded action path mappings actionPathCacheMap = map[string]*ActionPathData{} // Used to prevent concurrent writes to map actionPathCacheLock = sync.Mutex{} // The path returned if not found notFound = &RouteMatch{Action: "404"} ) var routerLog = RevelLog.New("section", "router") func init() { AddInitEventHandler(func(typeOf Event, value interface{}) (responseOf EventResponse) { // Add in an if typeOf == ROUTE_REFRESH_REQUESTED { // Clear the actionPathCacheMap cache actionPathCacheLock.Lock() defer actionPathCacheLock.Unlock() actionPathCacheMap = map[string]*ActionPathData{} } return }) } // NewRoute prepares the route to be used in matching. func NewRoute(moduleSource *Module, method, path, action, fixedArgs, routesPath string, line int) (r *Route) { // Handle fixed arguments argsReader := strings.NewReader(string(namespaceReplace([]byte(fixedArgs), moduleSource))) csvReader := csv.NewReader(argsReader) csvReader.TrimLeadingSpace = true fargs, err := csvReader.Read() if err != nil && err != io.EOF { routerLog.Error("NewRoute: Invalid fixed parameters for string ", "error", err, "fixedargs", fixedArgs) } r = &Route{ ModuleSource: moduleSource, Method: strings.ToUpper(method), Path: path, Action: string(namespaceReplace([]byte(action), moduleSource)), FixedParams: fargs, TreePath: treePath(strings.ToUpper(method), path), routesPath: routesPath, line: line, } // URL pattern if !strings.HasPrefix(r.Path, "/") { routerLog.Error("NewRoute: Absolute URL required.") return } // Ignore the not found status code if action != httpStatusCode { routerLog.Debugf("NewRoute: New splitActionPath path:%s action:%s", path, action) pathData, found := splitActionPath(&ActionPathData{ModuleSource: moduleSource, Route: r}, r.Action, false) if found { if pathData.TypeOfController != nil { // Assign controller type to avoid looking it up based on name r.TypeOfController = pathData.TypeOfController // Create the fixed parameters if l := len(pathData.Route.FixedParams); l > 0 && len(pathData.FixedParamsByName) == 0 { methodType := pathData.TypeOfController.Method(pathData.MethodName) if methodType != nil { pathData.FixedParamsByName = make(map[string]string, l) for i, argValue := range pathData.Route.FixedParams { Unbind(pathData.FixedParamsByName, methodType.Args[i].Name, argValue) } } else { routerLog.Panicf("NewRoute: Method %s not found for controller %s", pathData.MethodName, pathData.ControllerName) } } } r.ControllerNamespace = pathData.ControllerNamespace r.ControllerName = pathData.ControllerName r.ModuleSource = pathData.ModuleSource r.MethodName = pathData.MethodName // The same action path could be used for multiple routes (like the Static.Serve) } else { routerLog.Panicf("NewRoute: Failed to find controller for route path action %s \n%#v\n", path+"?"+r.Action, actionPathCacheMap) } } return } func (route *Route) ActionPath() string { return route.ModuleSource.Namespace() + route.ControllerName } func treePath(method, path string) string { if method == "*" { method = ":METHOD" } return "/" + method + path } type Router struct { Routes []*Route Tree *pathtree.Node Module string // The module the route is associated with path string // path to the routes file } func (router *Router) Route(req *Request) (routeMatch *RouteMatch) { // Override method if set in header if method := req.GetHttpHeader("X-HTTP-Method-Override"); method != "" && req.Method == "POST" { req.Method = method } leaf, expansions := router.Tree.Find(treePath(req.Method, req.GetPath())) if leaf == nil { return nil } // Create a map of the route parameters. var params url.Values if len(expansions) > 0 { params = make(url.Values) for i, v := range expansions { params[leaf.Wildcards[i]] = []string{v} } } var route *Route var controllerName, methodName string // The leaf value is now a list of possible routes to match, only a controller routeList := leaf.Value.([]*Route) var typeOfController *ControllerType //INFO.Printf("Found route for path %s %#v", req.URL.Path, len(routeList)) for index := range routeList { route = routeList[index] methodName = route.MethodName // Special handling for explicit 404's. if route.Action == httpStatusCode { route = nil break } // If wildcard match on method name use the method name from the params if methodName[0] == ':' { if methodKey, found := params[methodName[1:]]; found && len(methodKey) > 0 { methodName = strings.ToLower(methodKey[0]) } else { routerLog.Fatal("Route: Failure to find method name in parameters", "params", params, "methodName", methodName) } } // If the action is variablized, replace into it with the captured args. controllerName = route.ControllerName if controllerName[0] == ':' { controllerName = strings.ToLower(params[controllerName[1:]][0]) if typeOfController = route.ModuleSource.ControllerByName(controllerName, methodName); typeOfController != nil { break } } else { typeOfController = route.TypeOfController break } route = nil } if route == nil { routeMatch = notFound } else { routeMatch = &RouteMatch{ ControllerName: route.ControllerNamespace + controllerName, MethodName: methodName, Params: params, FixedParams: route.FixedParams, TypeOfController: typeOfController, ModuleSource: route.ModuleSource, } } return } // Refresh re-reads the routes file and re-calculates the routing table. // Returns an error if a specified action could not be found. func (router *Router) Refresh() (err *Error) { RaiseEvent(ROUTE_REFRESH_REQUESTED, nil) router.Routes, err = parseRoutesFile(appModule, router.path, "", true) RaiseEvent(ROUTE_REFRESH_COMPLETED, nil) if err != nil { return } err = router.updateTree() return } func (router *Router) updateTree() *Error { router.Tree = pathtree.New() pathMap := map[string][]*Route{} allPathsOrdered := []string{} // It is possible for some route paths to overlap // based on wildcard matches, // TODO when pathtree is fixed (made to be smart enough to not require a predefined intake order) keeping the routes in order is not necessary for _, route := range router.Routes { if _, found := pathMap[route.TreePath]; !found { pathMap[route.TreePath] = append(pathMap[route.TreePath], route) allPathsOrdered = append(allPathsOrdered, route.TreePath) } else { pathMap[route.TreePath] = append(pathMap[route.TreePath], route) } } for _, path := range allPathsOrdered { routeList := pathMap[path] err := router.Tree.Add(path, routeList) // Allow GETs to respond to HEAD requests. if err == nil && routeList[0].Method == "GET" { err = router.Tree.Add(treePath("HEAD", routeList[0].Path), routeList) } // Error adding a route to the pathtree. if err != nil { return routeError(err, path, fmt.Sprintf("%#v", routeList), routeList[0].line) } } return nil } // Returns the controller namespace and name, action and module if found from the actionPath specified func splitActionPath(actionPathData *ActionPathData, actionPath string, useCache bool) (pathData *ActionPathData, found bool) { actionPath = strings.ToLower(actionPath) if pathData, found = actionPathCacheMap[actionPath]; found && useCache { return } var ( controllerNamespace, controllerName, methodName, action string foundModuleSource *Module typeOfController *ControllerType log = routerLog.New("actionPath", actionPath) ) actionSplit := strings.Split(actionPath, ".") if actionPathData != nil { foundModuleSource = actionPathData.ModuleSource } if len(actionSplit) == 2 { controllerName, methodName = strings.ToLower(actionSplit[0]), strings.ToLower(actionSplit[1]) if i := strings.Index(methodName, "("); i > 0 { methodName = methodName[:i] } log = log.New("controller", controllerName, "method", methodName) log.Debug("splitActionPath: Check for namespace") if i := strings.Index(controllerName, namespaceSeperator); i > -1 { controllerNamespace = controllerName[:i+1] if moduleSource, found := ModuleByName(controllerNamespace[:len(controllerNamespace)-1]); found { foundModuleSource = moduleSource controllerNamespace = moduleSource.Namespace() log = log.New("namespace",controllerNamespace) log.Debug("Found module namespace") } else { log.Warnf("splitActionPath: Unable to find module %s for action: %s", controllerNamespace[:len(controllerNamespace)-1], actionPath) } controllerName = controllerName[i+1:] log = log.New("controllerShortName",controllerName) // Check for the type of controller typeOfController = foundModuleSource.ControllerByName(controllerName, methodName) found = typeOfController != nil log.Debug("Found controller","found",found,"type",typeOfController) } else if controllerName[0] != ':' { // First attempt to find the controller in the module source if foundModuleSource != nil { typeOfController = foundModuleSource.ControllerByName(controllerName, methodName) if typeOfController != nil { controllerNamespace = typeOfController.Namespace } } log.Info("Found controller for path", "controllerType", typeOfController) if typeOfController == nil { // Check to see if we can determine the controller from only the controller name // an actionPath without a moduleSource will only come from // Scan through the controllers matchName := controllerName for key, controller := range controllers { // Strip away the namespace from the controller. to be match regularName := key if i := strings.Index(key, namespaceSeperator); i > -1 { regularName = regularName[i+1:] } if regularName == matchName { // Found controller match typeOfController = controller controllerNamespace = typeOfController.Namespace controllerName = typeOfController.ShortName() foundModuleSource = typeOfController.ModuleSource found = true break } } } else { found = true } } else { // If wildcard assign the route the controller namespace found controllerNamespace = actionPathData.ModuleSource.Name + namespaceSeperator foundModuleSource = actionPathData.ModuleSource found = true } action = actionSplit[1] } else { foundPaths := "" for path := range actionPathCacheMap { foundPaths += path + "," } log.Warnf("splitActionPath: Invalid action path %s found paths %s", actionPath, foundPaths) found = false } // Make sure no concurrent map writes occur if found { actionPathCacheLock.Lock() defer actionPathCacheLock.Unlock() if actionPathData != nil { actionPathData.ControllerNamespace = controllerNamespace actionPathData.ControllerName = controllerName actionPathData.MethodName = methodName actionPathData.Action = action actionPathData.ModuleSource = foundModuleSource actionPathData.TypeOfController = typeOfController } else { actionPathData = &ActionPathData{ ControllerNamespace: controllerNamespace, ControllerName: controllerName, MethodName: methodName, Action: action, ModuleSource: foundModuleSource, TypeOfController: typeOfController, } } actionPathData.TypeOfController = foundModuleSource.ControllerByName(controllerName, "") if actionPathData.TypeOfController == nil && actionPathData.ControllerName[0] != ':' { log.Warnf("splitActionPath: No controller found for %s %#v", foundModuleSource.Namespace()+controllerName, controllers) } pathData = actionPathData if pathData.Route != nil && len(pathData.Route.FixedParams) > 0 { // If there are fixed params on the route then add them to the path // This will give it a unique path and it should still be usable for a reverse lookup provided the name is matchable // for example // GET /test/ Application.Index("Test", "Test2") // {{url "Application.Index(test,test)" }} // should be parseable actionPath = actionPath + "(" + strings.ToLower(strings.Join(pathData.Route.FixedParams, ",")) + ")" } if actionPathData.Route != nil { log.Debugf("splitActionPath: Split Storing recognized action path %s for route %#v ", actionPath, actionPathData.Route) } pathData.Key = actionPath actionPathCacheMap[actionPath] = pathData if !strings.Contains(actionPath, namespaceSeperator) && pathData.TypeOfController != nil { actionPathCacheMap[strings.ToLower(pathData.TypeOfController.Namespace)+actionPath] = pathData log.Debugf("splitActionPath: Split Storing recognized action path %s for route %#v ", strings.ToLower(pathData.TypeOfController.Namespace)+actionPath, actionPathData.Route) } } return } // parseRoutesFile reads the given routes file and returns the contained routes. func parseRoutesFile(moduleSource *Module, routesPath, joinedPath string, validate bool) ([]*Route, *Error) { contentBytes, err := ioutil.ReadFile(routesPath) if err != nil { return nil, &Error{ Title: "Failed to load routes file", Description: err.Error(), } } return parseRoutes(moduleSource, routesPath, joinedPath, string(contentBytes), validate) } // parseRoutes reads the content of a routes file into the routing table. func parseRoutes(moduleSource *Module, routesPath, joinedPath, content string, validate bool) ([]*Route, *Error) { var routes []*Route // For each line.. for n, line := range strings.Split(content, "\n") { line = strings.TrimSpace(line) if len(line) == 0 || line[0] == '#' { continue } const modulePrefix = "module:" // Handle included routes from modules. // e.g. "module:testrunner" imports all routes from that module. if strings.HasPrefix(line, modulePrefix) { moduleRoutes, err := getModuleRoutes(line[len(modulePrefix):], joinedPath, validate) if err != nil { return nil, routeError(err, routesPath, content, n) } routes = append(routes, moduleRoutes...) continue } // A single route method, path, action, fixedArgs, found := parseRouteLine(line) if !found { continue } // this will avoid accidental double forward slashes in a route. // this also avoids pathtree freaking out and causing a runtime panic // because of the double slashes if strings.HasSuffix(joinedPath, "/") && strings.HasPrefix(path, "/") { joinedPath = joinedPath[0 : len(joinedPath)-1] } path = strings.Join([]string{AppRoot, joinedPath, path}, "") // This will import the module routes under the path described in the // routes file (joinedPath param). e.g. "* /jobs module:jobs" -> all // routes' paths will have the path /jobs prepended to them. // See #282 for more info if method == "*" && strings.HasPrefix(action, modulePrefix) { moduleRoutes, err := getModuleRoutes(action[len(modulePrefix):], path, validate) if err != nil { return nil, routeError(err, routesPath, content, n) } routes = append(routes, moduleRoutes...) continue } route := NewRoute(moduleSource, method, path, action, fixedArgs, routesPath, n) routes = append(routes, route) if validate { if err := validateRoute(route); err != nil { return nil, routeError(err, routesPath, content, n) } } } return routes, nil } // validateRoute checks that every specified action exists. func validateRoute(route *Route) error { // Skip 404s if route.Action == httpStatusCode { return nil } // Skip variable routes. if route.ControllerName[0] == ':' || route.MethodName[0] == ':' { return nil } // Precheck to see if controller exists if _, found := controllers[route.ControllerNamespace+route.ControllerName]; !found { // Scan through controllers to find module for _, c := range controllers { controllerName := strings.ToLower(c.Type.Name()) if controllerName == route.ControllerName { route.ControllerNamespace = c.ModuleSource.Name + namespaceSeperator routerLog.Warn("validateRoute: Matched empty namespace route for %s to this namespace %s for the route %s", controllerName, c.ModuleSource.Name, route.Path) } } } // TODO need to check later // does it do only validation or validation and instantiate the controller. var c Controller return c.SetTypeAction(route.ControllerNamespace+route.ControllerName, route.MethodName, route.TypeOfController) } // routeError adds context to a simple error message. func routeError(err error, routesPath, content string, n int) *Error { if revelError, ok := err.(*Error); ok { return revelError } // Load the route file content if necessary if content == "" { if contentBytes, er := ioutil.ReadFile(routesPath); er != nil { routerLog.Error("routeError: Failed to read route file ", "file", routesPath, "error", er) } else { content = string(contentBytes) } } return &Error{ Title: "Route validation error", Description: err.Error(), Path: routesPath, Line: n + 1, SourceLines: strings.Split(content, "\n"), Stack: fmt.Sprintf("%s", logger.NewCallStack()), } } // getModuleRoutes loads the routes file for the given module and returns the // list of routes. func getModuleRoutes(moduleName, joinedPath string, validate bool) (routes []*Route, err *Error) { // Look up the module. It may be not found due to the common case of e.g. the // testrunner module being active only in dev mode. module, found := ModuleByName(moduleName) if !found { routerLog.Debug("getModuleRoutes: Skipping routes for inactive module", "module", moduleName) return nil, nil } routePath := filepath.Join(module.Path, "conf", "routes") if _, e := os.Stat(routePath); e == nil { routes, err = parseRoutesFile(module, routePath, joinedPath, validate) } if err == nil { for _, route := range routes { route.ModuleSource = module } } return routes, err } // Groups: // 1: method // 4: path // 5: action // 6: fixedargs var routePattern = regexp.MustCompile( "(?i)^(GET|POST|PUT|DELETE|PATCH|OPTIONS|HEAD|WS|PROPFIND|MKCOL|COPY|MOVE|PROPPATCH|LOCK|UNLOCK|TRACE|PURGE|\\*)" + "[(]?([^)]*)(\\))?[ \t]+" + "(.*/[^ \t]*)[ \t]+([^ \t(]+)" + `\(?([^)]*)\)?[ \t]*$`) func parseRouteLine(line string) (method, path, action, fixedArgs string, found bool) { matches := routePattern.FindStringSubmatch(line) if matches == nil { return } method, path, action, fixedArgs = matches[1], matches[4], matches[5], matches[6] found = true return } func NewRouter(routesPath string) *Router { return &Router{ Tree: pathtree.New(), path: routesPath, } } type ActionDefinition struct { Host, Method, URL, Action string Star bool Args map[string]string } func (a *ActionDefinition) String() string { return a.URL } func (router *Router) Reverse(action string, argValues map[string]string) (ad *ActionDefinition) { ad, err := router.ReverseError(action,argValues,nil) if err!=nil { routerLog.Error("splitActionPath: Failed to find reverse route", "action", action, "arguments", argValues) } return ad } func (router *Router) ReverseError(action string, argValues map[string]string, req *Request) (ad *ActionDefinition, err error) { var log logger.MultiLogger if req!=nil { log = req.controller.Log.New("action", action) } else { log = routerLog.New("action", action) } pathData, found := splitActionPath(nil, action, true) if !found { log.Error("splitActionPath: Failed to find reverse route", "action", action, "arguments", argValues) return } log.Debug("Checking for route", "pathdataRoute", pathData.Route) if pathData.Route == nil { var possibleRoute *Route // If the route is nil then we need to go through the routes to find the first matching route // from this controllers namespace, this is likely a wildcard route match for _, route := range router.Routes { // Skip routes that are not wild card or empty if route.ControllerName == "" || route.MethodName == "" { continue } if route.ModuleSource == pathData.ModuleSource && route.ControllerName[0] == ':' { // Wildcard match in same module space pathData.Route = route break } else if route.ActionPath() == pathData.ModuleSource.Namespace()+pathData.ControllerName && (route.Method[0] == ':' || route.Method == pathData.MethodName) { // Action path match pathData.Route = route break } else if route.ControllerName == pathData.ControllerName && (route.Method[0] == ':' || route.Method == pathData.MethodName) { // Controller name match possibleRoute = route } } if pathData.Route == nil && possibleRoute != nil { pathData.Route = possibleRoute routerLog.Warnf("Reverse: For a url reverse a match was based on %s matched path to route %#v ", action, possibleRoute) } if pathData.Route != nil { routerLog.Debugf("Reverse: Reverse Storing recognized action path %s for route %#v\n", action, pathData.Route) } } // Likely unknown route because of a wildcard, perform manual lookup if pathData.Route != nil { route := pathData.Route // If the controller or method are wildcards we need to populate the argValues controllerWildcard := route.ControllerName[0] == ':' methodWildcard := route.MethodName[0] == ':' // populate route arguments with the names if controllerWildcard { argValues[route.ControllerName[1:]] = pathData.ControllerName } if methodWildcard { argValues[route.MethodName[1:]] = pathData.MethodName } // In theory all routes should be defined and pre-populated, the route controllers may not be though // with wildcard routes if pathData.TypeOfController == nil { if controllerWildcard || methodWildcard { if controller := ControllerTypeByName(pathData.ControllerNamespace+pathData.ControllerName, route.ModuleSource); controller != nil { // Wildcard match boundary pathData.TypeOfController = controller // See if the path exists in the module based } else { log.Errorf("Reverse: Controller %s not found in reverse lookup", pathData.ControllerNamespace+pathData.ControllerName) err = errors.New("Reverse: Controller not found in reverse lookup") return } } } if pathData.TypeOfController == nil { log.Errorf("Reverse: Controller %s not found in reverse lookup", pathData.ControllerNamespace+pathData.ControllerName) err = errors.New("Reverse: Controller not found in reverse lookup") return } var ( queryValues = make(url.Values) pathElements = strings.Split(route.Path, "/") ) for i, el := range pathElements { if el == "" || (el[0] != ':' && el[0] != '*') { continue } val, ok := pathData.FixedParamsByName[el[1:]] if !ok { val, ok = argValues[el[1:]] } if !ok { val = "" log.Error("Reverse: reverse route missing route argument ", "argument", el[1:]) panic("Check stack") err = errors.New("Missing route arguement") return } pathElements[i] = val delete(argValues, el[1:]) continue } // Add any args that were not inserted into the path into the query string. for k, v := range argValues { queryValues.Set(k, v) } // Calculate the final URL and Method urlPath := strings.Join(pathElements, "/") if len(queryValues) > 0 { urlPath += "?" + queryValues.Encode() } method := route.Method star := false if route.Method == "*" { method = "GET" star = true } log.Infof("Reversing action %s to %s Using Route %#v",action,urlPath,pathData.Route) ad = &ActionDefinition{ URL: urlPath, Method: method, Star: star, Action: action, Args: argValues, Host: "TODO", } return } routerLog.Error("Reverse: Failed to find controller for reverse route", "action", action, "arguments", argValues) err = errors.New("Reverse: Failed to find controller for reverse route") return } func RouterFilter(c *Controller, fc []Filter) { // Figure out the Controller/Action route := MainRouter.Route(c.Request) if route == nil { c.Result = c.NotFound("No matching route found: " + c.Request.GetRequestURI()) return } // The route may want to explicitly return a 404. if route.Action == httpStatusCode { c.Result = c.NotFound("(intentionally)") return } // Set the action. if err := c.SetTypeAction(route.ControllerName, route.MethodName, route.TypeOfController); err != nil { c.Result = c.NotFound(err.Error()) return } // Add the route and fixed params to the Request Params. c.Params.Route = route.Params // Assign logger if from module if c.Type.ModuleSource != nil && c.Type.ModuleSource != appModule { c.Log = c.Type.ModuleSource.Log.New("ip", c.ClientIP, "path", c.Request.URL.Path, "method", c.Request.Method) } // Add the fixed parameters mapped by name. // TODO: Pre-calculate this mapping. for i, value := range route.FixedParams { if c.Params.Fixed == nil { c.Params.Fixed = make(url.Values) } if i < len(c.MethodType.Args) { arg := c.MethodType.Args[i] c.Params.Fixed.Set(arg.Name, value) } else { routerLog.Warn("RouterFilter: Too many parameters to action", "action", route.Action, "value", value) break } } fc[0](c, fc[1:]) } // HTTPMethodOverride overrides allowed http methods via form or browser param func HTTPMethodOverride(c *Controller, fc []Filter) { // An array of HTTP verbs allowed. verbs := []string{"POST", "PUT", "PATCH", "DELETE"} method := strings.ToUpper(c.Request.Method) if method == "POST" { param := "" if f, err := c.Request.GetForm(); err == nil { param = strings.ToUpper(f.Get("_method")) } if len(param) > 0 { override := false // Check if param is allowed for _, verb := range verbs { if verb == param { override = true break } } if override { c.Request.Method = param } else { c.Response.Status = 405 c.Result = c.RenderError(&Error{ Title: "Method not allowed", Description: "Method " + param + " is not allowed (valid: " + strings.Join(verbs, ", ") + ")", }) return } } } fc[0](c, fc[1:]) // Execute the next filter stage. } func init() { OnAppStart(func() { MainRouter = NewRouter(filepath.Join(BasePath, "conf", "routes")) err := MainRouter.Refresh() if MainWatcher != nil && Config.BoolDefault("watch.routes", true) { MainWatcher.Listen(MainRouter, MainRouter.path) } else if err != nil { // Not in dev mode and Route loading failed, we should crash. routerLog.Panic("init: router initialize error", "error", err) } }) } revel-1.0.0/router_test.go000066400000000000000000000400471370252312000155330ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package revel import ( "fmt" "net/http" "net/url" "strings" "testing" ) // Data-driven tests that check that a given routes-file line translates into // the expected Route object. var routeTestCases = map[string]*Route{ "get / Application.Index": { Method: "GET", Path: "/", Action: "Application.Index", FixedParams: []string{}, }, "post /app/:id Application.SaveApp": { Method: "POST", Path: "/app/:id", Action: "Application.SaveApp", FixedParams: []string{}, }, "get /app/ Application.List": { Method: "GET", Path: "/app/", Action: "Application.List", FixedParams: []string{}, }, `get /app/:appId/ Application.Show`: { Method: "GET", Path: `/app/:appId/`, Action: "Application.Show", FixedParams: []string{}, }, `get /app-wild/*appId/ Application.WildShow`: { Method: "GET", Path: `/app-wild/*appId/`, Action: "Application.WildShow", FixedParams: []string{}, }, `GET /public/:filepath Static.Serve("public")`: { Method: "GET", Path: "/public/:filepath", Action: "Static.Serve", FixedParams: []string{ "public", }, }, `GET /javascript/:filepath Static.Serve("public/js")`: { Method: "GET", Path: "/javascript/:filepath", Action: "Static.Serve", FixedParams: []string{ "public", }, }, "* /apps/:id/:action Application.:action": { Method: "*", Path: "/apps/:id/:action", Action: "Application.:action", FixedParams: []string{}, }, "* /:controller/:action :controller.:action": { Method: "*", Path: "/:controller/:action", Action: ":controller.:action", FixedParams: []string{}, }, `GET / Application.Index("Test", "Test2")`: { Method: "GET", Path: "/", Action: "Application.Index", FixedParams: []string{ "Test", "Test2", }, }, } // Run the test cases above. func TestComputeRoute(t *testing.T) { for routeLine, expected := range routeTestCases { method, path, action, fixedArgs, found := parseRouteLine(routeLine) if !found { t.Error("Failed to parse route line:", routeLine) continue } actual := NewRoute(appModule, method, path, action, fixedArgs, "", 0) eq(t, "Method", actual.Method, expected.Method) eq(t, "Path", actual.Path, expected.Path) eq(t, "Action", actual.Action, expected.Action) if t.Failed() { t.Fatal("Failed on route:", routeLine) } } } // Router Tests const TestRoutes = ` # This is a comment GET / Application.Index GET /test/ Application.Index("Test", "Test2") GET /app/:id/ Application.Show GET /app-wild/*id/ Application.WildShow POST /app/:id Application.Save PATCH /app/:id/ Application.Update PROPFIND /app/:id Application.WebDevMethodPropFind MKCOL /app/:id Application.WebDevMethodMkCol COPY /app/:id Application.WebDevMethodCopy MOVE /app/:id Application.WebDevMethodMove PROPPATCH /app/:id Application.WebDevMethodPropPatch LOCK /app/:id Application.WebDevMethodLock UNLOCK /app/:id Application.WebDevMethodUnLock TRACE /app/:id Application.WebDevMethodTrace PURGE /app/:id Application.CacheMethodPurge GET /javascript/:filepath App\Static.Serve("public/js") GET /public/*filepath Static.Serve("public") * /:controller/:action :controller.:action GET /favicon.ico 404 ` var routeMatchTestCases = map[*http.Request]*RouteMatch{ { Method: "GET", URL: &url.URL{Path: "/"}, }: { ControllerName: "application", MethodName: "Index", FixedParams: []string{}, Params: map[string][]string{}, }, { Method: "GET", URL: &url.URL{Path: "/test/"}, }: { ControllerName: "application", MethodName: "Index", FixedParams: []string{"Test", "Test2"}, Params: map[string][]string{}, }, { Method: "GET", URL: &url.URL{Path: "/app/123"}, }: { ControllerName: "application", MethodName: "Show", FixedParams: []string{}, Params: map[string][]string{"id": {"123"}}, }, { Method: "PATCH", URL: &url.URL{Path: "/app/123"}, }: { ControllerName: "application", MethodName: "Update", FixedParams: []string{}, Params: map[string][]string{"id": {"123"}}, }, { Method: "POST", URL: &url.URL{Path: "/app/123"}, }: { ControllerName: "application", MethodName: "Save", FixedParams: []string{}, Params: map[string][]string{"id": {"123"}}, }, { Method: "GET", URL: &url.URL{Path: "/app/123/"}, }: { ControllerName: "application", MethodName: "Show", FixedParams: []string{}, Params: map[string][]string{"id": {"123"}}, }, { Method: "GET", URL: &url.URL{Path: "/public/css/style.css"}, }: { ControllerName: "static", MethodName: "Serve", FixedParams: []string{"public"}, Params: map[string][]string{"filepath": {"css/style.css"}}, }, { Method: "GET", URL: &url.URL{Path: "/javascript/sessvars.js"}, }: { ControllerName: "static", MethodName: "Serve", FixedParams: []string{"public/js"}, Params: map[string][]string{"filepath": {"sessvars.js"}}, }, { Method: "GET", URL: &url.URL{Path: "/Implicit/Route"}, }: { ControllerName: "implicit", MethodName: "Route", FixedParams: []string{}, Params: map[string][]string{ "METHOD": {"GET"}, "controller": {"Implicit"}, "action": {"Route"}, }, }, { Method: "GET", URL: &url.URL{Path: "/favicon.ico"}, }: { ControllerName: "", MethodName: "", Action: "404", FixedParams: []string{}, Params: map[string][]string{}, }, { Method: "POST", URL: &url.URL{Path: "/app/123"}, Header: http.Header{"X-Http-Method-Override": []string{"PATCH"}}, }: { ControllerName: "application", MethodName: "Update", FixedParams: []string{}, Params: map[string][]string{"id": {"123"}}, }, { Method: "GET", URL: &url.URL{Path: "/app/123"}, Header: http.Header{"X-Http-Method-Override": []string{"PATCH"}}, }: { ControllerName: "application", MethodName: "Show", FixedParams: []string{}, Params: map[string][]string{"id": {"123"}}, }, { Method: "PATCH", URL: &url.URL{Path: "/app/123"}, }: { ControllerName: "application", MethodName: "Update", FixedParams: []string{}, Params: map[string][]string{"id": {"123"}}, }, { Method: "PROPFIND", URL: &url.URL{Path: "/app/123"}, }: { ControllerName: "application", MethodName: "WebDevMethodPropFind", FixedParams: []string{}, Params: map[string][]string{"id": {"123"}}, }, { Method: "MKCOL", URL: &url.URL{Path: "/app/123"}, }: { ControllerName: "application", MethodName: "WebDevMethodMkCol", FixedParams: []string{}, Params: map[string][]string{"id": {"123"}}, }, { Method: "COPY", URL: &url.URL{Path: "/app/123"}, }: { ControllerName: "application", MethodName: "WebDevMethodCopy", FixedParams: []string{}, Params: map[string][]string{"id": {"123"}}, }, { Method: "MOVE", URL: &url.URL{Path: "/app/123"}, }: { ControllerName: "application", MethodName: "WebDevMethodMove", FixedParams: []string{}, Params: map[string][]string{"id": {"123"}}, }, { Method: "PROPPATCH", URL: &url.URL{Path: "/app/123"}, }: { ControllerName: "application", MethodName: "WebDevMethodPropPatch", FixedParams: []string{}, Params: map[string][]string{"id": {"123"}}, }, { Method: "LOCK", URL: &url.URL{Path: "/app/123"}, }: { ControllerName: "application", MethodName: "WebDevMethodLock", FixedParams: []string{}, Params: map[string][]string{"id": {"123"}}, }, { Method: "UNLOCK", URL: &url.URL{Path: "/app/123"}, }: { ControllerName: "application", MethodName: "WebDevMethodUnLock", FixedParams: []string{}, Params: map[string][]string{"id": {"123"}}, }, { Method: "TRACE", URL: &url.URL{Path: "/app/123"}, }: { ControllerName: "application", MethodName: "WebDevMethodTrace", FixedParams: []string{}, Params: map[string][]string{"id": {"123"}}, }, { Method: "PURGE", URL: &url.URL{Path: "/app/123"}, }: { ControllerName: "application", MethodName: "CacheMethodPurge", FixedParams: []string{}, Params: map[string][]string{"id": {"123"}}, }, } func TestRouteMatches(t *testing.T) { initControllers() BasePath = "/BasePath" router := NewRouter("") router.Routes, _ = parseRoutes(appModule, "", "", TestRoutes, false) if err := router.updateTree(); err != nil { t.Errorf("updateTree failed: %s", err) } for req, expected := range routeMatchTestCases { t.Log("Routing:", req.Method, req.URL) context := NewGoContext(nil) context.Request.SetRequest(req) c := NewTestController(nil, req) actual := router.Route(c.Request) if !eq(t, "Found route", actual != nil, expected != nil) { continue } if expected.ControllerName != "" { eq(t, "ControllerName", actual.ControllerName, appModule.Namespace()+expected.ControllerName) } else { eq(t, "ControllerName", actual.ControllerName, expected.ControllerName) } eq(t, "MethodName", actual.MethodName, strings.ToLower(expected.MethodName)) eq(t, "len(Params)", len(actual.Params), len(expected.Params)) for key, actualValue := range actual.Params { eq(t, "Params "+key, actualValue[0], expected.Params[key][0]) } eq(t, "len(FixedParams)", len(actual.FixedParams), len(expected.FixedParams)) for i, actualValue := range actual.FixedParams { eq(t, "FixedParams", actualValue, expected.FixedParams[i]) } } } // Reverse Routing type ReverseRouteArgs struct { action string args map[string]string } var reverseRoutingTestCases = map[*ReverseRouteArgs]*ActionDefinition{ { action: "Application.Index", args: map[string]string{}, }: { URL: "/", Method: "GET", Star: false, Action: "Application.Index", }, { action: "Application.Show", args: map[string]string{"id": "123"}, }: { URL: "/app/123/", Method: "GET", Star: false, Action: "Application.Show", }, { action: "Implicit.Route", args: map[string]string{}, }: { URL: "/implicit/route", Method: "GET", Star: true, Action: "Implicit.Route", }, { action: "Application.Save", args: map[string]string{"id": "123", "c": "http://continue"}, }: { URL: "/app/123?c=http%3A%2F%2Fcontinue", Method: "POST", Star: false, Action: "Application.Save", }, { action: "Application.WildShow", args: map[string]string{"id": "123"}, }: { URL: "/app-wild/123/", Method: "GET", Star: false, Action: "Application.WildShow", }, { action: "Application.WebDevMethodPropFind", args: map[string]string{"id": "123"}, }: { URL: "/app/123", Method: "PROPFIND", Star: false, Action: "Application.WebDevMethodPropFind", }, { action: "Application.WebDevMethodMkCol", args: map[string]string{"id": "123"}, }: { URL: "/app/123", Method: "MKCOL", Star: false, Action: "Application.WebDevMethodMkCol", }, { action: "Application.WebDevMethodCopy", args: map[string]string{"id": "123"}, }: { URL: "/app/123", Method: "COPY", Star: false, Action: "Application.WebDevMethodCopy", }, { action: "Application.WebDevMethodMove", args: map[string]string{"id": "123"}, }: { URL: "/app/123", Method: "MOVE", Star: false, Action: "Application.WebDevMethodMove", }, { action: "Application.WebDevMethodPropPatch", args: map[string]string{"id": "123"}, }: { URL: "/app/123", Method: "PROPPATCH", Star: false, Action: "Application.WebDevMethodPropPatch", }, { action: "Application.WebDevMethodLock", args: map[string]string{"id": "123"}, }: { URL: "/app/123", Method: "LOCK", Star: false, Action: "Application.WebDevMethodLock", }, { action: "Application.WebDevMethodUnLock", args: map[string]string{"id": "123"}, }: { URL: "/app/123", Method: "UNLOCK", Star: false, Action: "Application.WebDevMethodUnLock", }, { action: "Application.WebDevMethodTrace", args: map[string]string{"id": "123"}, }: { URL: "/app/123", Method: "TRACE", Star: false, Action: "Application.WebDevMethodTrace", }, { action: "Application.CacheMethodPurge", args: map[string]string{"id": "123"}, }: { URL: "/app/123", Method: "PURGE", Star: false, Action: "Application.CacheMethodPurge", }, } type testController struct { *Controller } func initControllers() { registerControllers() } func TestReverseRouting(t *testing.T) { initControllers() router := NewRouter("") router.Routes, _ = parseRoutes(appModule, "", "", TestRoutes, false) for routeArgs, expected := range reverseRoutingTestCases { actual := router.Reverse(routeArgs.action, routeArgs.args) if !eq(t, fmt.Sprintf("Found route %s %s", routeArgs.action, actual), actual != nil, expected != nil) { continue } eq(t, "Url", actual.URL, expected.URL) eq(t, "Method", actual.Method, expected.Method) eq(t, "Star", actual.Star, expected.Star) eq(t, "Action", actual.Action, expected.Action) } } func BenchmarkRouter(b *testing.B) { router := NewRouter("") router.Routes, _ = parseRoutes(nil, "", "", TestRoutes, false) if err := router.updateTree(); err != nil { b.Errorf("updateTree failed: %s", err) } b.ResetTimer() for i := 0; i < b.N/len(routeMatchTestCases); i++ { for req := range routeMatchTestCases { c := NewTestController(nil, req) r := router.Route(c.Request) if r == nil { b.Errorf("Request not found: %s", req.URL.Path) } } } } // The benchmark from github.com/ant0ine/go-urlrouter func BenchmarkLargeRouter(b *testing.B) { router := NewRouter("") routePaths := []string{ "/", "/signin", "/signout", "/profile", "/settings", "/upload/*file", } for i := 0; i < 10; i++ { for j := 0; j < 5; j++ { routePaths = append(routePaths, fmt.Sprintf("/resource%d/:id/property%d", i, j)) } routePaths = append(routePaths, fmt.Sprintf("/resource%d/:id", i)) routePaths = append(routePaths, fmt.Sprintf("/resource%d", i)) } routePaths = append(routePaths, "/:any") for _, p := range routePaths { router.Routes = append(router.Routes, NewRoute(appModule, "GET", p, "Controller.Action", "", "", 0)) } if err := router.updateTree(); err != nil { b.Errorf("updateTree failed: %s", err) } requestUrls := []string{ "http://example.org/", "http://example.org/resource9/123", "http://example.org/resource9/123/property1", "http://example.org/doesnotexist", } var reqs []*http.Request for _, url := range requestUrls { req, _ := http.NewRequest("GET", url, nil) reqs = append(reqs, req) } b.ResetTimer() for i := 0; i < b.N/len(reqs); i++ { for _, req := range reqs { c := NewTestController(nil, req) route := router.Route(c.Request) if route == nil { b.Errorf("Failed to route: %s", req.URL.Path) } } } } func BenchmarkRouterFilter(b *testing.B) { startFakeBookingApp() controllers := []*Controller{ NewTestController(nil, showRequest), NewTestController(nil, staticRequest), } for _, c := range controllers { c.Params = &Params{} ParseParams(c.Params, c.Request) } b.ResetTimer() for i := 0; i < b.N/len(controllers); i++ { for _, c := range controllers { RouterFilter(c, NilChain) } } } func TestOverrideMethodFilter(t *testing.T) { req, _ := http.NewRequest("POST", "/hotels/3", strings.NewReader("_method=put")) req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") c := NewTestController(nil, req) if HTTPMethodOverride(c, NilChain); c.Request.Method != "PUT" { t.Errorf("Expected to override current method '%s' in route, found '%s' instead", "", c.Request.Method) } } // Helpers func eq(t *testing.T, name string, a, b interface{}) bool { if a != b { t.Error(name, ": (actual)", a, " != ", b, "(expected)") return false } return true } revel-1.0.0/server-engine.go000066400000000000000000000145421370252312000157260ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package revel import ( "errors" "io" "mime/multipart" "net/url" "strings" "time" ) const ( /* Minimum Engine Type Values */ _ = iota ENGINE_RESPONSE_STATUS ENGINE_WRITER ENGINE_PARAMETERS ENGINE_PATH ENGINE_REQUEST ENGINE_RESPONSE ) const ( /* HTTP Engine Type Values Starts at 1000 */ HTTP_QUERY = ENGINE_PARAMETERS HTTP_PATH = ENGINE_PATH HTTP_BODY = iota + 1000 HTTP_FORM = iota + 1000 HTTP_MULTIPART_FORM = iota + 1000 HTTP_METHOD = iota + 1000 HTTP_REQUEST_URI = iota + 1000 HTTP_REQUEST_CONTEXT = iota + 1000 HTTP_REMOTE_ADDR = iota + 1000 HTTP_HOST = iota + 1000 HTTP_URL = iota + 1000 HTTP_SERVER_HEADER = iota + 1000 HTTP_STREAM_WRITER = iota + 1000 HTTP_WRITER = ENGINE_WRITER ) type ( ServerContext interface { GetRequest() ServerRequest GetResponse() ServerResponse } // Callback ServerRequest type ServerRequest interface { GetRaw() interface{} Get(theType int) (interface{}, error) Set(theType int, theValue interface{}) bool } // Callback ServerResponse type ServerResponse interface { ServerRequest } // Callback WebSocket type ServerWebSocket interface { ServerResponse MessageSendJSON(v interface{}) error MessageReceiveJSON(v interface{}) error MessageSend(v interface{}) error MessageReceive(v interface{}) error } // Expected response for HTTP_SERVER_HEADER type (if implemented) ServerHeader interface { SetCookie(cookie string) // Sets the cookie GetCookie(key string) (value ServerCookie, err error) // Gets the cookie Set(key string, value string) Add(key string, value string) Del(key string) Get(key string) (value []string) GetKeys() (headerKeys []string) SetStatus(statusCode int) } // Expected response for FROM_HTTP_COOKIE type (if implemented) ServerCookie interface { GetValue() string } // Expected response for HTTP_MULTIPART_FORM ServerMultipartForm interface { GetFiles() map[string][]*multipart.FileHeader GetValues() url.Values RemoveAll() error } StreamWriter interface { WriteStream(name string, contentlen int64, modtime time.Time, reader io.Reader) error } ServerEngine interface { // Initialize the server (non blocking) Init(init *EngineInit) // Starts the server. This will block until server is stopped Start() // Fires a new event to the server Event(event Event, args interface{}) EventResponse // Returns the engine instance for specific calls Engine() interface{} // Returns the engine Name Name() string // Returns any stats Stats() map[string]interface{} } // The initialization structure passed into the engine EngineInit struct { Address, // The address Network string // The network Port int // The port HTTPMuxList ServerMuxList // The HTTPMux Callback func(ServerContext) // The ServerContext callback endpoint } // An empty server engine ServerEngineEmpty struct { } // The route handler structure ServerMux struct { PathPrefix string // The path prefix Callback interface{} // The callback interface as appropriate to the server } // A list of handlers used for adding special route functions ServerMuxList []ServerMux ) // Sorting function func (r ServerMuxList) Len() int { return len(r) } // Sorting function func (r ServerMuxList) Less(i, j int) bool { return len(r[i].PathPrefix) > len(r[j].PathPrefix) } // Sorting function func (r ServerMuxList) Swap(i, j int) { r[i], r[j] = r[j], r[i] } // Search function, returns the largest path matching this func (r ServerMuxList) Find(path string) (interface{}, bool) { for _, p := range r { if p.PathPrefix == path || strings.HasPrefix(path, p.PathPrefix) { return p.Callback, true } } return nil, false } // Adds this routehandler to the route table. It will be called (if the path prefix matches) // before the Revel mux, this can only be called after the ENGINE_BEFORE_INITIALIZED event func AddHTTPMux(path string, callback interface{}) { ServerEngineInit.HTTPMuxList = append(ServerEngineInit.HTTPMuxList, ServerMux{PathPrefix: path, Callback: callback}) } // Callback point for the server to handle the func handleInternal(ctx ServerContext) { start := time.Now() var c *Controller if RevelConfig.Controller.Reuse { c = RevelConfig.Controller.Stack.Pop().(*Controller) defer func() { RevelConfig.Controller.Stack.Push(c) }() } else { c = NewControllerEmpty() } var ( req, resp = c.Request, c.Response ) c.SetController(ctx) req.WebSocket, _ = ctx.GetResponse().(ServerWebSocket) clientIP := ClientIP(req) // Once finished in the internal, we can return these to the stack c.ClientIP = clientIP c.Log = AppLog.New("ip", clientIP, "path", req.GetPath(), "method", req.Method) // Call the first filter, this will process the request Filters[0](c, Filters[1:]) if c.Result != nil { c.Result.Apply(req, resp) } else if c.Response.Status != 0 { c.Response.SetStatus(c.Response.Status) } // Close the Writer if we can if w, ok := resp.GetWriter().(io.Closer); ok { _ = w.Close() } // Revel request access log format // RequestStartTime ClientIP ResponseStatus RequestLatency HTTPMethod URLPath // Sample format: terminal format // INFO 2017/08/02 22:31:41 server-engine.go:168: Request Stats ip=::1 path=/public/img/favicon.png method=GET action=Static.Serve namespace=static\\ start=2017/08/02 22:31:41 status=200 duration_seconds=0.0007656 // Recommended storing format to json code which looks like // {"action":"Static.Serve","caller":"server-engine.go:168","duration_seconds":0.00058336,"ip":"::1","lvl":3, // "method":"GET","msg":"Request Stats","namespace":"static\\","path":"/public/img/favicon.png", // "start":"2017-08-02T22:34:08-0700","status":200,"t":"2017-08-02T22:34:08.303112145-07:00"} c.Log.Info("Request Stats", "start", start, "status", c.Response.Status, "duration_seconds", time.Since(start).Seconds(), "section", "requestlog", ) } var ( ENGINE_UNKNOWN_GET = errors.New("Server Engine Invalid Get") ) func (e *ServerEngineEmpty) Get(_ string) interface{} { return nil } func (e *ServerEngineEmpty) Set(_ string, _ interface{}) bool { return false } revel-1.0.0/server.go000066400000000000000000000117001370252312000144540ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package revel import ( "fmt" "github.com/revel/revel/session" "os" "strconv" "strings" "github.com/revel/revel/utils" ) // Revel's variables server, router, etc var ( MainRouter *Router MainTemplateLoader *TemplateLoader MainWatcher *Watcher serverEngineMap = map[string]func() ServerEngine{} CurrentEngine ServerEngine ServerEngineInit *EngineInit serverLogger = RevelLog.New("section", "server") ) func RegisterServerEngine(name string, loader func() ServerEngine) { serverLogger.Debug("RegisterServerEngine: Registered engine ", "name", name) serverEngineMap[name] = loader } // InitServer initializes the server and returns the handler // It can be used as an alternative entry-point if one needs the http handler // to be exposed. E.g. to run on multiple addresses and ports or to set custom // TLS options. func InitServer() { CurrentEngine.Init(ServerEngineInit) initControllerStack() startupHooks.Run() // Load templates MainTemplateLoader = NewTemplateLoader(TemplatePaths) if err := MainTemplateLoader.Refresh(); err != nil { serverLogger.Debug("InitServer: Main template loader failed to refresh", "error", err) } // The "watch" config variable can turn on and off all watching. // (As a convenient way to control it all together.) if Config.BoolDefault("watch", true) { MainWatcher = NewWatcher() Filters = append([]Filter{WatchFilter}, Filters...) } // If desired (or by default), create a watcher for templates and routes. // The watcher calls Refresh() on things on the first request. if MainWatcher != nil && Config.BoolDefault("watch.templates", true) { MainWatcher.Listen(MainTemplateLoader, MainTemplateLoader.paths...) } } // Run the server. // This is called from the generated main file. // If port is non-zero, use that. Else, read the port from app.conf. func Run(port int) { defer func() { if r := recover(); r != nil { RevelLog.Crit("Recovered error in startup", "error", r) RaiseEvent(REVEL_FAILURE, r) panic("Fatal error in startup") } }() // Initialize the session logger, must be initiated from this app to avoid // circular references session.InitSession(RevelLog) // Create the CurrentEngine instance from the application config InitServerEngine(port, Config.StringDefault("server.engine", GO_NATIVE_SERVER_ENGINE)) RaiseEvent(ENGINE_BEFORE_INITIALIZED, nil) InitServer() RaiseEvent(ENGINE_STARTED, nil) // This is needed for the harness to recognize that the server is started, it looks for the word // "Listening" in the stdout stream fmt.Fprintf(os.Stdout, "Revel engine is listening on.. %s\n", ServerEngineInit.Address) // Start never returns, CurrentEngine.Start() fmt.Fprintf(os.Stdout, "Revel engine is NOT listening on.. %s\n", ServerEngineInit.Address) RaiseEvent(ENGINE_SHUTDOWN, nil) shutdownHooks.Run() println("\nRevel exited normally\n") } // Build an engine initialization object and start the engine func InitServerEngine(port int, serverEngine string) { address := HTTPAddr if address == "" { address = "localhost" } if port == 0 { port = HTTPPort } var network = "tcp" var localAddress string // If the port is zero, treat the address as a fully qualified local address. // This address must be prefixed with the network type followed by a colon, // e.g. unix:/tmp/app.socket or tcp6:::1 (equivalent to tcp6:0:0:0:0:0:0:0:1) if port == 0 { parts := strings.SplitN(address, ":", 2) network = parts[0] localAddress = parts[1] } else { localAddress = address + ":" + strconv.Itoa(port) } if engineLoader, ok := serverEngineMap[serverEngine]; !ok { panic("Server Engine " + serverEngine + " Not found") } else { CurrentEngine = engineLoader() serverLogger.Debug("InitServerEngine: Found server engine and invoking", "name", CurrentEngine.Name()) ServerEngineInit = &EngineInit{ Address: localAddress, Network: network, Port: port, Callback: handleInternal, } } AddInitEventHandler(CurrentEngine.Event) } // Initialize the controller stack for the application func initControllerStack() { RevelConfig.Controller.Reuse = Config.BoolDefault("revel.controller.reuse",true) if RevelConfig.Controller.Reuse { RevelConfig.Controller.Stack = utils.NewStackLock( Config.IntDefault("revel.controller.stack", 10), Config.IntDefault("revel.controller.maxstack", 200), func() interface{} { return NewControllerEmpty() }) RevelConfig.Controller.CachedStackSize = Config.IntDefault("revel.cache.controller.stack", 10) RevelConfig.Controller.CachedStackMaxSize = Config.IntDefault("revel.cache.controller.maxstack", 100) RevelConfig.Controller.CachedMap = map[string]*utils.SimpleLockStack{} } } // Called to stop the server func StopServer(value interface{}) EventResponse { return RaiseEvent(ENGINE_SHUTDOWN_REQUEST,value) }revel-1.0.0/server_adapter_go.go000066400000000000000000000407761370252312000166600ustar00rootroot00000000000000package revel import ( "net" "net/http" "time" "context" "golang.org/x/net/websocket" "io" "mime/multipart" "net/url" "os" "os/signal" "path" "sort" "strconv" "strings" "github.com/revel/revel/utils" ) // Register the GoHttpServer engine func init() { AddInitEventHandler(func(typeOf Event, value interface{}) (responseOf EventResponse) { if typeOf == REVEL_BEFORE_MODULES_LOADED { RegisterServerEngine(GO_NATIVE_SERVER_ENGINE, func() ServerEngine { return &GoHttpServer{} }) } return }) } // The Go HTTP server type GoHttpServer struct { Server *http.Server // The server instance ServerInit *EngineInit // The server engine initialization MaxMultipartSize int64 // The largest size of file to accept goContextStack *utils.SimpleLockStack // The context stack Set via server.context.stack, server.context.maxstack goMultipartFormStack *utils.SimpleLockStack // The multipart form stack set via server.form.stack, server.form.maxstack HttpMuxList ServerMuxList HasAppMux bool signalChan chan os.Signal } // Called to initialize the server with this EngineInit func (g *GoHttpServer) Init(init *EngineInit) { g.MaxMultipartSize = int64(Config.IntDefault("server.request.max.multipart.filesize", 32)) << 20 /* 32 MB */ g.goContextStack = utils.NewStackLock(Config.IntDefault("server.context.stack", 100), Config.IntDefault("server.context.maxstack", 200), func() interface{} { return NewGoContext(g) }) g.goMultipartFormStack = utils.NewStackLock(Config.IntDefault("server.form.stack", 100), Config.IntDefault("server.form.maxstack", 200), func() interface{} { return &GoMultipartForm{} }) g.ServerInit = init revelHandler := http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { g.Handle(writer, request) }) // Adds the mux list g.HttpMuxList = init.HTTPMuxList sort.Sort(g.HttpMuxList) g.HasAppMux = len(g.HttpMuxList) > 0 g.signalChan = make(chan os.Signal) g.Server = &http.Server{ Addr: init.Address, Handler: revelHandler, ReadTimeout: time.Duration(Config.IntDefault("http.timeout.read", 0)) * time.Second, WriteTimeout: time.Duration(Config.IntDefault("http.timeout.write", 0)) * time.Second, } } // Handler is assigned in the Init func (g *GoHttpServer) Start() { go func() { time.Sleep(100 * time.Millisecond) serverLogger.Debugf("Start: Listening on %s...", g.Server.Addr) }() if HTTPSsl { if g.ServerInit.Network != "tcp" { // This limitation is just to reduce complexity, since it is standard // to terminate SSL upstream when using unix domain sockets. serverLogger.Fatal("SSL is only supported for TCP sockets. Specify a port to listen on.") } serverLogger.Fatal("Failed to listen:", "error", g.Server.ListenAndServeTLS(HTTPSslCert, HTTPSslKey)) } else { listener, err := net.Listen(g.ServerInit.Network, g.Server.Addr) if err != nil { serverLogger.Fatal("Failed to listen:", "error", err) } serverLogger.Warn("Server exiting:", "error", g.Server.Serve(listener)) } } // Handle the request and response for the server func (g *GoHttpServer) Handle(w http.ResponseWriter, r *http.Request) { // This section is called if the developer has added custom mux to the app if g.HasAppMux && g.handleAppMux(w, r) { return } g.handleMux(w, r) } // Handle the request and response for the servers mux func (g *GoHttpServer) handleAppMux(w http.ResponseWriter, r *http.Request) bool { // Check the prefix and split them requestPath := path.Clean(r.URL.Path) if handler, hasHandler := g.HttpMuxList.Find(requestPath); hasHandler { clientIP := HttpClientIP(r) localLog := AppLog.New("ip", clientIP, "path", r.URL.Path, "method", r.Method) defer func() { if err := recover(); err != nil { localLog.Error("An error was caught using the handler", "path", requestPath, "error", err) w.WriteHeader(http.StatusInternalServerError) } }() start := time.Now() handler.(http.HandlerFunc)(w, r) localLog.Info("Request Stats", "start", start, "duration_seconds", time.Since(start).Seconds(), "section", "requestlog", ) return true } return false } // Passes the server request to Revel func (g *GoHttpServer) handleMux(w http.ResponseWriter, r *http.Request) { if maxRequestSize := int64(Config.IntDefault("http.maxrequestsize", 0)); maxRequestSize > 0 { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) } upgrade := r.Header.Get("Upgrade") context := g.goContextStack.Pop().(*GoContext) defer func() { g.goContextStack.Push(context) }() context.Request.SetRequest(r) context.Response.SetResponse(w) if upgrade == "websocket" || upgrade == "Websocket" { websocket.Handler(func(ws *websocket.Conn) { //Override default Read/Write timeout with sane value for a web socket request if err := ws.SetDeadline(time.Now().Add(time.Hour * 24)); err != nil { serverLogger.Error("SetDeadLine failed:", err) } r.Method = "WS" context.Request.WebSocket = ws context.WebSocket = &GoWebSocket{Conn: ws, GoResponse: *context.Response} g.ServerInit.Callback(context) }).ServeHTTP(w, r) } else { g.ServerInit.Callback(context) } } // ClientIP method returns client IP address from HTTP request. // // Note: Set property "app.behind.proxy" to true only if Revel is running // behind proxy like nginx, haproxy, apache, etc. Otherwise // you may get inaccurate Client IP address. Revel parses the // IP address in the order of X-Forwarded-For, X-Real-IP. // // By default revel will get http.Request's RemoteAddr func HttpClientIP(r *http.Request) string { if Config.BoolDefault("app.behind.proxy", false) { // Header X-Forwarded-For if fwdFor := strings.TrimSpace(r.Header.Get(HdrForwardedFor)); fwdFor != "" { index := strings.Index(fwdFor, ",") if index == -1 { return fwdFor } return fwdFor[:index] } // Header X-Real-Ip if realIP := strings.TrimSpace(r.Header.Get(HdrRealIP)); realIP != "" { return realIP } } if remoteAddr, _, err := net.SplitHostPort(r.RemoteAddr); err == nil { return remoteAddr } return "" } // The server key name const GO_NATIVE_SERVER_ENGINE = "go" // Returns the name of this engine func (g *GoHttpServer) Name() string { return GO_NATIVE_SERVER_ENGINE } // Returns stats for this engine func (g *GoHttpServer) Stats() map[string]interface{} { return map[string]interface{}{ "Go Engine Context": g.goContextStack.String(), "Go Engine Forms": g.goMultipartFormStack.String(), } } // Return the engine instance func (g *GoHttpServer) Engine() interface{} { return g.Server } // Handles an event from Revel func (g *GoHttpServer) Event(event Event, args interface{}) (r EventResponse) { switch event { case ENGINE_STARTED: signal.Notify(g.signalChan, os.Interrupt, os.Kill) go func() { _ = <-g.signalChan serverLogger.Info("Received quit singal Please wait ... ") RaiseEvent(ENGINE_SHUTDOWN_REQUEST, nil) }() case ENGINE_SHUTDOWN_REQUEST: ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(Config.IntDefault("app.cancel.timeout", 60))) defer cancel() g.Server.Shutdown(ctx) default: } return } type ( // The go context GoContext struct { Request *GoRequest // The request Response *GoResponse // The response WebSocket *GoWebSocket // The websocket } // The go request GoRequest struct { Original *http.Request // The original FormParsed bool // True if form parsed MultiFormParsed bool // True if multipart form parsed WebSocket *websocket.Conn // The websocket ParsedForm *GoMultipartForm // The parsed form data Goheader *GoHeader // The header Engine *GoHttpServer // THe server } // The response GoResponse struct { Original http.ResponseWriter // The original writer Goheader *GoHeader // The header Writer io.Writer // The writer Request *GoRequest // The request Engine *GoHttpServer // The engine } // The multipart form GoMultipartForm struct { Form *multipart.Form // The form } // The go header GoHeader struct { Source interface{} // The source isResponse bool // True if response header } // The websocket GoWebSocket struct { Conn *websocket.Conn // The connection GoResponse // The response } // The cookie GoCookie http.Cookie ) // Create a new go context func NewGoContext(instance *GoHttpServer) *GoContext { // This bit in here is for the test cases, which pass in a nil value if instance == nil { instance = &GoHttpServer{MaxMultipartSize: 32 << 20} instance.goContextStack = utils.NewStackLock(100, 200, func() interface{} { return NewGoContext(instance) }) instance.goMultipartFormStack = utils.NewStackLock(100, 200, func() interface{} { return &GoMultipartForm{} }) } c := &GoContext{Request: &GoRequest{Goheader: &GoHeader{}, Engine: instance}} c.Response = &GoResponse{Goheader: &GoHeader{}, Request: c.Request, Engine: instance} return c } // get the request func (c *GoContext) GetRequest() ServerRequest { return c.Request } // Get the response func (c *GoContext) GetResponse() ServerResponse { if c.WebSocket != nil { return c.WebSocket } return c.Response } // Destroy the context func (c *GoContext) Destroy() { c.Response.Destroy() c.Request.Destroy() if c.WebSocket != nil { c.WebSocket.Destroy() c.WebSocket = nil } } // Communicate with the server engine to exchange data func (r *GoRequest) Get(key int) (value interface{}, err error) { switch key { case HTTP_SERVER_HEADER: value = r.GetHeader() case HTTP_MULTIPART_FORM: value, err = r.GetMultipartForm() case HTTP_QUERY: value = r.Original.URL.Query() case HTTP_FORM: value, err = r.GetForm() case HTTP_REQUEST_URI: value = r.Original.URL.String() case HTTP_REQUEST_CONTEXT: value = r.Original.Context() case HTTP_REMOTE_ADDR: value = r.Original.RemoteAddr case HTTP_METHOD: value = r.Original.Method case HTTP_PATH: value = r.Original.URL.Path case HTTP_HOST: value = r.Original.Host case HTTP_URL: value = r.Original.URL case HTTP_BODY: value = r.Original.Body default: err = ENGINE_UNKNOWN_GET } return } // Sets the request key with value func (r *GoRequest) Set(key int, value interface{}) bool { return false } // Returns the form func (r *GoRequest) GetForm() (url.Values, error) { if !r.FormParsed { if e := r.Original.ParseForm(); e != nil { return nil, e } r.FormParsed = true } return r.Original.Form, nil } // Returns the form func (r *GoRequest) GetMultipartForm() (ServerMultipartForm, error) { if !r.MultiFormParsed { if e := r.Original.ParseMultipartForm(r.Engine.MaxMultipartSize); e != nil { return nil, e } r.ParsedForm = r.Engine.goMultipartFormStack.Pop().(*GoMultipartForm) r.ParsedForm.Form = r.Original.MultipartForm } return r.ParsedForm, nil } // Returns the header func (r *GoRequest) GetHeader() ServerHeader { return r.Goheader } // Returns the raw value func (r *GoRequest) GetRaw() interface{} { return r.Original } // Sets the request func (r *GoRequest) SetRequest(req *http.Request) { r.Original = req r.Goheader.Source = r r.Goheader.isResponse = false } // Destroy the request func (r *GoRequest) Destroy() { r.Goheader.Source = nil r.Original = nil r.FormParsed = false r.MultiFormParsed = false r.ParsedForm = nil } // Gets the key from the response func (r *GoResponse) Get(key int) (value interface{}, err error) { switch key { case HTTP_SERVER_HEADER: value = r.Header() case HTTP_STREAM_WRITER: value = r case HTTP_WRITER: value = r.Writer default: err = ENGINE_UNKNOWN_GET } return } // Sets the key with the value func (r *GoResponse) Set(key int, value interface{}) (set bool) { switch key { case ENGINE_RESPONSE_STATUS: r.Header().SetStatus(value.(int)) set = true case HTTP_WRITER: r.SetWriter(value.(io.Writer)) set = true } return } // Sets the header func (r *GoResponse) Header() ServerHeader { return r.Goheader } // Gets the original response func (r *GoResponse) GetRaw() interface{} { return r.Original } // Sets the writer func (r *GoResponse) SetWriter(writer io.Writer) { r.Writer = writer } // Write output to stream func (r *GoResponse) WriteStream(name string, contentlen int64, modtime time.Time, reader io.Reader) error { // Check to see if the output stream is modified, if not send it using the // Native writer written := false if _, ok := r.Writer.(http.ResponseWriter); ok { if rs, ok := reader.(io.ReadSeeker); ok { http.ServeContent(r.Original, r.Request.Original, name, modtime, rs) written = true } } if !written { // Else, do a simple io.Copy. ius := r.Request.Original.Header.Get("If-Unmodified-Since") if t, err := http.ParseTime(ius); err == nil && !modtime.IsZero() { // The Date-Modified header truncates sub-second precision, so // use mtime < t+1s instead of mtime <= t to check for unmodified. if modtime.Before(t.Add(1 * time.Second)) { h := r.Original.Header() delete(h, "Content-Type") delete(h, "Content-Length") if h.Get("Etag") != "" { delete(h, "Last-Modified") } r.Original.WriteHeader(http.StatusNotModified) return nil } } if contentlen != -1 { header := ServerHeader(r.Goheader) if writer, found := r.Writer.(*CompressResponseWriter); found { header = ServerHeader(writer.Header) } header.Set("Content-Length", strconv.FormatInt(contentlen, 10)) } if _, err := io.Copy(r.Writer, reader); err != nil { r.Original.WriteHeader(http.StatusInternalServerError) return err } } return nil } // Frees response func (r *GoResponse) Destroy() { if c, ok := r.Writer.(io.Closer); ok { c.Close() } r.Goheader.Source = nil r.Original = nil r.Writer = nil } // Sets the response func (r *GoResponse) SetResponse(w http.ResponseWriter) { r.Original = w r.Writer = w r.Goheader.Source = r r.Goheader.isResponse = true } // Sets the cookie func (r *GoHeader) SetCookie(cookie string) { if r.isResponse { r.Source.(*GoResponse).Original.Header().Add("Set-Cookie", cookie) } } // Gets the cookie func (r *GoHeader) GetCookie(key string) (value ServerCookie, err error) { if !r.isResponse { var cookie *http.Cookie if cookie, err = r.Source.(*GoRequest).Original.Cookie(key); err == nil { value = GoCookie(*cookie) } } return } // Sets (replaces) header key func (r *GoHeader) Set(key string, value string) { if r.isResponse { r.Source.(*GoResponse).Original.Header().Set(key, value) } } // Adds the header key func (r *GoHeader) Add(key string, value string) { if r.isResponse { r.Source.(*GoResponse).Original.Header().Add(key, value) } } // Deletes the header key func (r *GoHeader) Del(key string) { if r.isResponse { r.Source.(*GoResponse).Original.Header().Del(key) } } // Gets the header key func (r *GoHeader) Get(key string) (value []string) { if !r.isResponse { value = r.Source.(*GoRequest).Original.Header[key] if len(value) == 0 { if ihead := r.Source.(*GoRequest).Original.Header.Get(key); ihead != "" { value = append(value, ihead) } } } else { value = r.Source.(*GoResponse).Original.Header()[key] } return } // Returns list of header keys func (r *GoHeader) GetKeys() (value []string) { if !r.isResponse { for key := range r.Source.(*GoRequest).Original.Header { value = append(value, key) } } else { for key := range r.Source.(*GoResponse).Original.Header() { value = append(value, key) } } return } // Sets the status of the header func (r *GoHeader) SetStatus(statusCode int) { if r.isResponse { r.Source.(*GoResponse).Original.WriteHeader(statusCode) } } // Return cookies value func (r GoCookie) GetValue() string { return r.Value } // Return files from the form func (f *GoMultipartForm) GetFiles() map[string][]*multipart.FileHeader { return f.Form.File } // Return values from the form func (f *GoMultipartForm) GetValues() url.Values { return url.Values(f.Form.Value) } // Remove all values from the form freeing memory func (f *GoMultipartForm) RemoveAll() error { return f.Form.RemoveAll() } /** * Message send JSON */ func (g *GoWebSocket) MessageSendJSON(v interface{}) error { return websocket.JSON.Send(g.Conn, v) } /** * Message receive JSON */ func (g *GoWebSocket) MessageReceiveJSON(v interface{}) error { return websocket.JSON.Receive(g.Conn, v) } /** * Message Send */ func (g *GoWebSocket) MessageSend(v interface{}) error { return websocket.Message.Send(g.Conn, v) } /** * Message receive */ func (g *GoWebSocket) MessageReceive(v interface{}) error { return websocket.Message.Receive(g.Conn, v) } revel-1.0.0/server_test.go000066400000000000000000000073041370252312000155200ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package revel import ( "github.com/stretchr/testify/assert" "net/http" "net/http/httptest" "os" "path/filepath" "strings" "testing" "time" ) // This tries to benchmark the usual request-serving pipeline to get an overall // performance metric. // // Each iteration runs one mock request to display a hotel's detail page by id. // // Contributing parts: // - Routing // - Controller lookup / invocation // - Parameter binding // - Session, flash, i18n cookies // - Render() call magic // - Template rendering func BenchmarkServeAction(b *testing.B) { benchmarkRequest(b, showRequest) } func BenchmarkServeJson(b *testing.B) { benchmarkRequest(b, jsonRequest) } func BenchmarkServePlaintext(b *testing.B) { benchmarkRequest(b, plaintextRequest) } // This tries to benchmark the static serving overhead when serving an "average // size" 7k file. func BenchmarkServeStatic(b *testing.B) { benchmarkRequest(b, staticRequest) } func benchmarkRequest(b *testing.B, req *http.Request) { startFakeBookingApp() b.ResetTimer() resp := httptest.NewRecorder() for i := 0; i < b.N; i++ { CurrentEngine.(*GoHttpServer).Handle(resp, req) } } // Test that the booking app can be successfully run for a test. func TestFakeServer(t *testing.T) { startFakeBookingApp() resp := httptest.NewRecorder() // First, test that the expected responses are actually generated CurrentEngine.(*GoHttpServer).Handle(resp, showRequest) if !strings.Contains(resp.Body.String(), "300 Main St.") { t.Errorf("Failed to find hotel address in action response:\n%s", resp.Body) t.FailNow() } resp.Body.Reset() CurrentEngine.(*GoHttpServer).Handle(resp, staticRequest) sessvarsSize := getFileSize(t, filepath.Join(BasePath, "public", "js", "sessvars.js")) if int64(resp.Body.Len()) != sessvarsSize { t.Errorf("Expected sessvars.js to have %d bytes, got %d:\n%s", sessvarsSize, resp.Body.Len(), resp.Body) t.FailNow() } resp.Body.Reset() CurrentEngine.(*GoHttpServer).Handle(resp, jsonRequest) if !strings.Contains(resp.Body.String(), `"Address":"300 Main St."`) { t.Errorf("Failed to find hotel address in JSON response:\n%s", resp.Body) t.FailNow() } resp.Body.Reset() CurrentEngine.(*GoHttpServer).Handle(resp, plaintextRequest) if resp.Body.String() != "Hello, World!" { t.Errorf("Failed to find greeting in plaintext response:\n%s", resp.Body) t.FailNow() } resp.Body = nil } func getFileSize(t *testing.T, name string) int64 { fi, err := os.Stat(name) if err != nil { t.Errorf("Unable to stat file:\n%s", name) t.FailNow() } return fi.Size() } // Ensure on app start runs in order func TestOnAppStart(t *testing.T) { str := "" a := assert.New(t) OnAppStart(func() { str += " World" }, 2) OnAppStart(func() { str += "Hello" }, 1) startFakeBookingApp() a.Equal("Hello World", str, "Failed to order OnAppStart") } // Ensure on app stop runs in order func TestOnAppStop(t *testing.T) { a := assert.New(t) startFakeBookingApp() i := "" OnAppStop(func() { i += "cruel world" t.Logf("i: %v \n", i) }, 2) OnAppStop(func() { i += "goodbye " t.Logf("i: %v \n", i) }, 1) go func() { time.Sleep(2 * time.Second) RaiseEvent(ENGINE_SHUTDOWN_REQUEST, nil) }() Run(0) a.Equal("goodbye cruel world", i, "Did not get shutdown events") } var ( showRequest, _ = http.NewRequest("GET", "/hotels/3", nil) staticRequest, _ = http.NewRequest("GET", "/public/js/sessvars.js", nil) jsonRequest, _ = http.NewRequest("GET", "/hotels/3/booking", nil) plaintextRequest, _ = http.NewRequest("GET", "/hotels", nil) ) revel-1.0.0/session/000077500000000000000000000000001370252312000143035ustar00rootroot00000000000000revel-1.0.0/session/init.go000066400000000000000000000003411370252312000155730ustar00rootroot00000000000000package session // The logger for the session import "github.com/revel/revel/logger" var sessionLog logger.MultiLogger func InitSession(coreLogger logger.MultiLogger) { sessionLog = coreLogger.New("section", "session") } revel-1.0.0/session/session.go000066400000000000000000000243711370252312000163240ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package session import ( "encoding/hex" "encoding/json" "errors" "github.com/twinj/uuid" "reflect" "strconv" "strings" "time" ) const ( // The key for the identity of the session SessionIDKey = "_ID" // The expiration date of the session TimestampKey = "_TS" // The value name indicating how long the session should persist - ie should it persist after the browser closes // this is set under the TimestampKey if the session data should expire immediately SessionValueName = "session" // The key container for the json objects of the data, any non strings found in the map will be placed in here // serialized by key using JSON SessionObjectKeyName = "_object_" // The mapped session object SessionMapKeyName = "_map_" // The suffix of the session cookie SessionCookieSuffix = "_SESSION" ) // Session data, can be any data, there are reserved keywords used by the storage data // SessionIDKey Is the key name for the session // TimestampKey Is the time that the session should expire // type Session map[string]interface{} func NewSession() Session { return Session{} } // ID retrieves from the cookie or creates a time-based UUID identifying this // session. func (s Session) ID() string { if sessionIDStr, ok := s[SessionIDKey]; ok { return sessionIDStr.(string) } buffer := uuid.NewV4() s[SessionIDKey] = hex.EncodeToString(buffer.Bytes()) return s[SessionIDKey].(string) } // getExpiration return a time.Time with the session's expiration date. // It uses the passed in expireAfterDuration to add with the current time if the timeout is not // browser dependent (ie session). If previous session has set to "session", the time returned is time.IsZero() func (s Session) GetExpiration(expireAfterDuration time.Duration) time.Time { if expireAfterDuration == 0 || s[TimestampKey] == SessionValueName { // Expire after closing browser return time.Time{} } return time.Now().Add(expireAfterDuration) } // SetNoExpiration sets session to expire when browser session ends func (s Session) SetNoExpiration() { s[TimestampKey] = SessionValueName } // SetDefaultExpiration sets session to expire after default duration func (s Session) SetDefaultExpiration() { delete(s, TimestampKey) } // sessionTimeoutExpiredOrMissing returns a boolean of whether the session // cookie is either not present or present but beyond its time to live; i.e., // whether there is not a valid session. func (s Session) SessionTimeoutExpiredOrMissing() bool { if exp, present := s[TimestampKey]; !present { return true } else if exp == SessionValueName { return false } else if expInt, _ := strconv.Atoi(exp.(string)); int64(expInt) < time.Now().Unix() { return true } return false } // Constant error if session value is not found var SESSION_VALUE_NOT_FOUND = errors.New("Session value not found") // Get an object or property from the session // it may be embedded inside the session. func (s Session) Get(key string) (newValue interface{}, err error) { // First check to see if it is in the session if v, found := s[key]; found { return v, nil } return s.GetInto(key, nil, false) } // Get into the specified value. // If value exists in the session it will just return the value func (s Session) GetInto(key string, target interface{}, force bool) (result interface{}, err error) { if v, found := s[key]; found && !force { return v, nil } splitKey := strings.Split(key, ".") rootKey := splitKey[0] // Force always recreates the object from the session data map if force { if target == nil { if result, err = s.sessionDataFromMap(key); err != nil { return } } else if result, err = s.sessionDataFromObject(rootKey, target); err != nil { return } return s.getNestedProperty(splitKey, result) } // Attempt to find the key in the session, this is the most generalized form v, found := s[rootKey] if !found { if target == nil { // Try to fetch it from the session if v, err = s.sessionDataFromMap(rootKey); err != nil { return } } else if v, err = s.sessionDataFromObject(rootKey, target); err != nil { return } } return s.getNestedProperty(splitKey, v) } // Returns the default value if the key is not found func (s Session) GetDefault(key string, value interface{}, defaultValue interface{}) interface{} { v, e := s.GetInto(key, value, false) if e != nil { v = defaultValue } return v } // Extract the values from the session func (s Session) GetProperty(key string, value interface{}) (interface{}, error) { // Capitalize the first letter key = strings.Title(key) sessionLog.Info("getProperty", "key", key, "value", value) // For a map it is easy if reflect.TypeOf(value).Kind() == reflect.Map { val := reflect.ValueOf(value) valueOf := val.MapIndex(reflect.ValueOf(key)) if valueOf == reflect.Zero(reflect.ValueOf(value).Type()) { return nil, nil } //idx := val.MapIndex(reflect.ValueOf(key)) if !valueOf.IsValid() { return nil, nil } return valueOf.Interface(), nil } objValue := s.reflectValue(value) field := objValue.FieldByName(key) if !field.IsValid() { return nil, SESSION_VALUE_NOT_FOUND } return field.Interface(), nil } // Places the object into the session, a nil value will cause remove the key from the session // (or you can use the Session.Del(key) function func (s Session) Set(key string, value interface{}) error { if value == nil { s.Del(key) return nil } s[key] = value return nil } // Delete the key from the sessionObjects and Session func (s Session) Del(key string) { sessionJsonMap := s.getSessionJsonMap() delete(sessionJsonMap, key) delete(s, key) } // Extracts the session as a map of [string keys] and json values func (s Session) getSessionJsonMap() map[string]string { if sessionJson, found := s[SessionObjectKeyName]; found { if _, valid := sessionJson.(map[string]string); !valid { sessionLog.Error("Session object key corrupted, reset", "was", sessionJson) s[SessionObjectKeyName] = map[string]string{} } // serialized data inside the session _objects } else { s[SessionObjectKeyName] = map[string]string{} } return s[SessionObjectKeyName].(map[string]string) } // Convert the map to a simple map[string]string map // this will marshal any non string objects encountered and store them the the jsonMap // The expiration time will also be assigned func (s Session) Serialize() map[string]string { sessionJsonMap := s.getSessionJsonMap() newMap := map[string]string{} newObjectMap := map[string]string{} for key, value := range sessionJsonMap { newObjectMap[key] = value } for key, value := range s { if key == SessionObjectKeyName || key == SessionMapKeyName { continue } if reflect.ValueOf(value).Kind() == reflect.String { newMap[key] = value.(string) continue } if data, err := json.Marshal(value); err != nil { sessionLog.Error("Unable to marshal session ", "key", key, "error", err) continue } else { newObjectMap[key] = string(data) } } if len(newObjectMap) > 0 { if data, err := json.Marshal(newObjectMap); err != nil { sessionLog.Error("Unable to marshal session ", "key", SessionObjectKeyName, "error", err) } else { newMap[SessionObjectKeyName] = string(data) } } return newMap } // Set the session object from the loaded data func (s Session) Load(data map[string]string) { for key, value := range data { if key == SessionObjectKeyName { target := map[string]string{} if err := json.Unmarshal([]byte(value), &target); err != nil { sessionLog.Error("Unable to unmarshal session ", "key", SessionObjectKeyName, "error", err) } else { s[key] = target } } else { s[key] = value } } } // Checks to see if the session is empty func (s Session) Empty() bool { i := 0 for k := range s { i++ if k == SessionObjectKeyName || k == SessionMapKeyName { continue } } return i == 0 } func (s *Session) reflectValue(obj interface{}) reflect.Value { var val reflect.Value if reflect.TypeOf(obj).Kind() == reflect.Ptr { val = reflect.ValueOf(obj).Elem() } else { val = reflect.ValueOf(obj) } return val } // Starting at position 1 drill into the object func (s Session) getNestedProperty(keys []string, newValue interface{}) (result interface{}, err error) { for x := 1; x < len(keys); x++ { newValue, err = s.GetProperty(keys[x], newValue) if err != nil || newValue == nil { return newValue, err } } return newValue, nil } // Always converts the data from the session mapped objects into the target, // it will store the results under the session key name SessionMapKeyName func (s Session) sessionDataFromMap(key string) (result interface{}, err error) { var mapValue map[string]interface{} uncastMapValue, found := s[SessionMapKeyName] if !found { mapValue = map[string]interface{}{} s[SessionMapKeyName] = mapValue } else if mapValue, found = uncastMapValue.(map[string]interface{}); !found { // Unusual means that the value in the session was not expected sessionLog.Errorf("Unusual means that the value in the session was not expected", "session", uncastMapValue) mapValue = map[string]interface{}{} s[SessionMapKeyName] = mapValue } // Try to extract the key from the map result, found = mapValue[key] if !found { result, err = s.convertSessionData(key, nil) if err == nil { mapValue[key] = result } } return } // Unpack the object from the session map and store it in the session when done, if no error occurs func (s Session) sessionDataFromObject(key string, newValue interface{}) (result interface{}, err error) { result, err = s.convertSessionData(key, newValue) if err != nil { return } s[key] = result return } // Converts from the session json map into the target, func (s Session) convertSessionData(key string, target interface{}) (result interface{}, err error) { sessionJsonMap := s.getSessionJsonMap() v, found := sessionJsonMap[key] if !found { return target, SESSION_VALUE_NOT_FOUND } // Create a target if needed if target == nil { target = map[string]interface{}{} if err := json.Unmarshal([]byte(v), &target); err != nil { return target, err } } else if err := json.Unmarshal([]byte(v), target); err != nil { return target, err } result = target return } revel-1.0.0/session/session_cookie_test.go000066400000000000000000000044611370252312000207120ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package session_test import ( "testing" "github.com/revel/revel" "github.com/revel/revel/session" "github.com/stretchr/testify/assert" "net/http" "time" ) func TestCookieRestore(t *testing.T) { a := assert.New(t) session.InitSession(revel.RevelLog) cse := revel.NewSessionCookieEngine() originSession := session.NewSession() setSharedDataTest(originSession) originSession["foo"] = "foo" originSession["bar"] = "bar" cookie := cse.GetCookie(originSession) if !cookie.Expires.IsZero() { t.Error("incorrect cookie expire", cookie.Expires) } restoredSession := session.NewSession() cse.DecodeCookie(revel.GoCookie(*cookie), restoredSession) a.Equal("foo",restoredSession["foo"]) a.Equal("bar",restoredSession["bar"]) testSharedData(originSession, restoredSession, t, a) } func TestCookieSessionExpire(t *testing.T) { session.InitSession(revel.RevelLog) cse := revel.NewSessionCookieEngine() cse.ExpireAfterDuration = time.Hour session := session.NewSession() session["user"] = "Tom" var cookie *http.Cookie for i := 0; i < 3; i++ { cookie = cse.GetCookie(session) time.Sleep(time.Second) cse.DecodeCookie(revel.GoCookie(*cookie), session) } expectExpire := time.Now().Add(cse.ExpireAfterDuration) if cookie.Expires.Unix() < expectExpire.Add(-time.Second).Unix() { t.Error("expect expires", cookie.Expires, "after", expectExpire.Add(-time.Second)) } if cookie.Expires.Unix() > expectExpire.Unix() { t.Error("expect expires", cookie.Expires, "before", expectExpire) } // Test that the expiration time is zero for a "browser" session session.SetNoExpiration() cookie = cse.GetCookie(session) if !cookie.Expires.IsZero() { t.Error("expect cookie expires is zero") } // Check the default session is set session.SetDefaultExpiration() cookie = cse.GetCookie(session) expectExpire = time.Now().Add(cse.ExpireAfterDuration) if cookie.Expires.Unix() < expectExpire.Add(-time.Second).Unix() { t.Error("expect expires", cookie.Expires, "after", expectExpire.Add(-time.Second)) } if cookie.Expires.Unix() > expectExpire.Unix() { t.Error("expect expires", cookie.Expires, "before", expectExpire) } } revel-1.0.0/session/session_test.go000066400000000000000000000027761370252312000173700ustar00rootroot00000000000000// Copyright (c) 2012-2018 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package session_test import ( "fmt" "github.com/revel/revel" "github.com/revel/revel/session" "github.com/stretchr/testify/assert" "testing" ) // test the commands func TestSessionString(t *testing.T) { session.InitSession(revel.RevelLog) a := assert.New(t) s := session.NewSession() s.Set("happy", "day") a.Equal("day", s.GetDefault("happy", nil, ""), fmt.Sprintf("Session Data %#v\n", s)) } func TestSessionStruct(t *testing.T) { session.InitSession(revel.RevelLog) a := assert.New(t) s := session.NewSession() setSharedDataTest(s) a.Equal("test", s.GetDefault("happy.a.aa", nil, ""), fmt.Sprintf("Session Data %#v\n", s)) stringMap := s.Serialize() s1 := session.NewSession() s1.Load(stringMap) testSharedData(s, s1, t, a) } func setSharedDataTest(s session.Session) { data := struct { A struct { Aa string } B int C string D float32 }{A: struct { Aa string }{Aa: "test"}, B: 5, C: "test", D: -325.25} s.Set("happy", data) } func testSharedData(s, s1 session.Session, t *testing.T, a *assert.Assertions) { // Compress the session to a string t.Logf("Original session %#v\n", s) t.Logf("New built session %#v\n", s1) data,err := s1.Get("happy.a.aa") a.Nil(err,"Expected nil") a.Equal("test", data, fmt.Sprintf("Session Data %#v\n", s)) t.Logf("After test session %#v\n", s1) } revel-1.0.0/session_adapter_cookie.go000066400000000000000000000076611370252312000176750ustar00rootroot00000000000000package revel import ( "fmt" "github.com/revel/revel/session" "net/http" "net/url" "strconv" "strings" "time" ) type ( // The session cookie engine SessionCookieEngine struct { ExpireAfterDuration time.Duration } ) // A logger for the session engine var sessionEngineLog = RevelLog.New("section", "session-engine") // Create a new instance to test func init() { RegisterSessionEngine(initCookieEngine, "revel-cookie") } // For testing purposes this engine is used func NewSessionCookieEngine() *SessionCookieEngine { ce := &SessionCookieEngine{} return ce } // Called when the the application starts, retrieves data from the app config so cannot be run until then func initCookieEngine() SessionEngine { ce := &SessionCookieEngine{} var err error if expiresString, ok := Config.String("session.expires"); !ok { ce.ExpireAfterDuration = 30 * 24 * time.Hour } else if expiresString == session.SessionValueName { ce.ExpireAfterDuration = 0 } else if ce.ExpireAfterDuration, err = time.ParseDuration(expiresString); err != nil { panic(fmt.Errorf("session.expires invalid: %s", err)) } return ce } // Decode the session information from the cookie retrieved from the controller request func (cse *SessionCookieEngine) Decode(c *Controller) { // Decode the session from a cookie. c.Session = map[string]interface{}{} sessionMap := c.Session if cookie, err := c.Request.Cookie(CookiePrefix + session.SessionCookieSuffix); err != nil { return } else { cse.DecodeCookie(cookie, sessionMap) c.Session = sessionMap } } // Encode the session information to the cookie, set the cookie on the controller func (cse *SessionCookieEngine) Encode(c *Controller) { c.SetCookie(cse.GetCookie(c.Session)) } // Exposed only for testing purposes func (cse *SessionCookieEngine) DecodeCookie(cookie ServerCookie, s session.Session) { // Decode the session from a cookie. // Separate the data from the signature. cookieValue := cookie.GetValue() hyphen := strings.Index(cookieValue, "-") if hyphen == -1 || hyphen >= len(cookieValue)-1 { return } sig, data := cookieValue[:hyphen], cookieValue[hyphen+1:] // Verify the signature. if !Verify(data, sig) { sessionEngineLog.Warn("Session cookie signature failed") return } // Parse the cookie into a temp map, and then load it into the session object tempMap := map[string]string{} ParseKeyValueCookie(data, func(key, val string) { tempMap[key] = val }) s.Load(tempMap) // Check timeout after unpacking values - if timeout missing (or removed) destroy all session // objects if s.SessionTimeoutExpiredOrMissing() { // If this fails we need to delete all the keys from the session for key := range s { delete(s, key) } } } // Convert session to cookie func (cse *SessionCookieEngine) GetCookie(s session.Session) *http.Cookie { var sessionValue string ts := s.GetExpiration(cse.ExpireAfterDuration) if ts.IsZero() { s[session.TimestampKey] = session.SessionValueName } else { s[session.TimestampKey] = strconv.FormatInt(ts.Unix(), 10) } // Convert the key to a string map stringMap := s.Serialize() for key, value := range stringMap { if strings.ContainsAny(key, ":\x00") { panic("Session keys may not have colons or null bytes") } if strings.Contains(value, "\x00") { panic("Session values may not have null bytes") } sessionValue += "\x00" + key + ":" + value + "\x00" } if len(sessionValue) > 1024*4 { sessionEngineLog.Error("SessionCookieEngine.Cookie, session data has exceeded 4k limit (%d) cookie data will not be reliable", "length", len(sessionValue)) } sessionData := url.QueryEscape(sessionValue) sessionCookie := &http.Cookie{ Name: CookiePrefix + session.SessionCookieSuffix, Value: Sign(sessionData) + "-" + sessionData, Domain: CookieDomain, Path: "/", HttpOnly: true, Secure: CookieSecure, SameSite: CookieSameSite, Expires: ts.UTC(), MaxAge: int(cse.ExpireAfterDuration.Seconds()), } return sessionCookie } revel-1.0.0/session_engine.go000066400000000000000000000020221370252312000161530ustar00rootroot00000000000000package revel // The session engine provides an interface to allow for storage of session data type ( SessionEngine interface { Decode(c *Controller) // Called to decode the session information on the controller Encode(c *Controller) // Called to encode the session information on the controller } ) var ( sessionEngineMap = map[string]func() SessionEngine{} CurrentSessionEngine SessionEngine ) // Initialize session engine on startup func init() { OnAppStart(initSessionEngine, 5) } func RegisterSessionEngine(f func() SessionEngine, name string) { sessionEngineMap[name] = f } // Called when application is starting up func initSessionEngine() { // Check for session engine to use and assign it sename := Config.StringDefault("session.engine", "revel-cookie") if se, found := sessionEngineMap[sename]; found { CurrentSessionEngine = se() } else { sessionLog.Warn("Session engine '%s' not found, using default session engine revel-cookie", sename) CurrentSessionEngine = sessionEngineMap["revel-cookie"]() } } revel-1.0.0/session_filter.go000066400000000000000000000014121370252312000161750ustar00rootroot00000000000000package revel // SessionFilter is a Revel Filter that retrieves and sets the session cookie. // Within Revel, it is available as a Session attribute on Controller instances. // The name of the Session cookie is set as CookiePrefix + "_SESSION". import () var sessionLog = RevelLog.New("section", "session") func SessionFilter(c *Controller, fc []Filter) { CurrentSessionEngine.Decode(c) sessionWasEmpty := c.Session.Empty() // Make session vars available in templates as {{.session.xyz}} c.ViewArgs["session"] = c.Session c.ViewArgs["_controller"] = c fc[0](c, fc[1:]) // If session is not empty or if session was not empty then // pass it back to the session engine to be encoded if !c.Session.Empty() || !sessionWasEmpty { CurrentSessionEngine.Encode(c) } } revel-1.0.0/template.go000066400000000000000000000364131370252312000147710ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package revel import ( "bufio" "bytes" "fmt" "io" "io/ioutil" "os" "path/filepath" "regexp" "strconv" "strings" "sync" "sync/atomic" ) // ErrorCSSClass httml CSS error class name var ErrorCSSClass = "hasError" // TemplateLoader object handles loading and parsing of templates. // Everything below the application's views directory is treated as a template. type TemplateLoader struct { // Paths to search for templates, in priority order. paths []string // load version seed for templates loadVersionSeed int // A templateRuntime of looked up template results runtimeLoader atomic.Value // Lock to prevent concurrent map writes templateMutex sync.Mutex } type Template interface { // The name of the template. Name() string // Name of template // The content of the template as a string (Used in error handling). Content() []string // Content // Called by the server to render the template out the io.Writer, context contains the view args to be passed to the template. Render(wr io.Writer, context interface{}) error // The full path to the file on the disk. Location() string // Disk location } var invalidSlugPattern = regexp.MustCompile(`[^a-z0-9 _-]`) var whiteSpacePattern = regexp.MustCompile(`\s+`) var templateLog = RevelLog.New("section", "template") // TemplateOutputArgs returns the result of the template rendered using the passed in arguments. func TemplateOutputArgs(templatePath string, args map[string]interface{}) (data []byte, err error) { // Get the Template. lang, _ := args[CurrentLocaleViewArg].(string) template, err := MainTemplateLoader.TemplateLang(templatePath, lang) if err != nil { return nil, err } tr := &RenderTemplateResult{ Template: template, ViewArgs: args, } b, err := tr.ToBytes() if err != nil { return nil, err } return b.Bytes(), nil } func NewTemplateLoader(paths []string) *TemplateLoader { loader := &TemplateLoader{ paths: paths, } return loader } // WatchDir returns true of directory doesn't start with . (dot) // otherwise false func (loader *TemplateLoader) WatchDir(info os.FileInfo) bool { // Watch all directories, except the ones starting with a dot. return !strings.HasPrefix(info.Name(), ".") } // WatchFile returns true of file doesn't start with . (dot) // otherwise false func (loader *TemplateLoader) WatchFile(basename string) bool { // Watch all files, except the ones starting with a dot. return !strings.HasPrefix(basename, ".") } // DEPRECATED Use TemplateLang, will be removed in future release func (loader *TemplateLoader) Template(name string) (tmpl Template, err error) { runtimeLoader := loader.runtimeLoader.Load().(*templateRuntime) return runtimeLoader.TemplateLang(name, "") } func (loader *TemplateLoader) TemplateLang(name, lang string) (tmpl Template, err error) { runtimeLoader := loader.runtimeLoader.Load().(*templateRuntime) return runtimeLoader.TemplateLang(name, lang) } // Refresh method scans the views directory and parses all templates as Go Templates. // If a template fails to parse, the error is set on the loader. // (It's awkward to refresh a single Go Template) func (loader *TemplateLoader) Refresh() (err *Error) { loader.templateMutex.Lock() defer loader.templateMutex.Unlock() loader.loadVersionSeed++ runtimeLoader := &templateRuntime{loader: loader, version: loader.loadVersionSeed, templateMap: map[string]Template{}} templateLog.Debug("Refresh: Refreshing templates from ", "path", loader.paths) if err = loader.initializeEngines(runtimeLoader, Config.StringDefault("template.engines", GO_TEMPLATE)); err != nil { return } for _, engine := range runtimeLoader.templatesAndEngineList { engine.Event(TEMPLATE_REFRESH_REQUESTED, nil) } RaiseEvent(TEMPLATE_REFRESH_REQUESTED, nil) defer func() { for _, engine := range runtimeLoader.templatesAndEngineList { engine.Event(TEMPLATE_REFRESH_COMPLETED, nil) } RaiseEvent(TEMPLATE_REFRESH_COMPLETED, nil) // Reset the runtimeLoader loader.runtimeLoader.Store(runtimeLoader) }() // Resort the paths, make sure the revel path is the last path, // so anything can override it revelTemplatePath := filepath.Join(RevelPath, "templates") // Go through the paths for i, o := range loader.paths { if o == revelTemplatePath && i != len(loader.paths)-1 { loader.paths[i] = loader.paths[len(loader.paths)-1] loader.paths[len(loader.paths)-1] = revelTemplatePath } } templateLog.Debug("Refresh: Refreshing templates from", "path", loader.paths) runtimeLoader.compileError = nil runtimeLoader.TemplatePaths = map[string]string{} for _, basePath := range loader.paths { // Walk only returns an error if the template loader is completely unusable // (namely, if one of the TemplateFuncs does not have an acceptable signature). // Handling symlinked directories var fullSrcDir string f, err := os.Lstat(basePath) if err == nil && f.Mode()&os.ModeSymlink == os.ModeSymlink { fullSrcDir, err = filepath.EvalSymlinks(basePath) if err != nil { templateLog.Panic("Refresh: Eval symlinks error ", "error", err) } } else { fullSrcDir = basePath } var templateWalker filepath.WalkFunc templateWalker = func(path string, info os.FileInfo, err error) error { if err != nil { templateLog.Error("Refresh: error walking templates:", "error", err) return nil } // Walk into watchable directories if info.IsDir() { if !loader.WatchDir(info) { return filepath.SkipDir } return nil } // Only add watchable if !loader.WatchFile(info.Name()) { return nil } fileBytes, err := runtimeLoader.findAndAddTemplate(path, fullSrcDir, basePath) if err != nil { // Add in this template name to the list of templates unable to be compiled runtimeLoader.compileErrorNameList = append(runtimeLoader.compileErrorNameList, filepath.ToSlash(path[len(fullSrcDir)+1:])) } // Store / report the first error encountered. if err != nil && runtimeLoader.compileError == nil { runtimeLoader.compileError, _ = err.(*Error) if nil == runtimeLoader.compileError { _, line, description := ParseTemplateError(err) runtimeLoader.compileError = &Error{ Title: "Template Compilation Error", Path: path, Description: description, Line: line, SourceLines: strings.Split(string(fileBytes), "\n"), } } templateLog.Errorf("Refresh: Template compilation error (In %s around line %d):\n\t%s", path, runtimeLoader.compileError.Line, err.Error()) } else if nil != err { //&& strings.HasPrefix(templateName, "errors/") { if compileError, ok := err.(*Error); ok { templateLog.Errorf("Template compilation error (In %s around line %d):\n\t%s", path, compileError.Line, err.Error()) } else { templateLog.Errorf("Template compilation error (In %s ):\n\t%s", path, err.Error()) } } return nil } if _, err = os.Lstat(fullSrcDir); os.IsNotExist(err) { // #1058 Given views/template path is not exists // so no need to walk, move on to next path continue } funcErr := Walk(fullSrcDir, templateWalker) // If there was an error with the Funcs, set it and return immediately. if funcErr != nil { runtimeLoader.compileError = NewErrorFromPanic(funcErr) return runtimeLoader.compileError } } // Note: compileError may or may not be set. return runtimeLoader.compileError } type templateRuntime struct { loader *TemplateLoader // load version for templates version int // Template data and implementation templatesAndEngineList []TemplateEngine // If an error was encountered parsing the templates, it is stored here. compileError *Error // A list of the names of the templates with errors compileErrorNameList []string // Map from template name to the path from whence it was loaded. TemplatePaths map[string]string // A map of looked up template results templateMap map[string]Template } // Checks to see if template exists in templatePaths, if so it is skipped (templates are imported in order // reads the template file into memory, replaces namespace keys with module (if found func (runtimeLoader *templateRuntime) findAndAddTemplate(path, fullSrcDir, basePath string) (fileBytes []byte, err error) { templateName := filepath.ToSlash(path[len(fullSrcDir)+1:]) // Convert template names to use forward slashes, even on Windows. if os.PathSeparator == '\\' { templateName = strings.Replace(templateName, `\`, `/`, -1) // ` } // Check to see if template was found if place, found := runtimeLoader.TemplatePaths[templateName]; found { templateLog.Debug("findAndAddTemplate: Not Loading, template is already exists: ", "name", templateName, "old", place, "new", path) return } fileBytes, err = ioutil.ReadFile(path) if err != nil { templateLog.Error("findAndAddTemplate: Failed reading file:", "path", path, "error", err) return } // Parse template file and replace the "_LOCAL_|" in the template with the module name // allow for namespaces to be renamed "_LOCAL_(.*?)|" if module := ModuleFromPath(path, false); module != nil { fileBytes = namespaceReplace(fileBytes, module) } // if we have an engine picked for this template process it now baseTemplate := NewBaseTemplate(templateName, path, basePath, fileBytes) // Try to find a default engine for the file for _, engine := range runtimeLoader.templatesAndEngineList { if engine.Handles(baseTemplate) { _, err = runtimeLoader.loadIntoEngine(engine, baseTemplate) return } } // Try all engines available var defaultError error for _, engine := range runtimeLoader.templatesAndEngineList { if loaded, loaderr := runtimeLoader.loadIntoEngine(engine, baseTemplate); loaded { return } else { templateLog.Debugf("findAndAddTemplate: Engine '%s' unable to compile %s %s", engine.Name(), path, loaderr.Error()) if defaultError == nil { defaultError = loaderr } } } // Assign the error from the first parser err = defaultError // No engines could be found return the err if err == nil { err = fmt.Errorf("Failed to parse template file using engines %s", path) } return } func (runtimeLoader *templateRuntime) loadIntoEngine(engine TemplateEngine, baseTemplate *TemplateView) (loaded bool, err error) { if loadedTemplate, found := runtimeLoader.templateMap[baseTemplate.TemplateName]; found { // Duplicate template found in map templateLog.Debug("template already exists in map: ", baseTemplate.TemplateName, " in engine ", engine.Name(), "\r\n\told file:", loadedTemplate.Location(), "\r\n\tnew file:", baseTemplate.FilePath) return } if loadedTemplate := engine.Lookup(baseTemplate.TemplateName); loadedTemplate != nil { // Duplicate template found for engine templateLog.Debug("loadIntoEngine: template already exists: ", "template", baseTemplate.TemplateName, "inengine ", engine.Name(), "old", loadedTemplate.Location(), "new", baseTemplate.FilePath) loaded = true return } if err = engine.ParseAndAdd(baseTemplate); err == nil { if tmpl := engine.Lookup(baseTemplate.TemplateName); tmpl != nil { runtimeLoader.templateMap[baseTemplate.TemplateName] = tmpl } runtimeLoader.TemplatePaths[baseTemplate.TemplateName] = baseTemplate.FilePath templateLog.Debugf("loadIntoEngine:Engine '%s' compiled %s", engine.Name(), baseTemplate.FilePath) loaded = true } else { templateLog.Debug("loadIntoEngine: Engine failed to compile", "engine", engine.Name(), "file", baseTemplate.FilePath, "error", err) } return } // Parse the line, and description from an error message like: // html/template:Application/Register.html:36: no such template "footer.html" func ParseTemplateError(err error) (templateName string, line int, description string) { if e, ok := err.(*Error); ok { return "", e.Line, e.Description } description = err.Error() i := regexp.MustCompile(`:\d+:`).FindStringIndex(description) if i != nil { line, err = strconv.Atoi(description[i[0]+1 : i[1]-1]) if err != nil { templateLog.Error("ParseTemplateError: Failed to parse line number from error message:", "error", err) } templateName = description[:i[0]] if colon := strings.Index(templateName, ":"); colon != -1 { templateName = templateName[colon+1:] } templateName = strings.TrimSpace(templateName) description = description[i[1]+1:] } return templateName, line, description } // Template returns the Template with the given name. The name is the template's path // relative to a template loader root. // // An Error is returned if there was any problem with any of the templates. (In // this case, if a template is returned, it may still be usable.) func (runtimeLoader *templateRuntime) TemplateLang(name, lang string) (tmpl Template, err error) { if runtimeLoader.compileError != nil { for _, errName := range runtimeLoader.compileErrorNameList { if name == errName { return nil, runtimeLoader.compileError } } } // Fetch the template from the map tmpl = runtimeLoader.templateLoad(name, lang) if tmpl == nil { err = fmt.Errorf("Template %s not found.", name) } return } // Load and also updates map if name is not found (to speed up next lookup) func (runtimeLoader *templateRuntime) templateLoad(name, lang string) (tmpl Template) { langName := name found := false if lang != "" { // Look up and return the template. langName = name + "." + lang tmpl, found = runtimeLoader.templateMap[langName] if found { return } tmpl, found = runtimeLoader.templateMap[name] } else { tmpl, found = runtimeLoader.templateMap[name] if found { return } } if !found { // Neither name is found // Look up and return the template. for _, engine := range runtimeLoader.templatesAndEngineList { if tmpl = engine.Lookup(langName); tmpl != nil { found = true break } if tmpl = engine.Lookup(name); tmpl != nil { found = true break } } if !found { return } } // If we found anything store it in the map, we need to copy so we do not // run into concurrency issues runtimeLoader.loader.templateMutex.Lock() defer runtimeLoader.loader.templateMutex.Unlock() // In case another thread has loaded the map, reload the atomic value and check newRuntimeLoader := runtimeLoader.loader.runtimeLoader.Load().(*templateRuntime) if newRuntimeLoader.version != runtimeLoader.version { return } newTemplateMap := map[string]Template{} for k, v := range newRuntimeLoader.templateMap { newTemplateMap[k] = v } newTemplateMap[langName] = tmpl if _, found := newTemplateMap[name]; !found { newTemplateMap[name] = tmpl } runtimeCopy := &templateRuntime{} *runtimeCopy = *newRuntimeLoader runtimeCopy.templateMap = newTemplateMap // Set the atomic value runtimeLoader.loader.runtimeLoader.Store(runtimeCopy) return } func (i *TemplateView) Location() string { return i.FilePath } func (i *TemplateView) Content() (content []string) { if i.FileBytes != nil { // Parse the bytes buffer := bytes.NewBuffer(i.FileBytes) reader := bufio.NewScanner(buffer) for reader.Scan() { content = append(content, string(reader.Bytes())) } } return content } func NewBaseTemplate(templateName, filePath, basePath string, fileBytes []byte) *TemplateView { return &TemplateView{TemplateName: templateName, FilePath: filePath, FileBytes: fileBytes, BasePath: basePath} } revel-1.0.0/template_adapter_go.go000066400000000000000000000076711370252312000171620ustar00rootroot00000000000000package revel import ( "html/template" "io" "log" "strings" ) const GO_TEMPLATE = "go" // Called on startup, initialized when the REVEL_BEFORE_MODULES_LOADED is called func init() { AddInitEventHandler(func(typeOf Event, value interface{}) (responseOf EventResponse) { if typeOf == REVEL_BEFORE_MODULES_LOADED { RegisterTemplateLoader(GO_TEMPLATE, func(loader *TemplateLoader) (TemplateEngine, error) { // Set the template delimiters for the project if present, then split into left // and right delimiters around a space character TemplateDelims := Config.StringDefault("template.go.delimiters", "") var splitDelims []string if TemplateDelims != "" { splitDelims = strings.Split(TemplateDelims, " ") if len(splitDelims) != 2 { log.Fatalln("app.conf: Incorrect format for template.delimiters") } } return &GoEngine{ loader: loader, templateSet: template.New("__root__").Funcs(TemplateFuncs), templatesByName: map[string]*GoTemplate{}, splitDelims: splitDelims, }, nil }) } return }) } // Adapter for Go Templates. type GoTemplate struct { *template.Template engine *GoEngine *TemplateView } // return a 'revel.Template' from Go's template. func (gotmpl GoTemplate) Render(wr io.Writer, arg interface{}) error { return gotmpl.Execute(wr, arg) } // The main template engine for Go type GoEngine struct { // The template loader loader *TemplateLoader // THe current template set templateSet *template.Template // A map of templates by name templatesByName map[string]*GoTemplate // The delimiter that is used to indicate template code, defaults to {{ splitDelims []string // True if map is case insensitive CaseInsensitive bool } // Convert the path to lower case if needed func (i *GoEngine) ConvertPath(path string) string { if i.CaseInsensitive { return strings.ToLower(path) } return path } // Returns true if this engine can handle the response func (i *GoEngine) Handles(templateView *TemplateView) bool { return EngineHandles(i, templateView) } // Parses the template vide and adds it to the template set func (engine *GoEngine) ParseAndAdd(baseTemplate *TemplateView) error { // If alternate delimiters set for the project, change them for this set if engine.splitDelims != nil && strings.Index(baseTemplate.Location(), ViewsPath) > -1 { engine.templateSet.Delims(engine.splitDelims[0], engine.splitDelims[1]) } else { // Reset to default otherwise engine.templateSet.Delims("", "") } templateSource := string(baseTemplate.FileBytes) templateName := engine.ConvertPath(baseTemplate.TemplateName) tpl, err := engine.templateSet.New(baseTemplate.TemplateName).Parse(templateSource) if nil != err { _, line, description := ParseTemplateError(err) return &Error{ Title: "Template Compilation Error", Path: baseTemplate.TemplateName, Description: description, Line: line, SourceLines: strings.Split(templateSource, "\n"), } } engine.templatesByName[templateName] = &GoTemplate{Template: tpl, engine: engine, TemplateView: baseTemplate} return nil } // Lookups the template name, to see if it is contained in this engine func (engine *GoEngine) Lookup(templateName string) Template { // Case-insensitive matching of template file name if tpl, found := engine.templatesByName[engine.ConvertPath(templateName)]; found { return tpl } return nil } // Return the engine name func (engine *GoEngine) Name() string { return GO_TEMPLATE } // An event listener to listen for Revel INIT events func (engine *GoEngine) Event(action Event, i interface{}) { if action == TEMPLATE_REFRESH_REQUESTED { // At this point all the templates have been passed into the engine.templatesByName = map[string]*GoTemplate{} engine.templateSet = template.New("__root__").Funcs(TemplateFuncs) // Check to see what should be used for case sensitivity engine.CaseInsensitive = Config.BoolDefault("go.template.caseinsensitive", true) } } revel-1.0.0/template_engine.go000066400000000000000000000101071370252312000163060ustar00rootroot00000000000000package revel import ( "bufio" "bytes" "errors" "fmt" "path/filepath" "strings" ) type TemplateEngine interface { // prase template string and add template to the set. ParseAndAdd(basePath *TemplateView) error // returns Template corresponding to the given templateName, or nil Lookup(templateName string) Template // Fired by the template loader when events occur Event(event Event, arg interface{}) // returns true if this engine should be used to parse the file specified in baseTemplate Handles(templateView *TemplateView) bool // returns the name of the engine Name() string } // The template view information type TemplateView struct { TemplateName string // The name of the view FilePath string // The file path (view relative) BasePath string // The file system base path FileBytes []byte // The file loaded EngineType string // The name of the engine used to render the view } var templateLoaderMap = map[string]func(loader *TemplateLoader) (TemplateEngine, error){} // Allow for templates to be registered during init but not initialized until application has been started func RegisterTemplateLoader(key string, loader func(loader *TemplateLoader) (TemplateEngine, error)) (err error) { if _, found := templateLoaderMap[key]; found { err = fmt.Errorf("Template loader %s already exists", key) } templateLog.Debug("Registered template engine loaded", "name", key) templateLoaderMap[key] = loader return } // Sets the template name from Config // Sets the template API methods for parsing and storing templates before rendering func (loader *TemplateLoader) CreateTemplateEngine(templateEngineName string) (TemplateEngine, error) { if "" == templateEngineName { templateEngineName = GO_TEMPLATE } factory := templateLoaderMap[templateEngineName] if nil == factory { fmt.Printf("registered factories %#v\n %s \n", templateLoaderMap, templateEngineName) return nil, errors.New("Unknown template engine name - " + templateEngineName + ".") } templateEngine, err := factory(loader) if nil != err { return nil, errors.New("Failed to init template engine (" + templateEngineName + "), " + err.Error()) } templateLog.Debug("CreateTemplateEngine: init templates", "name", templateEngineName) return templateEngine, nil } // Passing in a comma delimited list of engine names to be used with this loader to parse the template files func (loader *TemplateLoader) initializeEngines(runtimeLoader *templateRuntime, templateEngineNameList string) (err *Error) { // Walk through the template loader's paths and build up a template set. if templateEngineNameList == "" { templateEngineNameList = GO_TEMPLATE } runtimeLoader.templatesAndEngineList = []TemplateEngine{} for _, engine := range strings.Split(templateEngineNameList, ",") { engine := strings.TrimSpace(strings.ToLower(engine)) if templateLoader, err := loader.CreateTemplateEngine(engine); err != nil { runtimeLoader.compileError = &Error{ Title: "Panic (Template Loader)", Description: err.Error(), } return runtimeLoader.compileError } else { // Always assign a default engine, switch it if it is specified in the config runtimeLoader.templatesAndEngineList = append(runtimeLoader.templatesAndEngineList, templateLoader) } } return } func EngineHandles(engine TemplateEngine, templateView *TemplateView) bool { if line, _, e := bufio.NewReader(bytes.NewBuffer(templateView.FileBytes)).ReadLine(); e == nil && string(line[:3]) == "#! " { // Extract the shebang and look at the rest of the line // #! pong2 // #! go templateType := strings.TrimSpace(string(line[2:])) if engine.Name() == templateType { // Advance the read file bytes so it does not include the shebang templateView.FileBytes = templateView.FileBytes[len(line)+1:] templateView.EngineType = templateType return true } } filename := filepath.Base(templateView.FilePath) bits := strings.Split(filename, ".") if len(bits) > 2 { templateType := strings.TrimSpace(bits[len(bits)-2]) if engine.Name() == templateType { templateView.EngineType = templateType return true } } return false } revel-1.0.0/template_functions.go000066400000000000000000000243431370252312000170600ustar00rootroot00000000000000package revel import ( "bytes" "errors" "fmt" "github.com/xeonx/timeago" "html" "html/template" "reflect" "strings" "time" ) var ( // The functions available for use in the templates. TemplateFuncs = map[string]interface{}{ "url": ReverseURL, "set": func(viewArgs map[string]interface{}, key string, value interface{}) template.JS { viewArgs[key] = value return template.JS("") }, "append": func(viewArgs map[string]interface{}, key string, value interface{}) template.JS { if viewArgs[key] == nil { viewArgs[key] = []interface{}{value} } else { viewArgs[key] = append(viewArgs[key].([]interface{}), value) } return template.JS("") }, "field": NewField, "firstof": func(args ...interface{}) interface{} { for _, val := range args { switch val.(type) { case nil: continue case string: if val == "" { continue } return val default: return val } } return nil }, "option": func(f *Field, val interface{}, label string) template.HTML { selected := "" if f.Flash() == val || (f.Flash() == "" && f.Value() == val) { selected = " selected" } return template.HTML(fmt.Sprintf(``, html.EscapeString(fmt.Sprintf("%v", val)), selected, html.EscapeString(label))) }, "radio": func(f *Field, val string) template.HTML { checked := "" if f.Flash() == val { checked = " checked" } return template.HTML(fmt.Sprintf(``, html.EscapeString(f.Name), html.EscapeString(val), checked)) }, "checkbox": func(f *Field, val string) template.HTML { checked := "" if f.Flash() == val { checked = " checked" } return template.HTML(fmt.Sprintf(``, html.EscapeString(f.Name), html.EscapeString(val), checked)) }, // Pads the given string with  's up to the given width. "pad": func(str string, width int) template.HTML { if len(str) >= width { return template.HTML(html.EscapeString(str)) } return template.HTML(html.EscapeString(str) + strings.Repeat(" ", width-len(str))) }, "errorClass": func(name string, viewArgs map[string]interface{}) template.HTML { errorMap, ok := viewArgs["errors"].(map[string]*ValidationError) if !ok || errorMap == nil { templateLog.Warn("errorClass: Called 'errorClass' without 'errors' in the view args.") return template.HTML("") } valError, ok := errorMap[name] if !ok || valError == nil { return template.HTML("") } return template.HTML(ErrorCSSClass) }, "msg": func(viewArgs map[string]interface{}, message string, args ...interface{}) template.HTML { str, ok := viewArgs[CurrentLocaleViewArg].(string) if !ok { return "" } return template.HTML(MessageFunc(str, message, args...)) }, // Replaces newlines with
"nl2br": func(text string) template.HTML { return template.HTML(strings.Replace(template.HTMLEscapeString(text), "\n", "
", -1)) }, // Skips sanitation on the parameter. Do not use with dynamic data. "raw": func(text string) template.HTML { return template.HTML(text) }, // Pluralize, a helper for pluralizing words to correspond to data of dynamic length. // items - a slice of items, or an integer indicating how many items there are. // pluralOverrides - optional arguments specifying the output in the // singular and plural cases. by default "" and "s" "pluralize": func(items interface{}, pluralOverrides ...string) string { singular, plural := "", "s" if len(pluralOverrides) >= 1 { singular = pluralOverrides[0] if len(pluralOverrides) == 2 { plural = pluralOverrides[1] } } switch v := reflect.ValueOf(items); v.Kind() { case reflect.Int: if items.(int) != 1 { return plural } case reflect.Slice: if v.Len() != 1 { return plural } default: templateLog.Error("pluralize: unexpected type: ", "value", v) } return singular }, // Format a date according to the application's default date(time) format. "date": func(date time.Time) string { return date.Format(DateFormat) }, "datetime": func(date time.Time) string { return date.Format(DateTimeFormat) }, // Fetch an object from the session. "session": func(key string, viewArgs map[string]interface{}) interface{} { if viewArgs != nil { if c, found := viewArgs["_controller"]; found { if v, err := c.(*Controller).Session.Get(key); err == nil { return v } else { templateLog.Errorf("template.session, key %s error %v", key, err) } } else { templateLog.Warnf("template.session, key %s requested without controller", key) } } else { templateLog.Warnf("template.session, key %s requested passing in view args", key) } return "" }, "slug": Slug, "even": func(a int) bool { return (a % 2) == 0 }, // Using https://github.com/xeonx/timeago "timeago": TimeAgo, "i18ntemplate": func(args ...interface{}) (template.HTML, error) { templateName, lang := "", "" var viewArgs interface{} switch len(args) { case 0: templateLog.Error("i18ntemplate: No arguments passed to template call") case 1: // Assume only the template name is passed in templateName = args[0].(string) case 2: // Assume template name and viewArgs is passed in templateName = args[0].(string) viewArgs = args[1] // Try to extract language from the view args if viewargsmap, ok := viewArgs.(map[string]interface{}); ok { lang, _ = viewargsmap[CurrentLocaleViewArg].(string) } default: // Assume third argument is the region templateName = args[0].(string) viewArgs = args[1] lang, _ = args[2].(string) if len(args) > 3 { templateLog.Error("i18ntemplate: Received more parameters then needed for", "template", templateName) } } var buf bytes.Buffer // Get template tmpl, err := MainTemplateLoader.TemplateLang(templateName, lang) if err == nil { err = tmpl.Render(&buf, viewArgs) } else { templateLog.Error("i18ntemplate: Failed to render i18ntemplate ", "name", templateName, "error", err) } return template.HTML(buf.String()), err }, } ) ///////////////////// // Template functions ///////////////////// // ReverseURL returns a url capable of invoking a given controller method: // "Application.ShowApp 123" => "/app/123" func ReverseURL(args ...interface{}) (template.URL, error) { if len(args) == 0 { return "", errors.New("no arguments provided to reverse route") } action := args[0].(string) if action == "Root" { return template.URL(AppRoot), nil } pathData, found := splitActionPath(nil, action, true) if !found { return "", fmt.Errorf("reversing '%s', expected 'Controller.Action'", action) } // Look up the types. if pathData.TypeOfController == nil { return "", fmt.Errorf("Failed reversing %s: controller not found %#v", action, pathData) } // Note method name is case insensitive search methodType := pathData.TypeOfController.Method(pathData.MethodName) if methodType == nil { return "", errors.New("revel/controller: In " + action + " failed to find function " + pathData.MethodName) } if len(methodType.Args) < len(args)-1 { return "", fmt.Errorf("reversing %s: route defines %d args, but received %d", action, len(methodType.Args), len(args)-1) } // Unbind the arguments. argsByName := make(map[string]string) // Bind any static args first fixedParams := len(pathData.FixedParamsByName) for i, argValue := range args[1:] { Unbind(argsByName, methodType.Args[i+fixedParams].Name, argValue) } return template.URL(MainRouter.Reverse(args[0].(string), argsByName).URL), nil } func Slug(text string) string { separator := "-" text = strings.ToLower(text) text = invalidSlugPattern.ReplaceAllString(text, "") text = whiteSpacePattern.ReplaceAllString(text, separator) text = strings.Trim(text, separator) return text } var timeAgoLangs = map[string]timeago.Config{} func TimeAgo(args ...interface{}) string { datetime := time.Now() lang := "" var viewArgs interface{} switch len(args) { case 0: templateLog.Error("TimeAgo: No arguments passed to timeago") case 1: // only the time is passed in datetime = args[0].(time.Time) case 2: // time and region is passed in datetime = args[0].(time.Time) switch v := reflect.ValueOf(args[1]); v.Kind() { case reflect.String: // second params type string equals region lang, _ = args[1].(string) case reflect.Map: // second params type map equals viewArgs viewArgs = args[1] if viewargsmap, ok := viewArgs.(map[string]interface{}); ok { lang, _ = viewargsmap[CurrentLocaleViewArg].(string) } default: templateLog.Error("TimeAgo: unexpected type: ", "value", v) } default: // Assume third argument is the region datetime = args[0].(time.Time) if reflect.ValueOf(args[1]).Kind() != reflect.Map { templateLog.Error("TimeAgo: unexpected type", "value", args[1]) } if reflect.ValueOf(args[2]).Kind() != reflect.String { templateLog.Error("TimeAgo: unexpected type: ", "value", args[2]) } viewArgs = args[1] lang, _ = args[2].(string) if len(args) > 3 { templateLog.Error("TimeAgo: Received more parameters then needed for timeago") } } if lang == "" { lang, _ = Config.String(defaultLanguageOption) if lang == "en" { timeAgoLangs[lang] = timeago.English } } _, ok := timeAgoLangs[lang] if !ok { timeAgoLangs[lang] = timeago.Config{ PastPrefix: "", PastSuffix: " " + MessageFunc(lang, "ago"), FuturePrefix: MessageFunc(lang, "in") + " ", FutureSuffix: "", Periods: []timeago.FormatPeriod{ {time.Second, MessageFunc(lang, "about a second"), MessageFunc(lang, "%d seconds")}, {time.Minute, MessageFunc(lang, "about a minute"), MessageFunc(lang, "%d minutes")}, {time.Hour, MessageFunc(lang, "about an hour"), MessageFunc(lang, "%d hours")}, {timeago.Day, MessageFunc(lang, "one day"), MessageFunc(lang, "%d days")}, {timeago.Month, MessageFunc(lang, "one month"), MessageFunc(lang, "%d months")}, {timeago.Year, MessageFunc(lang, "one year"), MessageFunc(lang, "%d years")}, }, Zero: MessageFunc(lang, "about a second"), Max: 73 * time.Hour, DefaultLayout: "2006-01-02", } } return timeAgoLangs[lang].Format(datetime) } revel-1.0.0/templates/000077500000000000000000000000001370252312000146165ustar00rootroot00000000000000revel-1.0.0/templates/errors/000077500000000000000000000000001370252312000161325ustar00rootroot00000000000000revel-1.0.0/templates/errors/403.html000066400000000000000000000002701370252312000173250ustar00rootroot00000000000000 Forbidden {{with .Error}}

{{.Title}}

{{.Description}}

{{end}} revel-1.0.0/templates/errors/403.json000066400000000000000000000001271370252312000173330ustar00rootroot00000000000000{ "title": "{{js .Error.Title}}", "description": "{{js .Error.Description}}" } revel-1.0.0/templates/errors/403.txt000066400000000000000000000000511370252312000171750ustar00rootroot00000000000000{{.Error.Title}} {{.Error.Description}} revel-1.0.0/templates/errors/403.xml000066400000000000000000000000561370252312000171630ustar00rootroot00000000000000{{.Error.Description}} revel-1.0.0/templates/errors/404-dev.html000066400000000000000000000017431370252312000201100ustar00rootroot00000000000000

These routes have been tried, in this order :

    {{range .Router.Routes}}
  1. {{pad .Method 10}}{{pad .Path 50}}{{.Action}} {{with .ModuleSource}}(Route Module:{{.Name}}){{end}}
  2. {{end}}
revel-1.0.0/templates/errors/404.html000066400000000000000000000004041370252312000173250ustar00rootroot00000000000000 Not found {{if .DevMode}} {{template "errors/404-dev.html" .}} {{else}} {{with .Error}}

{{.Title}}

{{.Description}}

{{end}} {{end}} revel-1.0.0/templates/errors/404.json000066400000000000000000000001271370252312000173340ustar00rootroot00000000000000{ "title": "{{js .Error.Title}}", "description": "{{js .Error.Description}}" } revel-1.0.0/templates/errors/404.txt000066400000000000000000000000511370252312000171760ustar00rootroot00000000000000{{.Error.Title}} {{.Error.Description}} revel-1.0.0/templates/errors/404.xml000066400000000000000000000000541370252312000171620ustar00rootroot00000000000000{{.Error.Description}} revel-1.0.0/templates/errors/405.html000066400000000000000000000003011370252312000173220ustar00rootroot00000000000000 Method not allowed {{with .Error}}

{{.Title}}

{{.Description}}

{{end}} revel-1.0.0/templates/errors/405.json000066400000000000000000000001271370252312000173350ustar00rootroot00000000000000{ "title": "{{js .Error.Title}}", "description": "{{js .Error.Description}}" } revel-1.0.0/templates/errors/405.txt000066400000000000000000000000511370252312000171770ustar00rootroot00000000000000{{.Error.Title}} {{.Error.Description}} revel-1.0.0/templates/errors/405.xml000066400000000000000000000001001370252312000171530ustar00rootroot00000000000000{{.Error.Description}} revel-1.0.0/templates/errors/500-dev.html000066400000000000000000000047121370252312000201040ustar00rootroot00000000000000 {{with .Error}} {{if .Path}}

In {{.Path}} {{if .Line}} (around {{if .Line}}line {{.Line}}{{end}}{{if .Column}} column {{.Column}}{{end}}) {{end}}

{{range .ContextSource}}
{{.Line}}:
{{.Source}}
{{end}}
{{end}} {{if .Stack}}

Call Stack

{{.Stack}}
{{end}} {{if .MetaError}}

Additionally, an error occurred while handling this error.

{{.MetaError}}
{{end}} {{end}} revel-1.0.0/templates/errors/500.html000066400000000000000000000004041370252312000173220ustar00rootroot00000000000000 Application error {{if .DevMode}} {{template "errors/500-dev.html" .}} {{else}}

Oops, an error occured

This exception has been logged.

{{end}} revel-1.0.0/templates/errors/500.json000066400000000000000000000001271370252312000173310ustar00rootroot00000000000000{ "title": "{{js .Error.Title}}", "description": "{{js .Error.Description}}" } revel-1.0.0/templates/errors/500.txt000066400000000000000000000004241370252312000171770ustar00rootroot00000000000000{{.Error.Title}} {{.Error.Description}} {{if eq .RunMode "dev"}} {{with .Error}} {{if .Path}} ---------- In {{.Path}} {{if .Line}}(around line {{.Line}}){{end}} {{range .ContextSource}} {{if .IsError}}>{{else}} {{end}} {{.Line}}: {{.Source}}{{end}} {{end}} {{end}} {{end}} revel-1.0.0/templates/errors/500.xml000066400000000000000000000001451370252312000171600ustar00rootroot00000000000000 {{.Error.Title}} {{.Error.Description}} revel-1.0.0/testdata/000077500000000000000000000000001370252312000144315ustar00rootroot00000000000000revel-1.0.0/testdata/app/000077500000000000000000000000001370252312000152115ustar00rootroot00000000000000revel-1.0.0/testdata/app/views/000077500000000000000000000000001370252312000163465ustar00rootroot00000000000000revel-1.0.0/testdata/app/views/footer.html000066400000000000000000000006301370252312000205310ustar00rootroot00000000000000 revel-1.0.0/testdata/app/views/header.html000066400000000000000000000024731370252312000204720ustar00rootroot00000000000000 {{.title}} {{range .moreStyles}} {{end}} {{range .moreScripts}} {{end}}
{{if .flash.error}}

{{.flash.error}}

{{end}} {{if .flash.success}}

{{.flash.success}}

{{end}} revel-1.0.0/testdata/app/views/hotels/000077500000000000000000000000001370252312000176445ustar00rootroot00000000000000revel-1.0.0/testdata/app/views/hotels/show.html000066400000000000000000000012271370252312000215140ustar00rootroot00000000000000{{template "header.html" .}}

View hotel

{{with .hotel}}

Name: {{.Name}}

Address: {{.Address}}

City: {{.City}}

State: {{.State}}

Zip: {{.Zip}}

Country: {{.Country}}

Nightly rate: {{.Price}}

Back to search

{{end}} {{template "footer.html" .}} revel-1.0.0/testdata/conf/000077500000000000000000000000001370252312000153565ustar00rootroot00000000000000revel-1.0.0/testdata/conf/app.conf000066400000000000000000000014111370252312000170020ustar00rootroot00000000000000# Application app.name=Booking example app.secret=secret # Server http.addr= http.port=9000 http.ssl=false http.sslcert= http.sslkey= # Logging log.trace.output = stderr log.info.output = stderr log.warn.output = stderr log.error.output = stderr log.trace.prefix = "TRACE " log.info.prefix = "INFO " log.warn.prefix = "WARN " log.error.prefix = "ERROR " db.import = github.com/mattn/go-sqlite3 db.driver = sqlite3 db.spec = :memory: build.tags=gorp # module.jobs=github.com/revel/modules/jobs module.static=github.com/revel/modules/static [dev] mode.dev=true watch=true module.testrunner=github.com/revel/modules/testrunner [prod] watch=false module.testrunner= log.trace.output = off log.info.output = off log.warn.output = stderr log.error.output = stderr revel-1.0.0/testdata/conf/routes000066400000000000000000000011341370252312000166210ustar00rootroot00000000000000# Routes # This file defines all application routes (Higher priority routes first) # ~~~~ module:testrunner GET /hotels Hotels.Index GET /hotels/:id Hotels.Show GET /hotels/:id/booking Hotels.Book # Map static resources from the /app/public folder to the /public path GET /public/*filepath Static.Serve("public") GET /favicon.ico Static.Serve("public/img","favicon.png") # Catch all * /:controller/:action :controller.:action revel-1.0.0/testdata/i18n/000077500000000000000000000000001370252312000152105ustar00rootroot00000000000000revel-1.0.0/testdata/i18n/config/000077500000000000000000000000001370252312000164555ustar00rootroot00000000000000revel-1.0.0/testdata/i18n/config/test_app.conf000066400000000000000000000011411370252312000211400ustar00rootroot00000000000000app.name={{ .AppName }} app.secret={{ .Secret }} http.addr= http.port=9000 cookie.prefix=REVEL i18n.default_language=en i18n.cookie=APP_LANG [dev] results.pretty=true results.staging=true watch=true module.testrunner = github.com/revel/modules/testrunner module.static=github.com/revel/modules/static log.trace.output = off log.info.output = stderr log.warn.output = stderr log.error.output = stderr [prod] results.pretty=false results.staging=false watch=false module.testrunner = log.trace.output = off log.info.output = off log.warn.output = %(app.name)s.log log.error.output = %(app.name)s.log revel-1.0.0/testdata/i18n/messages/000077500000000000000000000000001370252312000170175ustar00rootroot00000000000000revel-1.0.0/testdata/i18n/messages/dutch_messages.nl000066400000000000000000000001661370252312000223530ustar00rootroot00000000000000greeting=Hallo greeting.name=Rob greeting.suffix=, welkom bij Revel! [NL] greeting=Goeiedag [BE] greeting=Hallokes revel-1.0.0/testdata/i18n/messages/english_messages.en000066400000000000000000000006031370252312000226620ustar00rootroot00000000000000greeting=Hello greeting.name=Rob greeting.suffix=, welcome to Revel! folded=Greeting is '%(greeting)s' folded.arguments=%(greeting.name)s is %d years old arguments.string=My name is %s arguments.hex=The number %d in hexadecimal notation would be %x arguments.none=No arguments here son only_exists_in_default=Default [AU] greeting=G'day [US] greeting=Howdy [GB] greeting=All rightrevel-1.0.0/testdata/i18n/messages/english_messages2.en000066400000000000000000000000161370252312000227420ustar00rootroot00000000000000greeting2=Yo! revel-1.0.0/testdata/i18n/messages/invalid_message_file_name.txt000066400000000000000000000000001370252312000246770ustar00rootroot00000000000000revel-1.0.0/testdata/public/000077500000000000000000000000001370252312000157075ustar00rootroot00000000000000revel-1.0.0/testdata/public/js/000077500000000000000000000000001370252312000163235ustar00rootroot00000000000000revel-1.0.0/testdata/public/js/sessvars.js000066400000000000000000000000321370252312000205250ustar00rootroot00000000000000console.log('Test file'); revel-1.0.0/testing/000077500000000000000000000000001370252312000142755ustar00rootroot00000000000000revel-1.0.0/testing/testsuite.go000066400000000000000000000311451370252312000166610ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package testing import ( "bytes" "fmt" "io" "io/ioutil" "mime" "mime/multipart" "net/http" "net/http/cookiejar" "net/textproto" "net/url" "os" "path/filepath" "regexp" "strings" "github.com/revel/revel" "github.com/revel/revel/session" "golang.org/x/net/websocket" "net/http/httptest" ) type TestSuite struct { Client *http.Client Response *http.Response ResponseBody []byte Session session.Session SessionEngine revel.SessionEngine } type TestRequest struct { *http.Request testSuite *TestSuite } // This is populated by the generated code in the run/run/go file var TestSuites []interface{} // Array of structs that embed TestSuite // NewTestSuite returns an initialized TestSuite ready for use. It is invoked // by the test harness to initialize the embedded field in application tests. func NewTestSuite() TestSuite { return NewTestSuiteEngine(revel.NewSessionCookieEngine()) } // Define a new test suite with a custom session engine func NewTestSuiteEngine(engine revel.SessionEngine) TestSuite { jar, _ := cookiejar.New(nil) ts := TestSuite{ Client: &http.Client{Jar: jar}, Session: session.NewSession(), SessionEngine: engine, } return ts } // NewTestRequest returns an initialized *TestRequest. It is used for extending // testsuite package making it possibe to define own methods. Example: // type MyTestSuite struct { // testing.TestSuite // } // // func (t *MyTestSuite) PutFormCustom(...) { // req := http.NewRequest(...) // ... // return t.NewTestRequest(req) // } func (t *TestSuite) NewTestRequest(req *http.Request) *TestRequest { request := &TestRequest{ Request: req, testSuite: t, } return request } // Host returns the address and port of the server, e.g. "127.0.0.1:8557" func (t *TestSuite) Host() string { if revel.ServerEngineInit.Address[0] == ':' { return "127.0.0.1" + revel.ServerEngineInit.Address } return revel.ServerEngineInit.Address } // BaseUrl returns the base http/https URL of the server, e.g. "http://127.0.0.1:8557". // The scheme is set to https if http.ssl is set to true in the configuration file. func (t *TestSuite) BaseUrl() string { if revel.HTTPSsl { return "https://" + t.Host() } return "http://" + t.Host() } // WebSocketUrl returns the base websocket URL of the server, e.g. "ws://127.0.0.1:8557" func (t *TestSuite) WebSocketUrl() string { return "ws://" + t.Host() } // Get issues a GET request to the given path and stores the result in Response // and ResponseBody. func (t *TestSuite) Get(path string) { t.GetCustom(t.BaseUrl() + path).Send() } // GetCustom returns a GET request to the given URI in a form of its wrapper. func (t *TestSuite) GetCustom(uri string) *TestRequest { req, err := http.NewRequest("GET", uri, nil) if err != nil { panic(err) } return t.NewTestRequest(req) } // Delete issues a DELETE request to the given path and stores the result in // Response and ResponseBody. func (t *TestSuite) Delete(path string) { t.DeleteCustom(t.BaseUrl() + path).Send() } // DeleteCustom returns a DELETE request to the given URI in a form of its // wrapper. func (t *TestSuite) DeleteCustom(uri string) *TestRequest { req, err := http.NewRequest("DELETE", uri, nil) if err != nil { panic(err) } return t.NewTestRequest(req) } // Put issues a PUT request to the given path, sending the given Content-Type // and data, storing the result in Response and ResponseBody. "data" may be nil. func (t *TestSuite) Put(path string, contentType string, reader io.Reader) { t.PutCustom(t.BaseUrl()+path, contentType, reader).Send() } // PutCustom returns a PUT request to the given URI with specified Content-Type // and data in a form of wrapper. "data" may be nil. func (t *TestSuite) PutCustom(uri string, contentType string, reader io.Reader) *TestRequest { req, err := http.NewRequest("PUT", uri, reader) if err != nil { panic(err) } req.Header.Set("Content-Type", contentType) return t.NewTestRequest(req) } // PutForm issues a PUT request to the given path as a form put of the given key // and values, and stores the result in Response and ResponseBody. func (t *TestSuite) PutForm(path string, data url.Values) { t.PutFormCustom(t.BaseUrl()+path, data).Send() } // PutFormCustom returns a PUT request to the given URI as a form put of the // given key and values. The request is in a form of TestRequest wrapper. func (t *TestSuite) PutFormCustom(uri string, data url.Values) *TestRequest { return t.PutCustom(uri, "application/x-www-form-urlencoded", strings.NewReader(data.Encode())) } // Patch issues a PATCH request to the given path, sending the given // Content-Type and data, and stores the result in Response and ResponseBody. // "data" may be nil. func (t *TestSuite) Patch(path string, contentType string, reader io.Reader) { t.PatchCustom(t.BaseUrl()+path, contentType, reader).Send() } // PatchCustom returns a PATCH request to the given URI with specified // Content-Type and data in a form of wrapper. "data" may be nil. func (t *TestSuite) PatchCustom(uri string, contentType string, reader io.Reader) *TestRequest { req, err := http.NewRequest("PATCH", uri, reader) if err != nil { panic(err) } req.Header.Set("Content-Type", contentType) return t.NewTestRequest(req) } // Post issues a POST request to the given path, sending the given Content-Type // and data, storing the result in Response and ResponseBody. "data" may be nil. func (t *TestSuite) Post(path string, contentType string, reader io.Reader) { t.PostCustom(t.BaseUrl()+path, contentType, reader).Send() } // PostCustom returns a POST request to the given URI with specified // Content-Type and data in a form of wrapper. "data" may be nil. func (t *TestSuite) PostCustom(uri string, contentType string, reader io.Reader) *TestRequest { req, err := http.NewRequest("POST", uri, reader) if err != nil { panic(err) } req.Header.Set("Content-Type", contentType) return t.NewTestRequest(req) } // PostForm issues a POST request to the given path as a form post of the given // key and values, and stores the result in Response and ResponseBody. func (t *TestSuite) PostForm(path string, data url.Values) { t.PostFormCustom(t.BaseUrl()+path, data).Send() } // PostFormCustom returns a POST request to the given URI as a form post of the // given key and values. The request is in a form of TestRequest wrapper. func (t *TestSuite) PostFormCustom(uri string, data url.Values) *TestRequest { return t.PostCustom(uri, "application/x-www-form-urlencoded", strings.NewReader(data.Encode())) } // PostFile issues a multipart request to the given path sending given params // and files, and stores the result in Response and ResponseBody. func (t *TestSuite) PostFile(path string, params url.Values, filePaths url.Values) { t.PostFileCustom(t.BaseUrl()+path, params, filePaths).Send() } // PostFileCustom returns a multipart request to the given URI in a form of its // wrapper with the given params and files. func (t *TestSuite) PostFileCustom(uri string, params url.Values, filePaths url.Values) *TestRequest { body := &bytes.Buffer{} writer := multipart.NewWriter(body) for key, values := range filePaths { for _, value := range values { createFormFile(writer, key, value) } } for key, values := range params { for _, value := range values { err := writer.WriteField(key, value) t.AssertEqual(nil, err) } } err := writer.Close() t.AssertEqual(nil, err) return t.PostCustom(uri, writer.FormDataContentType(), body) } // Send issues any request and reads the response. If successful, the caller may // examine the Response and ResponseBody properties. Session data will be // added. func (r *TestRequest) Send() { writer := httptest.NewRecorder() context := revel.NewGoContext(nil) context.Request.SetRequest(r.Request) context.Response.SetResponse(writer) controller := revel.NewController(context) controller.Session = r.testSuite.Session r.testSuite.SessionEngine.Encode(controller) response := http.Response{Header: writer.Header()} cookies := response.Cookies() for _, c := range cookies { r.AddCookie(c) } r.MakeRequest() } // MakeRequest issues any request and read the response. If successful, the // caller may examine the Response and ResponseBody properties. You will need to // manage session / cookie data manually func (r *TestRequest) MakeRequest() { var err error if r.testSuite.Response, err = r.testSuite.Client.Do(r.Request); err != nil { panic(err) } if r.testSuite.ResponseBody, err = ioutil.ReadAll(r.testSuite.Response.Body); err != nil { panic(err) } // Create the controller again to receive the response for processing. context := revel.NewGoContext(nil) // Set the request with the header from the response.. newRequest := &http.Request{URL: r.URL, Header: r.testSuite.Response.Header} for _, cookie := range r.testSuite.Client.Jar.Cookies(r.Request.URL) { newRequest.AddCookie(cookie) } context.Request.SetRequest(newRequest) context.Response.SetResponse(httptest.NewRecorder()) controller := revel.NewController(context) // Decode the session data from the controller and assign it to the session r.testSuite.SessionEngine.Decode(controller) r.testSuite.Session = controller.Session } // WebSocket creates a websocket connection to the given path and returns it func (t *TestSuite) WebSocket(path string) *websocket.Conn { origin := t.BaseUrl() + "/" urlPath := t.WebSocketUrl() + path ws, err := websocket.Dial(urlPath, "", origin) if err != nil { panic(err) } return ws } func (t *TestSuite) AssertOk() { t.AssertStatus(http.StatusOK) } func (t *TestSuite) AssertNotFound() { t.AssertStatus(http.StatusNotFound) } func (t *TestSuite) AssertStatus(status int) { if t.Response.StatusCode != status { panic(fmt.Errorf("Status: (expected) %d != %d (actual)", status, t.Response.StatusCode)) } } func (t *TestSuite) AssertContentType(contentType string) { t.AssertHeader("Content-Type", contentType) } func (t *TestSuite) AssertHeader(name, value string) { actual := t.Response.Header.Get(name) if actual != value { panic(fmt.Errorf("Header %s: (expected) %s != %s (actual)", name, value, actual)) } } func (t *TestSuite) AssertEqual(expected, actual interface{}) { if !revel.Equal(expected, actual) { panic(fmt.Errorf("(expected) %v != %v (actual)", expected, actual)) } } func (t *TestSuite) AssertNotEqual(expected, actual interface{}) { if revel.Equal(expected, actual) { panic(fmt.Errorf("(expected) %v == %v (actual)", expected, actual)) } } func (t *TestSuite) Assert(exp bool) { t.Assertf(exp, "Assertion failed") } func (t *TestSuite) Assertf(exp bool, formatStr string, args ...interface{}) { if !exp { panic(fmt.Errorf(formatStr, args...)) } } // AssertContains asserts that the response contains the given string. func (t *TestSuite) AssertContains(s string) { if !bytes.Contains(t.ResponseBody, []byte(s)) { panic(fmt.Errorf("Assertion failed. Expected response to contain %s", s)) } } // AssertNotContains asserts that the response does not contain the given string. func (t *TestSuite) AssertNotContains(s string) { if bytes.Contains(t.ResponseBody, []byte(s)) { panic(fmt.Errorf("Assertion failed. Expected response not to contain %s", s)) } } // AssertContainsRegex asserts that the response matches the given regular expression. func (t *TestSuite) AssertContainsRegex(regex string) { r := regexp.MustCompile(regex) if !r.Match(t.ResponseBody) { panic(fmt.Errorf("Assertion failed. Expected response to match regexp %s", regex)) } } func createFormFile(writer *multipart.Writer, fieldname, filename string) { // Try to open the file. file, err := os.Open(filename) if err != nil { panic(err) } defer func() { _ = file.Close() }() // Create a new form-data header with the provided field name and file name. // Determine Content-Type of the file by its extension. h := textproto.MIMEHeader{} h.Set("Content-Disposition", fmt.Sprintf( `form-data; name="%s"; filename="%s"`, escapeQuotes(fieldname), escapeQuotes(filepath.Base(filename)), )) h.Set("Content-Type", "application/octet-stream") if ct := mime.TypeByExtension(filepath.Ext(filename)); ct != "" { h.Set("Content-Type", ct) } part, err := writer.CreatePart(h) if err != nil { panic(err) } // Copy the content of the file we have opened not reading the whole // file into memory. _, err = io.Copy(part, file) if err != nil { panic(err) } } var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"") // This function was borrowed from mime/multipart package. func escapeQuotes(s string) string { return quoteEscaper.Replace(s) } revel-1.0.0/testing/testsuite_test.go000066400000000000000000000160011370252312000177120ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package testing import ( "bytes" "fmt" "net/http" "net/http/httptest" "net/url" "os" "path/filepath" "strings" "testing" "time" "github.com/revel/revel" "github.com/revel/revel/session" ) func TestMisc(t *testing.T) { testSuite := createNewTestSuite(t) // test Host value if !strings.EqualFold("127.0.0.1:9001", testSuite.Host()) { t.Error("Incorrect Host value found.") } // test BaseUrl if !strings.EqualFold("http://127.0.0.1:9001", testSuite.BaseUrl()) { t.Error("Incorrect BaseUrl http value found.") } revel.HTTPSsl = true if !strings.EqualFold("https://127.0.0.1:9001", testSuite.BaseUrl()) { t.Error("Incorrect BaseUrl https value found.") } revel.HTTPSsl = false // test WebSocketUrl if !strings.EqualFold("ws://127.0.0.1:9001", testSuite.WebSocketUrl()) { t.Error("Incorrect WebSocketUrl value found.") } testSuite.AssertNotEqual("Yes", "No") testSuite.Assert(true) } func TestGet(t *testing.T) { ts := createTestServer(testHandle) defer ts.Close() testSuite := createNewTestSuite(t) testSuite.Get("/") testSuite.AssertOk() testSuite.AssertContains("this is testcase homepage") testSuite.AssertNotContains("not exists") } func TestGetNotFound(t *testing.T) { ts := createTestServer(testHandle) defer ts.Close() testSuite := createNewTestSuite(t) testSuite.Get("/notfound") testSuite.AssertNotFound() // testSuite.AssertContains("this is testcase homepage") // testSuite.AssertNotContains("not exists") } // This test is known to fail func TestGetCustom(t *testing.T) { testSuite := createNewTestSuite(t) for x := 0; x < 5; x++ { testSuite.GetCustom("http://httpbin.org/get").Send() if testSuite.Response.StatusCode == http.StatusOK { break } println("Failed request from http://httpbin.org/get", testSuite.Response.StatusCode) } testSuite.AssertOk() testSuite.AssertContentType("application/json") testSuite.AssertContains("httpbin.org") testSuite.AssertContainsRegex("gzip|deflate") } func TestDelete(t *testing.T) { ts := createTestServer(testHandle) defer ts.Close() testSuite := createNewTestSuite(t) testSuite.Delete("/purchases/10001") testSuite.AssertOk() } func TestPut(t *testing.T) { ts := createTestServer(testHandle) defer ts.Close() testSuite := createNewTestSuite(t) testSuite.Put("/purchases/10002", "application/json", bytes.NewReader([]byte(`{"sku":"163645GHT", "desc":"This is test product"}`)), ) testSuite.AssertStatus(http.StatusNoContent) } func TestPutForm(t *testing.T) { ts := createTestServer(testHandle) defer ts.Close() testSuite := createNewTestSuite(t) data := url.Values{} data.Add("name", "beacon1name") data.Add("value", "beacon1value") testSuite.PutForm("/send", data) testSuite.AssertStatus(http.StatusNoContent) } func TestPatch(t *testing.T) { ts := createTestServer(testHandle) defer ts.Close() testSuite := createNewTestSuite(t) testSuite.Patch("/purchases/10003", "application/json", bytes.NewReader([]byte(`{"desc": "This is test patch for product"}`)), ) testSuite.AssertStatus(http.StatusNoContent) } func TestPost(t *testing.T) { ts := createTestServer(testHandle) defer ts.Close() testSuite := createNewTestSuite(t) testSuite.Post("/login", "application/json", bytes.NewReader([]byte(`{"username":"testuser", "password":"testpass"}`)), ) testSuite.AssertOk() testSuite.AssertContains("login successful") } func TestPostForm(t *testing.T) { ts := createTestServer(testHandle) defer ts.Close() testSuite := createNewTestSuite(t) data := url.Values{} data.Add("username", "testuser") data.Add("password", "testpassword") testSuite.PostForm("/login", data) testSuite.AssertOk() testSuite.AssertContains("login successful") } func TestPostFileUpload(t *testing.T) { ts := createTestServer(testHandle) defer ts.Close() testSuite := createNewTestSuite(t) params := url.Values{} params.Add("first_name", "Jeevanandam") params.Add("last_name", "M.") currentDir, _ := os.Getwd() basePath := filepath.Dir(currentDir) filePaths := url.Values{} filePaths.Add("revel_file", filepath.Join(basePath, "revel.go")) filePaths.Add("server_file", filepath.Join(basePath, "server.go")) filePaths.Add("readme_file", filepath.Join(basePath, "README.md")) testSuite.PostFile("/upload", params, filePaths) testSuite.AssertOk() testSuite.AssertContains("File: revel.go") testSuite.AssertContains("File: server.go") testSuite.AssertNotContains("File: not_exists.go") testSuite.AssertEqual("text/plain; charset=utf-8", testSuite.Response.Header.Get("Content-Type")) } func createNewTestSuite(t *testing.T) *TestSuite { suite := NewTestSuite() if suite.Client == nil || suite.SessionEngine == nil { t.Error("Unable to create a testsuite") } return &suite } func testHandle(w http.ResponseWriter, r *http.Request) { if r.Method == "GET" { if r.URL.Path == "/" { _, _ = w.Write([]byte(`this is testcase homepage`)) return } } if r.Method == "POST" { if r.URL.Path == "/login" { http.SetCookie(w, &http.Cookie{ Name: session.SessionCookieSuffix, Value: "This is simple session value", Path: "/", HttpOnly: true, Secure: false, Expires: time.Now().Add(time.Minute * 5).UTC(), }) w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{ "id": "success", "message": "login successful" }`)) return } handleFileUpload(w, r) return } if r.Method == "DELETE" { if r.URL.Path == "/purchases/10001" { w.WriteHeader(http.StatusOK) return } } if r.Method == "PUT" { if r.URL.Path == "/purchases/10002" { w.WriteHeader(http.StatusNoContent) return } if r.URL.Path == "/send" { w.WriteHeader(http.StatusNoContent) return } } if r.Method == "PATCH" { if r.URL.Path == "/purchases/10003" { w.WriteHeader(http.StatusNoContent) return } } w.WriteHeader(http.StatusNotFound) } func handleFileUpload(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/upload" { _ = r.ParseMultipartForm(10e6) var buf bytes.Buffer for _, fhdrs := range r.MultipartForm.File { for _, hdr := range fhdrs { dotPos := strings.LastIndex(hdr.Filename, ".") fname := fmt.Sprintf("%s-%v%s", hdr.Filename[:dotPos], time.Now().Unix(), hdr.Filename[dotPos:]) _, _ = buf.WriteString(fmt.Sprintf( "Firstname: %v\nLastname: %v\nFile: %v\nHeader: %v\nUploaded as: %v\n", r.FormValue("first_name"), r.FormValue("last_name"), hdr.Filename, hdr.Header, fname)) } } _, _ = w.Write(buf.Bytes()) return } } func createTestServer(fn func(w http.ResponseWriter, r *http.Request)) *httptest.Server { testServer := httptest.NewServer(http.HandlerFunc(fn)) revel.ServerEngineInit.Address = testServer.URL[7:] return testServer } func init() { if revel.ServerEngineInit == nil { revel.ServerEngineInit = &revel.EngineInit{ Address: ":9001", Network: "http", Port: 9001, } } } revel-1.0.0/util.go000066400000000000000000000165371370252312000141400ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package revel import ( "bytes" "fmt" "io" "io/ioutil" "net" "net/http" "net/url" "os" "path/filepath" "reflect" "regexp" "strings" "github.com/revel/config" ) const ( // DefaultFileContentType Revel's default response content type DefaultFileContentType = "application/octet-stream" ) var ( cookieKeyValueParser = regexp.MustCompile("\x00([^:]*):([^\x00]*)\x00") HdrForwardedFor = http.CanonicalHeaderKey("X-Forwarded-For") HdrRealIP = http.CanonicalHeaderKey("X-Real-Ip") utilLog = RevelLog.New("section", "util") mimeConfig *config.Context ) // ExecutableTemplate adds some more methods to the default Template. type ExecutableTemplate interface { Execute(io.Writer, interface{}) error } // ExecuteTemplate execute a template and returns the result as a string. func ExecuteTemplate(tmpl ExecutableTemplate, data interface{}) string { var b bytes.Buffer if err := tmpl.Execute(&b, data); err != nil { utilLog.Error("ExecuteTemplate: Execute failed", "error", err) } return b.String() } // MustReadLines reads the lines of the given file. Panics in the case of error. func MustReadLines(filename string) []string { r, err := ReadLines(filename) if err != nil { panic(err) } return r } // ReadLines reads the lines of the given file. Panics in the case of error. func ReadLines(filename string) ([]string, error) { dataBytes, err := ioutil.ReadFile(filename) if err != nil { return nil, err } return strings.Split(string(dataBytes), "\n"), nil } func ContainsString(list []string, target string) bool { for _, el := range list { if el == target { return true } } return false } // FindMethod returns the reflect.Method, given a Receiver type and Func value. func FindMethod(recvType reflect.Type, funcVal reflect.Value) *reflect.Method { // It is not possible to get the name of the method from the Func. // Instead, compare it to each method of the Controller. for i := 0; i < recvType.NumMethod(); i++ { method := recvType.Method(i) if method.Func.Pointer() == funcVal.Pointer() { return &method } } return nil } // ParseKeyValueCookie takes the raw (escaped) cookie value and parses out key values. func ParseKeyValueCookie(val string, cb func(key, val string)) { val, _ = url.QueryUnescape(val) if matches := cookieKeyValueParser.FindAllStringSubmatch(val, -1); matches != nil { for _, match := range matches { cb(match[1], match[2]) } } } // LoadMimeConfig load mime-types.conf on init. func LoadMimeConfig() { var err error mimeConfig, err = config.LoadContext("mime-types.conf", ConfPaths) if err != nil { utilLog.Fatal("Failed to load mime type config:", "error", err) } } // ContentTypeByFilename returns a MIME content type based on the filename's extension. // If no appropriate one is found, returns "application/octet-stream" by default. // Additionally, specifies the charset as UTF-8 for text/* types. func ContentTypeByFilename(filename string) string { dot := strings.LastIndex(filename, ".") if dot == -1 || dot+1 >= len(filename) { return DefaultFileContentType } extension := filename[dot+1:] contentType := mimeConfig.StringDefault(extension, "") if contentType == "" { return DefaultFileContentType } if strings.HasPrefix(contentType, "text/") { return contentType + "; charset=utf-8" } return contentType } // DirExists returns true if the given path exists and is a directory. func DirExists(filename string) bool { fileInfo, err := os.Stat(filename) return err == nil && fileInfo.IsDir() } func FirstNonEmpty(strs ...string) string { for _, str := range strs { if len(str) > 0 { return str } } return "" } // Equal is a helper for comparing value equality, following these rules: // - Values with equivalent types are compared with reflect.DeepEqual // - int, uint, and float values are compared without regard to the type width. // for example, Equal(int32(5), int64(5)) == true // - strings and byte slices are converted to strings before comparison. // - else, return false. func Equal(a, b interface{}) bool { if reflect.TypeOf(a) == reflect.TypeOf(b) { return reflect.DeepEqual(a, b) } switch a.(type) { case int, int8, int16, int32, int64: switch b.(type) { case int, int8, int16, int32, int64: return reflect.ValueOf(a).Int() == reflect.ValueOf(b).Int() } case uint, uint8, uint16, uint32, uint64: switch b.(type) { case uint, uint8, uint16, uint32, uint64: return reflect.ValueOf(a).Uint() == reflect.ValueOf(b).Uint() } case float32, float64: switch b.(type) { case float32, float64: return reflect.ValueOf(a).Float() == reflect.ValueOf(b).Float() } case string: switch b.(type) { case []byte: return a.(string) == string(b.([]byte)) } case []byte: switch b.(type) { case string: return b.(string) == string(a.([]byte)) } } return false } // ClientIP method returns client IP address from HTTP request. // // Note: Set property "app.behind.proxy" to true only if Revel is running // behind proxy like nginx, haproxy, apache, etc. Otherwise // you may get inaccurate Client IP address. Revel parses the // IP address in the order of X-Forwarded-For, X-Real-IP. // // By default revel will get http.Request's RemoteAddr func ClientIP(r *Request) string { if Config.BoolDefault("app.behind.proxy", false) { // Header X-Forwarded-For if fwdFor := strings.TrimSpace(r.GetHttpHeader(HdrForwardedFor)); fwdFor != "" { index := strings.Index(fwdFor, ",") if index == -1 { return fwdFor } return fwdFor[:index] } // Header X-Real-Ip if realIP := strings.TrimSpace(r.GetHttpHeader(HdrRealIP)); realIP != "" { return realIP } } if remoteAddr, _, err := net.SplitHostPort(r.RemoteAddr); err == nil { return remoteAddr } return "" } // Walk method extends filepath.Walk to also follow symlinks. // Always returns the path of the file or directory. func Walk(root string, walkFn filepath.WalkFunc) error { return fsWalk(root, root, walkFn) } // createDir method creates nested directories if not exists func createDir(path string) error { if _, err := os.Stat(path); err != nil { if os.IsNotExist(err) { if err = os.MkdirAll(path, 0755); err != nil { return fmt.Errorf("Failed to create directory '%v': %v", path, err) } } else { return fmt.Errorf("Failed to create directory '%v': %v", path, err) } } return nil } func fsWalk(fname string, linkName string, walkFn filepath.WalkFunc) error { fsWalkFunc := func(path string, info os.FileInfo, err error) error { if err != nil { return err } var name string name, err = filepath.Rel(fname, path) if err != nil { return err } path = filepath.Join(linkName, name) if err == nil && info.Mode()&os.ModeSymlink == os.ModeSymlink { var symlinkPath string symlinkPath, err = filepath.EvalSymlinks(path) if err != nil { return err } // https://github.com/golang/go/blob/master/src/path/filepath/path.go#L392 info, err = os.Lstat(symlinkPath) if err != nil { return walkFn(path, info, err) } if info.IsDir() { return fsWalk(symlinkPath, path, walkFn) } } return walkFn(path, info, err) } err := filepath.Walk(fname, fsWalkFunc) return err } func init() { OnAppStart(LoadMimeConfig) } revel-1.0.0/util_test.go000066400000000000000000000052751370252312000151740ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package revel import ( "path/filepath" "reflect" "testing" ) func TestContentTypeByFilename(t *testing.T) { testCases := map[string]string{ "xyz.jpg": "image/jpeg", "helloworld.c": "text/x-c; charset=utf-8", "helloworld.": "application/octet-stream", "helloworld": "application/octet-stream", "hello.world.c": "text/x-c; charset=utf-8", } srcPath, _ := findSrcPaths(RevelImportPath) ConfPaths = []string{filepath.Join( srcPath, "conf"), } LoadMimeConfig() for filename, expected := range testCases { actual := ContentTypeByFilename(filename) if actual != expected { t.Errorf("%s: %s, Expected %s", filename, actual, expected) } } } func TestEqual(t *testing.T) { type testStruct struct{} type testStruct2 struct{} i, i2 := 8, 9 s, s2 := "@朕µ\n\tüöäß", "@朕µ\n\tüöäss" slice, slice2 := []int{1, 2, 3, 4, 5}, []int{1, 2, 3, 4, 5} slice3, slice4 := []int{5, 4, 3, 2, 1}, []int{5, 4, 3, 2, 1} tm := map[string][]interface{}{ "slices": {slice, slice2}, "slices2": {slice3, slice4}, "types": {new(testStruct), new(testStruct)}, "types2": {new(testStruct2), new(testStruct2)}, "ints": {int(i), int8(i), int16(i), int32(i), int64(i)}, "ints2": {int(i2), int8(i2), int16(i2), int32(i2), int64(i2)}, "uints": {uint(i), uint8(i), uint16(i), uint32(i), uint64(i)}, "uints2": {uint(i2), uint8(i2), uint16(i2), uint32(i2), uint64(i2)}, "floats": {float32(i), float64(i)}, "floats2": {float32(i2), float64(i2)}, "strings": {[]byte(s), s}, "strings2": {[]byte(s2), s2}, } testRow := func(row, row2 string, expected bool) { for _, a := range tm[row] { for _, b := range tm[row2] { ok := Equal(a, b) if ok != expected { ak := reflect.TypeOf(a).Kind() bk := reflect.TypeOf(b).Kind() t.Errorf("eq(%s=%v,%s=%v) want %t got %t", ak, a, bk, b, expected, ok) } } } } testRow("slices", "slices", true) testRow("slices", "slices2", false) testRow("slices2", "slices", false) testRow("types", "types", true) testRow("types2", "types", false) testRow("types", "types2", false) testRow("ints", "ints", true) testRow("ints", "ints2", false) testRow("ints2", "ints", false) testRow("uints", "uints", true) testRow("uints2", "uints", false) testRow("uints", "uints2", false) testRow("floats", "floats", true) testRow("floats2", "floats", false) testRow("floats", "floats2", false) testRow("strings", "strings", true) testRow("strings2", "strings", false) testRow("strings", "strings2", false) } revel-1.0.0/utils/000077500000000000000000000000001370252312000137605ustar00rootroot00000000000000revel-1.0.0/utils/simplestack.go000066400000000000000000000044231370252312000166310ustar00rootroot00000000000000package utils import ( "fmt" "sync" ) type ( SimpleLockStack struct { Current *SimpleLockStackElement Creator func() interface{} len int capacity int active int maxsize int lock sync.Mutex } SimpleLockStackElement struct { Value interface{} Previous *SimpleLockStackElement Next *SimpleLockStackElement } ObjectDestroy interface { Destroy() } ) func NewStackLock(startsize, maxsize int, creator func() interface{}) *SimpleLockStack { ss := &SimpleLockStack{lock: sync.Mutex{}, Current: &SimpleLockStackElement{Value: creator()}, Creator: creator, maxsize: maxsize} if startsize > 0 { elements := make([]SimpleLockStackElement, startsize-1) current := ss.Current for i := range elements { e := elements[i] if creator != nil { e.Value = creator() } current.Next = &e e.Previous = current current = &e } ss.capacity, ss.len, ss.active = startsize, startsize, 0 ss.Current = current } return ss } func (s *SimpleLockStack) Pop() (value interface{}) { s.lock.Lock() defer s.lock.Unlock() if s.len == 0 { // Pool is empty, create a new item to return if s.Creator != nil { value = s.Creator() } } else { value = s.Current.Value s.len-- if s.Current.Previous != nil { s.Current = s.Current.Previous } } // println("Pop ",value, s.len, s.active, s.capacity, s.Current.Next) s.active++ return } func (s *SimpleLockStack) Push(value interface{}) { if d, ok := value.(ObjectDestroy); ok { d.Destroy() } s.lock.Lock() defer s.lock.Unlock() if s.len == 0 { s.Current.Value = value } else if s.len < s.maxsize { if s.Current.Next == nil { s.Current.Next = &SimpleLockStackElement{Value: value, Previous: s.Current} s.capacity++ } else { s.Current.Next.Value = value } s.Current = s.Current.Next } else { // If we exceeded the capacity of stack do not store the created object return } s.len++ s.active-- //println("Push ",value, s.len, s.active, s.capacity) return } func (s *SimpleLockStack) Len() int { return s.len } func (s *SimpleLockStack) Capacity() int { return s.capacity } func (s *SimpleLockStack) Active() int { return s.active } func (s *SimpleLockStack) String() string { return fmt.Sprintf("SS: Capacity:%d Active:%d Stored:%d", s.capacity, s.active, s.len) } revel-1.0.0/utils/simplestack_test.go000066400000000000000000000055471370252312000177000ustar00rootroot00000000000000package utils import ( "testing" ) type SimpleStackTest struct { index int } func TestUnique(b *testing.T) { stack := NewStackLock(10, 40, func() interface{} { newone := &SimpleStackTest{} return newone }) values := []interface{}{} for x := 0; x < 10; x++ { values = append(values, stack.Pop()) } if stack.active != 10 { b.Errorf("Failed to match 10 active %v ", stack.active) } value1 := stack.Pop().(*SimpleStackTest) value1.index = stack.active value2 := stack.Pop().(*SimpleStackTest) value2.index = stack.active value3 := stack.Pop().(*SimpleStackTest) value3.index = stack.active if !isDifferent(value1, value2, value3) { b.Errorf("Failed to get unique values") } if stack.active != 13 { b.Errorf("Failed to match 13 active %v ", stack.active) } for _, v := range values { stack.Push(v) } if stack.len != 10 { b.Errorf("Failed to match 10 len %v ", stack.len) } if stack.capacity != 10 { b.Errorf("Failed to capacity 10 len %v ", stack.capacity) } stack.Push(value1) stack.Push(value2) stack.Push(value3) if stack.capacity != 13 { b.Errorf("Failed to capacity 13 len %v ", stack.capacity) } value1 = stack.Pop().(*SimpleStackTest) value2 = stack.Pop().(*SimpleStackTest) value3 = stack.Pop().(*SimpleStackTest) println(value1, value2, value3) if !isDifferent(value1, value2, value3) { b.Errorf("Failed to get unique values") } } func TestLimits(b *testing.T) { stack := NewStackLock(10, 20, func() interface{} { newone := &SimpleStackTest{} return newone }) values := []interface{}{} for x := 0; x < 50; x++ { values = append(values, stack.Pop()) } if stack.active != 50 { b.Errorf("Failed to match 50 active %v ", stack.active) } for _, v := range values { stack.Push(v) } if stack.Capacity() != 20 { b.Errorf("Failed to match 20 capcity %v ", stack.Capacity()) } } func isDifferent(values ...*SimpleStackTest) bool { if len(values) == 2 { return values[0] != values[1] } for _, v := range values[1:] { if values[0] == v { return false } } return isDifferent(values[1:]...) } func BenchmarkCreateWrite(b *testing.B) { stack := NewStackLock(0, 40, func() interface{} { return &SimpleStackTest{} }) for x := 0; x < b.N; x++ { stack.Push(x) } } func BenchmarkAllocWrite(b *testing.B) { stack := NewStackLock(b.N, b.N+100, func() interface{} { return &SimpleStackTest{} }) for x := 0; x < b.N; x++ { stack.Push(x) } } func BenchmarkCreate(b *testing.B) { NewStackLock(b.N, b.N+100, func() interface{} { return &SimpleStackTest{} }) } func BenchmarkParrallel(b *testing.B) { stack := NewStackLock(b.N, b.N+100, func() interface{} { return &SimpleStackTest{} }) b.RunParallel(func(pb *testing.PB) { for pb.Next() { for x := 0; x < 50000; x++ { stack.Push(x) } } }) b.RunParallel(func(pb *testing.PB) { for pb.Next() { for x := 0; x < 50000; x++ { stack.Pop() } } }) } revel-1.0.0/validation.go000066400000000000000000000230231370252312000153010ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package revel import ( "fmt" "net/http" "net/url" "regexp" "runtime" ) // ValidationError simple struct to store the Message & Key of a validation error type ValidationError struct { Message, Key string } // String returns the Message field of the ValidationError struct. func (e *ValidationError) String() string { if e == nil { return "" } return e.Message } // Validation context manages data validation and error messages. type Validation struct { Errors []*ValidationError Request *Request Translator func(locale, message string, args ...interface{}) string keep bool } // Keep tells revel to set a flash cookie on the client to make the validation // errors available for the next request. // This is helpful when redirecting the client after the validation failed. // It is good practice to always redirect upon a HTTP POST request. Thus // one should use this method when HTTP POST validation failed and redirect // the user back to the form. func (v *Validation) Keep() { v.keep = true } // Clear *all* ValidationErrors func (v *Validation) Clear() { v.Errors = []*ValidationError{} } // HasErrors returns true if there are any (ie > 0) errors. False otherwise. func (v *Validation) HasErrors() bool { return len(v.Errors) > 0 } // ErrorMap returns the errors mapped by key. // If there are multiple validation errors associated with a single key, the // first one "wins". (Typically the first validation will be the more basic). func (v *Validation) ErrorMap() map[string]*ValidationError { m := map[string]*ValidationError{} for _, e := range v.Errors { if _, ok := m[e.Key]; !ok { m[e.Key] = e } } return m } // Error adds an error to the validation context. func (v *Validation) Error(message string, args ...interface{}) *ValidationResult { result := v.ValidationResult(false).Message(message, args...) v.Errors = append(v.Errors, result.Error) return result } // Error adds an error to the validation context. func (v *Validation) ErrorKey(message string, args ...interface{}) *ValidationResult { result := v.ValidationResult(false).MessageKey(message, args...) v.Errors = append(v.Errors, result.Error) return result } // Error adds an error to the validation context. func (v *Validation) ValidationResult(ok bool) *ValidationResult { if ok { return &ValidationResult{Ok: ok} } else { return &ValidationResult{Ok: ok, Error: &ValidationError{}, Locale: v.Request.Locale, Translator: v.Translator} } } // ValidationResult is returned from every validation method. // It provides an indication of success, and a pointer to the Error (if any). type ValidationResult struct { Error *ValidationError Ok bool Locale string Translator func(locale, message string, args ...interface{}) string } // Key sets the ValidationResult's Error "key" and returns itself for chaining func (r *ValidationResult) Key(key string) *ValidationResult { if r.Error != nil { r.Error.Key = key } return r } // Message sets the error message for a ValidationResult. Returns itself to // allow chaining. Allows Sprintf() type calling with multiple parameters func (r *ValidationResult) Message(message string, args ...interface{}) *ValidationResult { if r.Error != nil { if len(args) == 0 { r.Error.Message = message } else { r.Error.Message = fmt.Sprintf(message, args...) } } return r } // Allow a message key to be passed into the validation result. The Validation has already // setup the translator to translate the message key func (r *ValidationResult) MessageKey(message string, args ...interface{}) *ValidationResult { if r.Error == nil { return r } // If translator found, use that to create the message, otherwise call Message method if r.Translator != nil { r.Error.Message = r.Translator(r.Locale, message, args...) } else { r.Message(message, args...) } return r } // Required tests that the argument is non-nil and non-empty (if string or list) func (v *Validation) Required(obj interface{}) *ValidationResult { return v.apply(Required{}, obj) } func (v *Validation) Min(n int, min int) *ValidationResult { return v.MinFloat(float64(n), float64(min)) } func (v *Validation) MinFloat(n float64, min float64) *ValidationResult { return v.apply(Min{min}, n) } func (v *Validation) Max(n int, max int) *ValidationResult { return v.MaxFloat(float64(n), float64(max)) } func (v *Validation) MaxFloat(n float64, max float64) *ValidationResult { return v.apply(Max{max}, n) } func (v *Validation) Range(n, min, max int) *ValidationResult { return v.RangeFloat(float64(n), float64(min), float64(max)) } func (v *Validation) RangeFloat(n, min, max float64) *ValidationResult { return v.apply(Range{Min{min}, Max{max}}, n) } func (v *Validation) MinSize(obj interface{}, min int) *ValidationResult { return v.apply(MinSize{min}, obj) } func (v *Validation) MaxSize(obj interface{}, max int) *ValidationResult { return v.apply(MaxSize{max}, obj) } func (v *Validation) Length(obj interface{}, n int) *ValidationResult { return v.apply(Length{n}, obj) } func (v *Validation) Match(str string, regex *regexp.Regexp) *ValidationResult { return v.apply(Match{regex}, str) } func (v *Validation) Email(str string) *ValidationResult { return v.apply(Email{Match{emailPattern}}, str) } func (v *Validation) IPAddr(str string, cktype ...int) *ValidationResult { return v.apply(IPAddr{cktype}, str) } func (v *Validation) MacAddr(str string) *ValidationResult { return v.apply(IPAddr{}, str) } func (v *Validation) Domain(str string) *ValidationResult { return v.apply(Domain{}, str) } func (v *Validation) URL(str string) *ValidationResult { return v.apply(URL{}, str) } func (v *Validation) PureText(str string, m int) *ValidationResult { return v.apply(PureText{m}, str) } func (v *Validation) FilePath(str string, m int) *ValidationResult { return v.apply(FilePath{m}, str) } func (v *Validation) apply(chk Validator, obj interface{}) *ValidationResult { if chk.IsSatisfied(obj) { return v.ValidationResult(true) } // Get the default key. var key string if pc, _, line, ok := runtime.Caller(2); ok { f := runtime.FuncForPC(pc) if defaultKeys, ok := DefaultValidationKeys[f.Name()]; ok { key = defaultKeys[line] } } else { utilLog.Error("Validation: Failed to get Caller information to look up Validation key") } // Add the error to the validation context. err := &ValidationError{ Message: chk.DefaultMessage(), Key: key, } v.Errors = append(v.Errors, err) // Also return it in the result. vr := v.ValidationResult(false) vr.Error = err return vr } // Check applies a group of validators to a field, in order, and return the // ValidationResult from the first one that fails, or the last one that // succeeds. func (v *Validation) Check(obj interface{}, checks ...Validator) *ValidationResult { var result *ValidationResult for _, check := range checks { result = v.apply(check, obj) if !result.Ok { return result } } return result } // ValidationFilter revel Filter function to be hooked into the filter chain. func ValidationFilter(c *Controller, fc []Filter) { // If json request, we shall assume json response is intended, // as such no validation cookies should be tied response if c.Params != nil && c.Params.JSON != nil { c.Validation = &Validation{Request: c.Request, Translator: MessageFunc} fc[0](c, fc[1:]) } else { errors, err := restoreValidationErrors(c.Request) c.Validation = &Validation{ Errors: errors, keep: false, Request: c.Request, Translator: MessageFunc, } hasCookie := (err != http.ErrNoCookie) fc[0](c, fc[1:]) // Add Validation errors to ViewArgs. c.ViewArgs["errors"] = c.Validation.ErrorMap() // Store the Validation errors var errorsValue string if c.Validation.keep { for _, err := range c.Validation.Errors { if err.Message != "" { errorsValue += "\x00" + err.Key + ":" + err.Message + "\x00" } } } // When there are errors from Validation and Keep() has been called, store the // values in a cookie. If there previously was a cookie but no errors, remove // the cookie. if errorsValue != "" { c.SetCookie(&http.Cookie{ Name: CookiePrefix + "_ERRORS", Value: url.QueryEscape(errorsValue), Domain: CookieDomain, Path: "/", HttpOnly: true, Secure: CookieSecure, SameSite: CookieSameSite, }) } else if hasCookie { c.SetCookie(&http.Cookie{ Name: CookiePrefix + "_ERRORS", MaxAge: -1, Domain: CookieDomain, Path: "/", HttpOnly: true, Secure: CookieSecure, SameSite: CookieSameSite, }) } } } // Restore Validation.Errors from a request. func restoreValidationErrors(req *Request) ([]*ValidationError, error) { var ( err error cookie ServerCookie errors = make([]*ValidationError, 0, 5) ) if cookie, err = req.Cookie(CookiePrefix + "_ERRORS"); err == nil { ParseKeyValueCookie(cookie.GetValue(), func(key, val string) { errors = append(errors, &ValidationError{ Key: key, Message: val, }) }) } return errors, err } // DefaultValidationKeys register default validation keys for all calls to Controller.Validation.Func(). // Map from (package).func => (line => name of first arg to Validation func) // E.g. "myapp/controllers.helper" or "myapp/controllers.(*Application).Action" // This is set on initialization in the generated main.go file. var DefaultValidationKeys map[string]map[int]string revel-1.0.0/validation_test.go000066400000000000000000000062151370252312000163440ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package revel import ( "net/http" "net/http/httptest" "testing" ) // getRecordedCookie returns the recorded cookie from a ResponseRecorder with // the given name. It utilizes the cookie reader found in the standard library. func getRecordedCookie(recorder *httptest.ResponseRecorder, name string) (*http.Cookie, error) { r := &http.Response{Header: recorder.HeaderMap} for _, cookie := range r.Cookies() { if cookie.Name == name { return cookie, nil } } return nil, http.ErrNoCookie } // r.Original.URL.String() func validationTester(req *Request, fn func(c *Controller)) *httptest.ResponseRecorder { recorder := httptest.NewRecorder() c := NewTestController(recorder, req.In.GetRaw().(*http.Request)) c.Request = req ValidationFilter(c, []Filter{I18nFilter, func(c *Controller, _ []Filter) { fn(c) }}) return recorder } // Test that errors are encoded into the _ERRORS cookie. func TestValidationWithError(t *testing.T) { recorder := validationTester(buildEmptyRequest().Request, func(c *Controller) { c.Validation.Required("") if !c.Validation.HasErrors() { t.Fatal("errors should be present") } c.Validation.Keep() }) if cookie, err := getRecordedCookie(recorder, "REVEL_ERRORS"); err != nil { t.Fatal(err) } else if cookie.MaxAge < 0 { t.Fatalf("cookie should not expire") } } // Test that no cookie is sent if errors are found, but Keep() is not called. func TestValidationNoKeep(t *testing.T) { recorder := validationTester(buildEmptyRequest().Request, func(c *Controller) { c.Validation.Required("") if !c.Validation.HasErrors() { t.Fatal("errors should not be present") } }) if _, err := getRecordedCookie(recorder, "REVEL_ERRORS"); err != http.ErrNoCookie { t.Fatal(err) } } // Test that a previously set _ERRORS cookie is deleted if no errors are found. func TestValidationNoKeepCookiePreviouslySet(t *testing.T) { req := buildRequestWithCookie("REVEL_ERRORS", "invalid").Request recorder := validationTester(req, func(c *Controller) { c.Validation.Required("success") if c.Validation.HasErrors() { t.Fatal("errors should not be present") } }) if cookie, err := getRecordedCookie(recorder, "REVEL_ERRORS"); err != nil { t.Fatal(err) } else if cookie.MaxAge >= 0 { t.Fatalf("cookie should be deleted") } } func TestValidateMessageKey(t *testing.T) { Init("prod", "github.com/revel/revel/testdata", "") loadMessages(testDataPath) // Assert that we have the expected number of languages if len(MessageLanguages()) != 2 { t.Fatalf("Expected messages to contain no more or less than 2 languages, instead there are %d languages", len(MessageLanguages())) } req := buildRequestWithAcceptLanguages("nl").Request validationTester(req, func(c *Controller) { c.Validation.Required("").MessageKey("greeting") if msg := c.Validation.Errors[0].Message; msg != "Hallo" { t.Errorf("Failed expected message Hallo got %s", msg) } if !c.Validation.HasErrors() { t.Fatal("errors should not be present") } }) } revel-1.0.0/validators.go000066400000000000000000000333461370252312000153300ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package revel import ( "errors" "fmt" "html" "net" "net/url" "reflect" "regexp" "strconv" "strings" "unicode/utf8" ) type Validator interface { IsSatisfied(interface{}) bool DefaultMessage() string } type Required struct{} func ValidRequired() Required { return Required{} } func (r Required) IsSatisfied(obj interface{}) bool { if obj == nil { return false } switch v := reflect.ValueOf(obj); v.Kind() { case reflect.Array, reflect.Slice, reflect.Map, reflect.String, reflect.Chan: if v.Len() == 0 { return false } case reflect.Ptr: return r.IsSatisfied(reflect.Indirect(v).Interface()) } return !reflect.DeepEqual(obj, reflect.Zero(reflect.TypeOf(obj)).Interface()) } func (r Required) DefaultMessage() string { return fmt.Sprintln("Required") } type Min struct { Min float64 } func ValidMin(min int) Min { return ValidMinFloat(float64(min)) } func ValidMinFloat(min float64) Min { return Min{min} } func (m Min) IsSatisfied(obj interface{}) bool { var ( num float64 ok bool ) switch reflect.TypeOf(obj).Kind() { case reflect.Float64: num, ok = obj.(float64) case reflect.Float32: ok = true num = float64(obj.(float32)) case reflect.Int: ok = true num = float64(obj.(int)) } if ok { return num >= m.Min } return false } func (m Min) DefaultMessage() string { return fmt.Sprintln("Minimum is", m.Min) } type Max struct { Max float64 } func ValidMax(max int) Max { return ValidMaxFloat(float64(max)) } func ValidMaxFloat(max float64) Max { return Max{max} } func (m Max) IsSatisfied(obj interface{}) bool { var ( num float64 ok bool ) switch reflect.TypeOf(obj).Kind() { case reflect.Float64: num, ok = obj.(float64) case reflect.Float32: ok = true num = float64(obj.(float32)) case reflect.Int: ok = true num = float64(obj.(int)) } if ok { return num <= m.Max } return false } func (m Max) DefaultMessage() string { return fmt.Sprintln("Maximum is", m.Max) } // Range requires an integer to be within Min, Max inclusive. type Range struct { Min Max } func ValidRange(min, max int) Range { return ValidRangeFloat(float64(min), float64(max)) } func ValidRangeFloat(min, max float64) Range { return Range{Min{min}, Max{max}} } func (r Range) IsSatisfied(obj interface{}) bool { return r.Min.IsSatisfied(obj) && r.Max.IsSatisfied(obj) } func (r Range) DefaultMessage() string { return fmt.Sprintln("Range is", r.Min.Min, "to", r.Max.Max) } // MinSize requires an array or string to be at least a given length. type MinSize struct { Min int } func ValidMinSize(min int) MinSize { return MinSize{min} } func (m MinSize) IsSatisfied(obj interface{}) bool { if str, ok := obj.(string); ok { return utf8.RuneCountInString(str) >= m.Min } v := reflect.ValueOf(obj) if v.Kind() == reflect.Slice { return v.Len() >= m.Min } return false } func (m MinSize) DefaultMessage() string { return fmt.Sprintln("Minimum size is", m.Min) } // MaxSize requires an array or string to be at most a given length. type MaxSize struct { Max int } func ValidMaxSize(max int) MaxSize { return MaxSize{max} } func (m MaxSize) IsSatisfied(obj interface{}) bool { if str, ok := obj.(string); ok { return utf8.RuneCountInString(str) <= m.Max } v := reflect.ValueOf(obj) if v.Kind() == reflect.Slice { return v.Len() <= m.Max } return false } func (m MaxSize) DefaultMessage() string { return fmt.Sprintln("Maximum size is", m.Max) } // Length requires an array or string to be exactly a given length. type Length struct { N int } func ValidLength(n int) Length { return Length{n} } func (s Length) IsSatisfied(obj interface{}) bool { if str, ok := obj.(string); ok { return utf8.RuneCountInString(str) == s.N } v := reflect.ValueOf(obj) if v.Kind() == reflect.Slice { return v.Len() == s.N } return false } func (s Length) DefaultMessage() string { return fmt.Sprintln("Required length is", s.N) } // Match requires a string to match a given regex. type Match struct { Regexp *regexp.Regexp } func ValidMatch(regex *regexp.Regexp) Match { return Match{regex} } func (m Match) IsSatisfied(obj interface{}) bool { str := obj.(string) return m.Regexp.MatchString(str) } func (m Match) DefaultMessage() string { return fmt.Sprintln("Must match", m.Regexp) } var emailPattern = regexp.MustCompile("^[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[a-zA-Z0-9](?:[\\w-]*[\\w])?$") type Email struct { Match } func ValidEmail() Email { return Email{Match{emailPattern}} } func (e Email) DefaultMessage() string { return fmt.Sprintln("Must be a valid email address") } const ( None = 0 IPAny = 1 IPv4 = 32 // IPv4 (32 chars) IPv6 = 39 // IPv6(39 chars) IPv4MappedIPv6 = 45 // IP4-mapped IPv6 (45 chars) , Ex) ::FFFF:129.144.52.38 IPv4CIDR = IPv4 + 3 IPv6CIDR = IPv6 + 3 IPv4MappedIPv6CIDR = IPv4MappedIPv6 + 3 ) // Requires a string(IP Address) to be within IP Pattern type inclusive. type IPAddr struct { Vaildtypes []int } // Requires an IP Address string to be exactly a given validation type (IPv4, IPv6, IPv4MappedIPv6, IPv4CIDR, IPv6CIDR, IPv4MappedIPv6CIDR OR IPAny) func ValidIPAddr(cktypes ...int) IPAddr { for _, cktype := range cktypes { if cktype != IPAny && cktype != IPv4 && cktype != IPv6 && cktype != IPv4MappedIPv6 && cktype != IPv4CIDR && cktype != IPv6CIDR && cktype != IPv4MappedIPv6CIDR { return IPAddr{Vaildtypes: []int{None}} } } return IPAddr{Vaildtypes: cktypes} } func isWithCIDR(str string, l int) bool { if str[l-3] == '/' || str[l-2] == '/' { cidr_bit := strings.Split(str, "/") if 2 == len(cidr_bit) { bit, err := strconv.Atoi(cidr_bit[1]) //IPv4 : 0~32, IPv6 : 0 ~ 128 if err == nil && bit >= 0 && bit <= 128 { return true } } } return false } func getIPType(str string, l int) int { if l < 3 { //least 3 chars (::F) return None } has_dot := strings.Index(str[2:], ".") has_colon := strings.Index(str[2:], ":") switch { case has_dot > -1 && has_colon == -1 && l >= 7 && l <= IPv4CIDR: if isWithCIDR(str, l) == true { return IPv4CIDR } else { return IPv4 } case has_dot == -1 && has_colon > -1 && l >= 6 && l <= IPv6CIDR: if isWithCIDR(str, l) == true { return IPv6CIDR } else { return IPv6 } case has_dot > -1 && has_colon > -1 && l >= 14 && l <= IPv4MappedIPv6: if isWithCIDR(str, l) == true { return IPv4MappedIPv6CIDR } else { return IPv4MappedIPv6 } } return None } func (i IPAddr) IsSatisfied(obj interface{}) bool { if str, ok := obj.(string); ok { l := len(str) ret := getIPType(str, l) for _, ck := range i.Vaildtypes { if ret != None && (ck == ret || ck == IPAny) { switch ret { case IPv4, IPv6, IPv4MappedIPv6: ip := net.ParseIP(str) if ip != nil { return true } case IPv4CIDR, IPv6CIDR, IPv4MappedIPv6CIDR: _, _, err := net.ParseCIDR(str) if err == nil { return true } } } } } return false } func (i IPAddr) DefaultMessage() string { return fmt.Sprintln("Must be a vaild IP address") } // Requires a MAC Address string to be exactly type MacAddr struct{} func ValidMacAddr() MacAddr { return MacAddr{} } func (m MacAddr) IsSatisfied(obj interface{}) bool { if str, ok := obj.(string); ok { if _, err := net.ParseMAC(str); err == nil { return true } } return false } func (m MacAddr) DefaultMessage() string { return fmt.Sprintln("Must be a vaild MAC address") } var domainPattern = regexp.MustCompile(`^(([a-zA-Z0-9-\p{L}]{1,63}\.)?(xn--)?[a-zA-Z0-9\p{L}]+(-[a-zA-Z0-9\p{L}]+)*\.)+[a-zA-Z\p{L}]{2,63}$`) // Requires a Domain string to be exactly type Domain struct { Regexp *regexp.Regexp } func ValidDomain() Domain { return Domain{domainPattern} } func (d Domain) IsSatisfied(obj interface{}) bool { if str, ok := obj.(string); ok { l := len(str) //can't exceed 253 chars. if l > 253 { return false } //first and last char must be alphanumeric if str[l-1] == 46 || str[0] == 46 { return false } return domainPattern.MatchString(str) } return false } func (d Domain) DefaultMessage() string { return fmt.Sprintln("Must be a vaild domain address") } var urlPattern = regexp.MustCompile(`^((((https?|ftps?|gopher|telnet|nntp)://)|(mailto:|news:))(%[0-9A-Fa-f]{2}|[-()_.!~*';/?:@#&=+$,A-Za-z0-9\p{L}])+)([).!';/?:,][[:blank:]])?$`) type URL struct { Domain } func ValidURL() URL { return URL{Domain: ValidDomain()} } func (u URL) IsSatisfied(obj interface{}) bool { if str, ok := obj.(string); ok { // TODO : Required lot of testing return urlPattern.MatchString(str) } return false } func (u URL) DefaultMessage() string { return fmt.Sprintln("Must be a vaild URL address") } /* NORMAL BenchmarkRegex-8 2000000000 0.24 ns/op STRICT BenchmarkLoop-8 2000000000 0.01 ns/op */ const ( NORMAL = 0 STRICT = 4 ) // Requires a string to be without invisible characters type PureText struct { Mode int } func ValidPureText(m int) PureText { if m != NORMAL && m != STRICT { // Q:required fatal error m = STRICT } return PureText{m} } func isPureTextStrict(str string) (bool, error) { l := len(str) for i := 0; i < l; i++ { c := str[i] // deny : control char (00-31 without 9(TAB) and Single 10(LF),13(CR) if c >= 0 && c <= 31 && c != 9 && c != 10 && c != 13 { return false, errors.New("detect control character") } // deny : control char (DEL) if c == 127 { return false, errors.New("detect control character (DEL)") } //deny : short tag (<~> <~ />) if c == 60 { for n := i + 2; n < l; n++ { // 62 (>) if str[n] == 62 { return false, errors.New("detect tag (<(.*)+>)") } } } //deny : html tag (< ~ >) if c == 60 { ds := 0 for n := i; n < l; n++ { // 60 (<) , 47(/) | 33(!) | 63(?) if str[n] == 60 && n+1 <= l && (str[n+1] == 47 || str[n+1] == 33 || str[n+1] == 63) { ds = 1 n += 3 //jump to next char } // 62 (>) if ds == 1 && str[n] == 62 { return false, errors.New("detect tag (<[!|?]~>)") } } } //deny : html encoded(hex) tag (&xxx;) // 38(&) , 35(#), 59(;) if c == 38 && i+1 <= l { max := i + 64 if max > l { max = l } for n := i; n < max; n++ { if str[n] == 59 { return false, errors.New("detect html encoded ta (&XXX;)") } } } } return true, nil } // Requires a string to match a given html tag elements regex pattern // referrer : http://www.w3schools.com/Tags/ var elementPattern = regexp.MustCompile(`(?im)<(?P(/*\s*|\?*|\!*)(figcaption|expression|blockquote|plaintext|textarea|progress|optgroup|noscript|noframes|menuitem|frameset|fieldset|!DOCTYPE|datalist|colgroup|behavior|basefont|summary|section|isindex|details|caption|bgsound|article|address|acronym|strong|strike|source|select|script|output|option|object|legend|keygen|ilayer|iframe|header|footer|figure|dialog|center|canvas|button|applet|video|track|title|thead|tfoot|tbody|table|style|small|param|meter|layer|label|input|frame|embed|blink|audio|aside|alert|time|span|samp|ruby|meta|menu|mark|main|link|html|head|form|font|code|cite|body|base|area|abbr|xss|xml|wbr|var|svg|sup|sub|pre|nav|map|kbd|ins|img|div|dir|dfn|del|col|big|bdo|bdi|!--|ul|tt|tr|th|td|rt|rp|ol|li|hr|em|dt|dl|dd|br|u|s|q|p|i|b|a|(h[0-9]+)))([^><]*)([><]*)`) // Requires a string to match a given urlencoded regex pattern var urlencodedPattern = regexp.MustCompile(`(?im)(\%[0-9a-fA-F]{1,})`) // Requires a string to match a given control characters regex pattern (ASCII : 00-08, 11, 12, 14, 15-31) var controlcharPattern = regexp.MustCompile(`(?im)([\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+)`) func isPureTextNormal(str string) (bool, error) { decoded_str := html.UnescapeString(str) matched_urlencoded := urlencodedPattern.MatchString(decoded_str) if matched_urlencoded == true { temp_buf, err := url.QueryUnescape(decoded_str) if err == nil { decoded_str = temp_buf } } matched_element := elementPattern.MatchString(decoded_str) if matched_element == true { return false, errors.New("detect html element") } matched_cc := controlcharPattern.MatchString(decoded_str) if matched_cc == true { return false, errors.New("detect control character") } return true, nil } func (p PureText) IsSatisfied(obj interface{}) bool { if str, ok := obj.(string); ok { var ret bool switch p.Mode { case STRICT: ret, _ = isPureTextStrict(str) case NORMAL: ret, _ = isPureTextNormal(str) } return ret } return false } func (p PureText) DefaultMessage() string { return fmt.Sprintln("Must be a vaild Text") } const ( ONLY_FILENAME = 0 ALLOW_RELATIVE_PATH = 1 ) const regexDenyFileNameCharList = `[\x00-\x1f|\x21-\x2c|\x3b-\x40|\x5b-\x5e|\x60|\x7b-\x7f]+` const regexDenyFileName = `|\x2e\x2e\x2f+` var checkAllowRelativePath = regexp.MustCompile(`(?m)(` + regexDenyFileNameCharList + `)`) var checkDenyRelativePath = regexp.MustCompile(`(?m)(` + regexDenyFileNameCharList + regexDenyFileName + `)`) // Requires an string to be sanitary file path type FilePath struct { Mode int } func ValidFilePath(m int) FilePath { if m != ONLY_FILENAME && m != ALLOW_RELATIVE_PATH { m = ONLY_FILENAME } return FilePath{m} } func (f FilePath) IsSatisfied(obj interface{}) bool { if str, ok := obj.(string); ok { var ret bool switch f.Mode { case ALLOW_RELATIVE_PATH: ret = checkAllowRelativePath.MatchString(str) if ret == false { return true } default: //ONLY_FILENAME ret = checkDenyRelativePath.MatchString(str) if ret == false { return true } } } return false } func (f FilePath) DefaultMessage() string { return fmt.Sprintln("Must be a unsanitary string") } revel-1.0.0/validators_test.go000066400000000000000000000631171370252312000163660ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package revel_test import ( "fmt" "net" "reflect" "regexp" "strings" "testing" "time" "github.com/revel/revel" ) const ( errorsMessage = "validation for %s should not be satisfied with %s\n" noErrorsMessage = "validation for %s should be satisfied with %s\n" ) type Expect struct { input interface{} expectedResult bool errorMessage string } func performTests(validator revel.Validator, tests []Expect, t *testing.T) { for _, test := range tests { if validator.IsSatisfied(test.input) != test.expectedResult { if test.expectedResult { t.Errorf(noErrorsMessage, reflect.TypeOf(validator), test.errorMessage) } else { t.Errorf(errorsMessage, reflect.TypeOf(validator), test.errorMessage) } } } } func TestRequired(t *testing.T) { tests := []Expect{ {nil, false, "nil data"}, {"Testing", true, "non-empty string"}, {"", false, "empty string"}, {true, true, "true boolean"}, {false, false, "false boolean"}, {1, true, "positive integer"}, {-1, true, "negative integer"}, {0, false, "0 integer"}, {time.Now(), true, "current time"}, {time.Time{}, false, "a zero time"}, {func() {}, true, "other non-nil data types"}, {net.IP(""), false, "empty IP address"}, } // testing both the struct and the helper method for _, required := range []revel.Required{{}, revel.ValidRequired()} { performTests(required, tests, t) } } func TestMin(t *testing.T) { tests := []Expect{ {11, true, "val > min"}, {10, true, "val == min"}, {9, false, "val < min"}, {true, false, "TypeOf(val) != int"}, } for _, min := range []revel.Min{{10}, revel.ValidMin(10)} { performTests(min, tests, t) } } func TestMax(t *testing.T) { tests := []Expect{ {9, true, "val < max"}, {10, true, "val == max"}, {11, false, "val > max"}, {true, false, "TypeOf(val) != int"}, } for _, max := range []revel.Max{{10}, revel.ValidMax(10)} { performTests(max, tests, t) } } func TestRange(t *testing.T) { tests := []Expect{ {50, true, "min <= val <= max"}, {10, true, "val == min"}, {100, true, "val == max"}, {9, false, "val < min"}, {101, false, "val > max"}, } goodValidators := []revel.Range{ {revel.Min{10}, revel.Max{100}}, revel.ValidRange(10, 100), } for _, rangeValidator := range goodValidators { performTests(rangeValidator, tests, t) } testsFloat := []Expect{ {50, true, "min <= val <= max"}, {10.25, true, "val == min"}, {100, true, "val == max"}, {9, false, "val < min"}, {101, false, "val > max"}, } goodValidatorsFloat := []revel.Range{ {revel.Min{10.25}, revel.Max{100.5}}, revel.ValidRangeFloat(10.25, 100.5), } for _, rangeValidator := range goodValidatorsFloat { performTests(rangeValidator, testsFloat, t) } tests = []Expect{ {10, true, "min == val == max"}, {9, false, "val < min && val < max && min == max"}, {11, false, "val > min && val > max && min == max"}, } goodValidators = []revel.Range{ {revel.Min{10}, revel.Max{10}}, revel.ValidRange(10, 10), } for _, rangeValidator := range goodValidators { performTests(rangeValidator, tests, t) } tests = make([]Expect, 7) for i, num := range []int{50, 100, 10, 9, 101, 0, -1} { tests[i] = Expect{ num, false, "min > val < max", } } // these are min/max with values swapped, so the min is the high // and max is the low. rangeValidator.IsSatisfied() should ALWAYS // result in false since val can never be greater than min and less // than max when min > max badValidators := []revel.Range{ {revel.Min{100}, revel.Max{10}}, revel.ValidRange(100, 10), } for _, rangeValidator := range badValidators { performTests(rangeValidator, tests, t) } badValidatorsFloat := []revel.Range{ {revel.Min{100}, revel.Max{10}}, revel.ValidRangeFloat(100, 10), } for _, rangeValidator := range badValidatorsFloat { performTests(rangeValidator, tests, t) } } func TestMinSize(t *testing.T) { greaterThanMessage := "len(val) >= min" tests := []Expect{ {"12", true, greaterThanMessage}, {"123", true, greaterThanMessage}, {[]int{1, 2}, true, greaterThanMessage}, {[]int{1, 2, 3}, true, greaterThanMessage}, {"", false, "len(val) <= min"}, {"手", false, "len(val) <= min"}, {[]int{}, false, "len(val) <= min"}, {nil, false, "TypeOf(val) != string && TypeOf(val) != slice"}, } for _, minSize := range []revel.MinSize{{2}, revel.ValidMinSize(2)} { performTests(minSize, tests, t) } } func TestMaxSize(t *testing.T) { lessThanMessage := "len(val) <= max" tests := []Expect{ {"", true, lessThanMessage}, {"12", true, lessThanMessage}, {"ルビー", true, lessThanMessage}, {[]int{}, true, lessThanMessage}, {[]int{1, 2}, true, lessThanMessage}, {[]int{1, 2, 3}, true, lessThanMessage}, {"1234", false, "len(val) >= max"}, {[]int{1, 2, 3, 4}, false, "len(val) >= max"}, } for _, maxSize := range []revel.MaxSize{{3}, revel.ValidMaxSize(3)} { performTests(maxSize, tests, t) } } func TestLength(t *testing.T) { tests := []Expect{ {"12", true, "len(val) == length"}, {"火箭", true, "len(val) == length"}, {[]int{1, 2}, true, "len(val) == length"}, {"123", false, "len(val) > length"}, {[]int{1, 2, 3}, false, "len(val) > length"}, {"1", false, "len(val) < length"}, {[]int{1}, false, "len(val) < length"}, {nil, false, "TypeOf(val) != string && TypeOf(val) != slice"}, } for _, length := range []revel.Length{{2}, revel.ValidLength(2)} { performTests(length, tests, t) } } func TestMatch(t *testing.T) { tests := []Expect{ {"bca123", true, `"[abc]{3}\d*" matches "bca123"`}, {"bc123", false, `"[abc]{3}\d*" does not match "bc123"`}, {"", false, `"[abc]{3}\d*" does not match ""`}, } regex := regexp.MustCompile(`[abc]{3}\d*`) for _, match := range []revel.Match{{regex}, revel.ValidMatch(regex)} { performTests(match, tests, t) } } func TestEmail(t *testing.T) { // unicode char included validStartingCharacters := strings.Split("!#$%^&*_+1234567890abcdefghijklmnopqrstuvwxyzñ", "") invalidCharacters := strings.Split(" ()", "") definiteInvalidDomains := []string{ "", // any empty string (x@) ".com", // only the TLD (x@.com) ".", // only the . (x@.) ".*", // TLD containing symbol (x@.*) "asdf", // no TLD "a!@#$%^&*()+_.com", // characters which are not ASCII/0-9/dash(-) in a domain "-a.com", // host starting with any symbol "a-.com", // host ending with any symbol "aå.com", // domain containing unicode (however, unicode domains do exist in the state of xn--.com e.g. å.com = xn--5ca.com) } // Email pattern is not exposed emailPattern := regexp.MustCompile("^[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[a-zA-Z0-9](?:[\\w-]*[\\w])?$") for _, email := range []revel.Email{{revel.Match{emailPattern}}, revel.ValidEmail()} { var currentEmail string // test invalid starting chars for _, startingChar := range validStartingCharacters { currentEmail = fmt.Sprintf("%sñbc+123@do-main.com", startingChar) if email.IsSatisfied(currentEmail) { t.Errorf(noErrorsMessage, "starting characters", fmt.Sprintf("email = %s", currentEmail)) } // validation should fail because of multiple @ symbols currentEmail = fmt.Sprintf("%s@ñbc+123@do-main.com", startingChar) if email.IsSatisfied(currentEmail) { t.Errorf(errorsMessage, "starting characters with multiple @ symbols", fmt.Sprintf("email = %s", currentEmail)) } // should fail simply because of the invalid char for _, invalidChar := range invalidCharacters { currentEmail = fmt.Sprintf("%sñbc%s+123@do-main.com", startingChar, invalidChar) if email.IsSatisfied(currentEmail) { t.Errorf(errorsMessage, "invalid starting characters", fmt.Sprintf("email = %s", currentEmail)) } } } // test invalid domains for _, invalidDomain := range definiteInvalidDomains { currentEmail = fmt.Sprintf("a@%s", invalidDomain) if email.IsSatisfied(currentEmail) { t.Errorf(errorsMessage, "invalid domain", fmt.Sprintf("email = %s", currentEmail)) } } // should always be satisfied if !email.IsSatisfied("t0.est+email123@1abc0-def.com") { t.Errorf(noErrorsMessage, "guaranteed valid email", fmt.Sprintf("email = %s", "t0.est+email123@1abc0-def.com")) } // should never be satisfied (this is redundant given the loops above) if email.IsSatisfied("a@xcom") { t.Errorf(noErrorsMessage, "guaranteed invalid email", fmt.Sprintf("email = %s", "a@xcom")) } if email.IsSatisfied("a@@x.com") { t.Errorf(noErrorsMessage, "guaranteed invalid email", fmt.Sprintf("email = %s", "a@@x.com")) } } } func runIPAddrTestfunc(t *testing.T, test_type int, ipaddr_list map[string]bool, msg_fmt string) { // generate dataset for test test_ipaddr_list := []Expect{} for ipaddr, expected := range ipaddr_list { test_ipaddr_list = append(test_ipaddr_list, Expect{input: ipaddr, expectedResult: expected, errorMessage: fmt.Sprintf(msg_fmt, ipaddr)}) } for _, ip_test_list := range []revel.IPAddr{{[]int{test_type}}, revel.ValidIPAddr(test_type)} { performTests(ip_test_list, test_ipaddr_list, t) } } func TestIPAddr(t *testing.T) { //IPv4 test_ipv4_ipaddrs := map[string]bool{ "192.168.1.1": true, "127.0.0.1": true, "10.10.90.12": true, "8.8.8.8": true, "4.4.4.4": true, "912.456.123.123": false, "999.999.999.999": false, "192.192.19.999": false, } //IPv4 with CIDR test_ipv4_with_cidr_ipaddrs := map[string]bool{ "192.168.1.1/24": true, "127.0.0.1/32": true, "10.10.90.12/8": true, "8.8.8.8/1": true, "4.4.4.4/7": true, "192.168.1.1/99": false, "127.0.0.1/9999": false, "10.10.90.12/33": false, "8.8.8.8/128": false, "4.4.4.4/256": false, } //IPv6 test_ipv6_ipaddrs := map[string]bool{ "2607:f0d0:1002:51::4": true, "2607:f0d0:1002:0051:0000:0000:0000:0004": true, "ff05::1:3": true, "FE80:0000:0000:0000:0202:B3FF:FE1E:8329": true, "FE80::0202:B3FF:FE1E:8329": true, "fe80::202:b3ff:fe1e:8329": true, "fe80:0000:0000:0000:0202:b3ff:fe1e:8329": true, "2001:470:1f09:495::3": true, "2001:470:1f1d:275::1": true, "2600:9000:5304:200::1": true, "2600:9000:5306:d500::1": true, "2600:9000:5301:b600::1": true, "2600:9000:5303:900::1": true, "127:12:12:12:12:12:!2:!2": false, "127.0.0.1": false, "234:23:23:23:23:23:23": false, } //IPv6 with CIDR test_ipv6_with_cidr_ipaddrs := map[string]bool{ "2000::/5": true, "2000::/15": true, "2001:db8::/33": true, "2001:db8::/48": true, "fc00::/7": true, } //IPv4-Mapped Embedded IPv6 Address test_ipv4_mapped_ipv6_ipaddrs := map[string]bool{ "2001:470:1f09:495::3:217.126.185.215": true, "2001:470:1f1d:275::1:213.0.69.132": true, "2600:9000:5304:200::1:205.251.196.2": true, "2600:9000:5306:d500::1:205.251.198.213": true, "2600:9000:5301:b600::1:205.251.193.182": true, "2600:9000:5303:900::1:205.251.195.9": true, "0:0:0:0:0:FFFF:222.1.41.90": true, "::FFFF:222.1.41.90": true, "0000:0000:0000:0000:0000:FFFF:12.155.166.101": true, "12.155.166.101": false, "12.12/12": false, } runIPAddrTestfunc(t, revel.IPv4, test_ipv4_ipaddrs, "invalid (%s) IPv4 address") runIPAddrTestfunc(t, revel.IPv4CIDR, test_ipv4_with_cidr_ipaddrs, "invalid (%s) IPv4 with CIDR address") runIPAddrTestfunc(t, revel.IPv6, test_ipv6_ipaddrs, "invalid (%s) IPv6 address") runIPAddrTestfunc(t, revel.IPv6CIDR, test_ipv6_with_cidr_ipaddrs, "invalid (%s) IPv6 with CIDR address") runIPAddrTestfunc(t, revel.IPv4MappedIPv6, test_ipv4_mapped_ipv6_ipaddrs, "invalid (%s) IPv4-Mapped Embedded IPv6 address") } func TestMacAddr(t *testing.T) { macaddr_list := map[string]bool{ "02:f3:71:eb:9e:4b": true, "02-f3-71-eb-9e-4b": true, "02f3.71eb.9e4b": true, "87:78:6e:3e:90:40": true, "87-78-6e-3e-90-40": true, "8778.6e3e.9040": true, "e7:28:b9:57:ab:36": true, "e7-28-b9-57-ab-36": true, "e728.b957.ab36": true, "eb:f8:2b:d7:e9:62": true, "eb-f8-2b-d7-e9-62": true, "ebf8.2bd7.e962": true, } test_macaddr_list := []Expect{} for macaddr, expected := range macaddr_list { test_macaddr_list = append(test_macaddr_list, Expect{input: macaddr, expectedResult: expected, errorMessage: fmt.Sprintf("invalid (%s) MAC address", macaddr)}) } for _, mac_test_list := range []revel.MacAddr{{}, revel.ValidMacAddr()} { performTests(mac_test_list, test_macaddr_list, t) } } func TestDomain(t *testing.T) { test_domains := map[string]bool{ "대한민국.xn-korea.co.kr": true, "google.com": true, "masełkowski.pl": true, "maselkowski.pl": true, "m.maselkowski.pl": true, "www.masełkowski.pl.com": true, "xn--masekowski-d0b.pl": true, "中国互联网络信息中心.中国": true, "masełkowski.pl.": false, "中国互联网络信息中心.xn--masekowski-d0b": false, "a.jp": true, "a.co": true, "a.co.jp": true, "a.co.or": true, "a.or.kr": true, "qwd-qwdqwd.com": true, "qwd-qwdqwd.co_m": false, "qwd-qwdqwd.c": false, "qwd-qwdqwd.-12": false, "qwd-qwdqwd.1212": false, "qwd-qwdqwd.org": true, "qwd-qwdqwd.ac.kr": true, "qwd-qwdqwd.gov": true, "chicken.beer": true, "aa.xyz": true, "google.asn.au": true, "google.com.au": true, "google.net.au": true, "google.priv.at": true, "google.ac.at": true, "google.gv.at": true, "google.avocat.fr": true, "google.geek.nz": true, "google.gen.nz": true, "google.kiwi.nz": true, "google.org.il": true, "google.net.il": true, "www.google.edu.au": true, "www.google.gov.au": true, "www.google.csiro.au": true, "www.google.act.au": true, "www.google.avocat.fr": true, "www.google.aeroport.fr": true, "www.google.co.nz": true, "www.google.geek.nz": true, "www.google.gen.nz": true, "www.google.kiwi.nz": true, "www.google.parliament.nz": true, "www.google.muni.il": true, "www.google.idf.il": true, } tests := []Expect{} for domain, expected := range test_domains { tests = append(tests, Expect{input: domain, expectedResult: expected, errorMessage: fmt.Sprintf("invalid (%s) domain", domain)}) } for _, domain := range []revel.Domain{{}, revel.ValidDomain()} { performTests(domain, tests, t) } } func TestURL(t *testing.T) { test_urls := map[string]bool{ "https://www.google.co.kr/url?sa=t&rct=j&q=&esrc=s&source=web": true, "http://stackoverflow.com/questions/27812164/can-i-import-3rd-party-package-into-golang-playground": true, "https://tour.golang.org/welcome/4": true, "https://revel.github.io/": true, "https://github.com/revel/revel/commit/bd1d083ee4345e919b3bca1e4c42ca682525e395#diff-972a2b2141d27e9d7a8a4149a7e28eef": true, "https://github.com/ndevilla/iniparser/pull/82#issuecomment-261817064": true, "http://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=0&rsv_idx=1&tn=baidu&wd=golang": true, "http://www.baidu.com/link?url=DrWkM_beo2M5kB5sLYnItKSQ0Ib3oDhKcPprdtLzAWNfFt_VN5oyD3KwnAKT6Xsk": true, } tests := []Expect{} for url, expected := range test_urls { tests = append(tests, Expect{input: url, expectedResult: expected, errorMessage: fmt.Sprintf("invalid (%s) url", url)}) } for _, url := range []revel.URL{{}, revel.ValidURL()} { performTests(url, tests, t) } } func TestPureTextNormal(t *testing.T) { test_txts := map[string]bool{ `qd08j123lneqw\t\nqwedojiqwd\rqwdoihjqwd1d[08jaedl;jkqwd\r\nqdolijqdwqwd`: false, `a\r\nbqd08j123lneqw\t\nqwedojiqwd\rqwdoihjqwd1d[08jaedl;jkqwd\r\nqdolijqdwqwd`: false, `FooBar`: false, `Foo<12>Bar`: true, `Foo<>Bar`: true, `Foo
Bar`: false, `Foo Baz`: false, `I <3 Ponies!`: true, `I like Golang\t\n`: true, `I & like Golang\t\n`: true, ` `: false, `I like Golang\r\n`: true, `I like Golang\r\na`: true, "I like Golang\t\n": true, "I & like Golang\t\n": true, `ハイレゾ対応ウォークマン®、ヘッドホン、スピーカー「Winter Gift Collection ~Presented by JUJU~」をソニーストアにて販売開始`: true, `VAIOパーソナルコンピューター type T TZシリーズ 無償点検・修理のお知らせとお詫び(2009年10月15日更新)`: true, `把百度设为主页关于百度About Baidu百度推广`: true, `%E6%8A%8A%E7%99%BE%E5%BA%A6%E8%AE%BE%E4%B8%BA%E4%B8%BB%E9%A1%B5%E5%85%B3%E4%BA%8E%E7%99%BE%E5%BA%A6About++Baidu%E7%99%BE%E5%BA%A6%E6%8E%A8%E5%B9%BF`: true, `%E6%8A%8A%E7%99%BE%E5%BA%A6%E8%AE%BE%E4%B8%BA%E4%B8%BB%E9%A1%B5%E5%85%B3%E4%BA%8E%E7%99%BE%E5%BA%A6About%20%20Baidu%E7%99%BE%E5%BA%A6%E6%8E%A8%E5%B9%BF`: true, `abcd/>qwdqwdoijhwer/>qwdojiqwdqwdqwdoijqwdoiqjd`: true, `abcd/>qwdqwdoijhwer/>qwdojiqwdqwdqwdoijqwdoiqjd`: false, ``: false, ``: false, `<img src="javascript:alert('abc')">`: false, `<a href="javascript:alert('hello');">AAA</a>`: false, } tests := []Expect{} for txt, expected := range test_txts { tests = append(tests, Expect{input: txt, expectedResult: expected, errorMessage: fmt.Sprintf("invalid (%#v) text", txt)}) } // normal for _, txt := range []revel.PureText{{revel.NORMAL}, revel.ValidPureText(revel.NORMAL)} { performTests(txt, tests, t) } } func TestPureTextStrict(t *testing.T) { test_txts := map[string]bool{ `qd08j123lneqw\t\nqwedojiqwd\rqwdoihjqwd1d[08jaedl;jkqwd\r\nqdolijqdwqwd`: false, `a\r\nbqd08j123lneqw\t\nqwedojiqwd\rqwdoihjqwd1d[08jaedl;jkqwd\r\nqdolijqdwqwd`: false, `FooBar`: false, `Foo<12>Bar`: false, `Foo<>Bar`: true, `Foo
Bar`: false, `Foo Baz`: false, `I <3 Ponies!`: true, `I like Golang\t\n`: false, `I & like Golang\t\n`: false, ` `: false, `I like Golang\r\n`: true, `I like Golang\r\na`: true, "I like Golang\t\n": false, "I & like Golang\t\n": false, `ハイレゾ対応ウォークマン®、ヘッドホン、スピーカー「Winter Gift Collection ~Presented by JUJU~」をソニーストアにて販売開始`: true, `VAIOパーソナルコンピューター type T TZシリーズ 無償点検・修理のお知らせとお詫び(2009年10月15日更新)`: true, `把百度设为主页关于百度About Baidu百度推广`: true, `%E6%8A%8A%E7%99%BE%E5%BA%A6%E8%AE%BE%E4%B8%BA%E4%B8%BB%E9%A1%B5%E5%85%B3%E4%BA%8E%E7%99%BE%E5%BA%A6About++Baidu%E7%99%BE%E5%BA%A6%E6%8E%A8%E5%B9%BF`: true, `%E6%8A%8A%E7%99%BE%E5%BA%A6%E8%AE%BE%E4%B8%BA%E4%B8%BB%E9%A1%B5%E5%85%B3%E4%BA%8E%E7%99%BE%E5%BA%A6About%20%20Baidu%E7%99%BE%E5%BA%A6%E6%8E%A8%E5%B9%BF`: true, `abcd/>qwdqwdoijhwer/>qwdojiqwdqwdqwdoijqwdoiqjd`: false, `abcd/>qwdqwdoijhwer/>qwdojiqwdqwdqwdoijqwdoiqjd`: false, ``: false, ``: false, `<img src="javascript:alert('abc')">`: false, `<a href="javascript:alert('hello');">AAA</a>`: false, } tests := []Expect{} for txt, expected := range test_txts { tests = append(tests, Expect{input: txt, expectedResult: expected, errorMessage: fmt.Sprintf("invalid (%#v) text", txt)}) } // strict for _, txt := range []revel.PureText{{revel.STRICT}, revel.ValidPureText(revel.STRICT)} { performTests(txt, tests, t) } } func TestFilePathOnlyFilePath(t *testing.T) { test_filepaths := map[string]bool{ "../../qwdqwdqwd/../qwdqwdqwd.txt": false, `../../qwdqwdqwd/.. /qwdqwdqwd.txt`: false, "\t../../qwdqwdqwd/../qwdqwdqwd.txt": false, `../../qwdqwdqwd/../qwdqwdqwd.txt`: false, `../../qwdqwdqwd/../qwdqwdqwd.txt`: false, "../../etc/passwd": false, "a.txt;rm -rf /": false, "sudo rm -rf ../": false, "a-1-s-d-v-we-wd_+qwd-qwd-qwd.txt": false, "a-qwdqwd_qwdqwdqwd-123.txt": true, "a.txt": true, "a-1-e-r-t-_1_21234_d_1234_qwed_1423_.txt": true, } tests := []Expect{} for filepath, expected := range test_filepaths { tests = append(tests, Expect{input: filepath, expectedResult: expected, errorMessage: fmt.Sprintf("unsanitary (%#v) string", filepath)}) } // filename without relative path for _, filepath := range []revel.FilePath{{revel.ONLY_FILENAME}, revel.ValidFilePath(revel.ONLY_FILENAME)} { performTests(filepath, tests, t) } } func TestFilePathAllowRelativePath(t *testing.T) { test_filepaths := map[string]bool{ "../../qwdqwdqwd/../qwdqwdqwd.txt": true, `../../qwdqwdqwd/.. /qwdqwdqwd.txt`: false, "\t../../qwdqwdqwd/../qwdqwdqwd.txt": false, `../../qwdqwdqwd/../qwdqwdqwd.txt`: false, `../../qwdqwdqwd/../qwdqwdqwd.txt`: false, "../../etc/passwd": true, "a.txt;rm -rf /": false, "sudo rm -rf ../": true, "a-1-s-d-v-we-wd_+qwd-qwd-qwd.txt": false, "a-qwdqwd_qwdqwdqwd-123.txt": true, "a.txt": true, "a-1-e-r-t-_1_21234_d_1234_qwed_1423_.txt": true, "/asdasd/asdasdasd/qwdqwd_qwdqwd/12-12/a-1-e-r-t-_1_21234_d_1234_qwed_1423_.txt": true, } tests := []Expect{} for filepath, expected := range test_filepaths { tests = append(tests, Expect{input: filepath, expectedResult: expected, errorMessage: fmt.Sprintf("unsanitary (%#v) string", filepath)}) } // filename with relative path for _, filepath := range []revel.FilePath{{revel.ALLOW_RELATIVE_PATH}, revel.ValidFilePath(revel.ALLOW_RELATIVE_PATH)} { performTests(filepath, tests, t) } } revel-1.0.0/version.go000066400000000000000000000006621370252312000146400ustar00rootroot00000000000000// Copyright (c) 2012-2018 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package revel const ( // Version current Revel version Version = "1.0.0" // BuildDate latest commit/release date BuildDate = "2020-07-11" // MinimumGoVersion minimum required Go version for Revel MinimumGoVersion = ">= go1.12" ) revel-1.0.0/watcher.go000066400000000000000000000207261370252312000146130ustar00rootroot00000000000000// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package revel import ( "os" "path/filepath" "strings" "sync" "github.com/fsnotify/fsnotify" "time" ) // Listener is an interface for receivers of filesystem events. type Listener interface { // Refresh is invoked by the watcher on relevant filesystem events. // If the listener returns an error, it is served to the user on the current request. Refresh() *Error } // DiscerningListener allows the receiver to selectively watch files. type DiscerningListener interface { Listener WatchDir(info os.FileInfo) bool WatchFile(basename string) bool } // Watcher allows listeners to register to be notified of changes under a given // directory. type Watcher struct { serial bool // true to process events in serial watchers []*fsnotify.Watcher // Parallel arrays of watcher/listener pairs. listeners []Listener // List of listeners for watcher forceRefresh bool // True to force the refresh lastError int // The last error found notifyMutex sync.Mutex // The mutext to serialize watches refreshTimer *time.Timer // The timer to countdown the next refresh timerMutex *sync.Mutex // A mutex to prevent concurrent updates refreshChannel chan *Error // The error channel to listen to when waiting for a refresh refreshChannelCount int // The number of clients listening on the channel refreshTimerMS time.Duration // The number of milliseconds between refreshing builds } func NewWatcher() *Watcher { return &Watcher{ forceRefresh: true, lastError: -1, refreshTimerMS: time.Duration(Config.IntDefault("watch.rebuild.delay", 10)), timerMutex: &sync.Mutex{}, refreshChannel: make(chan *Error, 10), refreshChannelCount: 0, } } // Listen registers for events within the given root directories (recursively). func (w *Watcher) Listen(listener Listener, roots ...string) { watcher, err := fsnotify.NewWatcher() if err != nil { utilLog.Fatal("Watcher: Failed to create watcher", "error", err) } // Replace the unbuffered Event channel with a buffered one. // Otherwise multiple change events only come out one at a time, across // multiple page views. (There appears no way to "pump" the events out of // the watcher) // This causes a notification when you do a check in go, since you are modifying a buffer in use watcher.Events = make(chan fsnotify.Event, 100) watcher.Errors = make(chan error, 10) // Walk through all files / directories under the root, adding each to watcher. for _, p := range roots { // is the directory / file a symlink? f, err := os.Lstat(p) if err == nil && f.Mode()&os.ModeSymlink == os.ModeSymlink { var realPath string realPath, err = filepath.EvalSymlinks(p) if err != nil { panic(err) } p = realPath } fi, err := os.Stat(p) if err != nil { utilLog.Error("Watcher: Failed to stat watched path, code will continue but auto updates will not work", "path", p, "error", err) continue } // If it is a file, watch that specific file. if !fi.IsDir() { err = watcher.Add(p) if err != nil { utilLog.Error("Watcher: Failed to watch, code will continue but auto updates will not work", "path", p, "error", err) } continue } var watcherWalker func(path string, info os.FileInfo, err error) error watcherWalker = func(path string, info os.FileInfo, err error) error { if err != nil { utilLog.Error("Watcher: Error walking path:", "error", err) return nil } if info.IsDir() { if dl, ok := listener.(DiscerningListener); ok { if !dl.WatchDir(info) { return filepath.SkipDir } } err := watcher.Add(path) if err != nil { utilLog.Error("Watcher: Failed to watch this path, code will continue but auto updates will not work", "path", path, "error", err) } } return nil } // Else, walk the directory tree. err = Walk(p, watcherWalker) if err != nil { utilLog.Error("Watcher: Failed to walk directory, code will continue but auto updates will not work", "path", p, "error", err) } } if w.eagerRebuildEnabled() { // Create goroutine to notify file changes in real time go w.NotifyWhenUpdated(listener, watcher) } w.watchers = append(w.watchers, watcher) w.listeners = append(w.listeners, listener) } // NotifyWhenUpdated notifies the watcher when a file event is received. func (w *Watcher) NotifyWhenUpdated(listener Listener, watcher *fsnotify.Watcher) { for { select { case ev := <-watcher.Events: if w.rebuildRequired(ev, listener) { // Serialize listener.Refresh() calls. if w.serial { // Serialize listener.Refresh() calls. w.notifyMutex.Lock() if err := listener.Refresh(); err != nil { utilLog.Error("Watcher: Listener refresh reported error:", "error", err) } w.notifyMutex.Unlock() } else { // Run refresh in parallel go func() { w.notifyInProcess(listener) }() } } case <-watcher.Errors: continue } } } // Notify causes the watcher to forward any change events to listeners. // It returns the first (if any) error returned. func (w *Watcher) Notify() *Error { // Serialize Notify() calls. w.notifyMutex.Lock() defer w.notifyMutex.Unlock() for i, watcher := range w.watchers { listener := w.listeners[i] // Pull all pending events / errors from the watcher. refresh := false for { select { case ev := <-watcher.Events: if w.rebuildRequired(ev, listener) { refresh = true } continue case <-watcher.Errors: continue default: // No events left to pull } break } if w.forceRefresh || refresh || w.lastError == i { var err *Error if w.serial { err = listener.Refresh() } else { err = w.notifyInProcess(listener) } if err != nil { w.lastError = i return err } } } w.forceRefresh = false w.lastError = -1 return nil } // Build a queue for refresh notifications // this will not return until one of the queue completes func (w *Watcher) notifyInProcess(listener Listener) (err *Error) { shouldReturn := false // This code block ensures that either a timer is created // or that a process would be added the the h.refreshChannel func() { w.timerMutex.Lock() defer w.timerMutex.Unlock() // If we are in the process of a rebuild, forceRefresh will always be true w.forceRefresh = true if w.refreshTimer != nil { utilLog.Info("Found existing timer running, resetting") w.refreshTimer.Reset(time.Millisecond * w.refreshTimerMS) shouldReturn = true w.refreshChannelCount++ } else { w.refreshTimer = time.NewTimer(time.Millisecond * w.refreshTimerMS) } }() // If another process is already waiting for the timer this one // only needs to return the output from the channel if shouldReturn { return <-w.refreshChannel } utilLog.Info("Waiting for refresh timer to expire") <-w.refreshTimer.C w.timerMutex.Lock() // Ensure the queue is properly dispatched even if a panic occurs defer func() { for x := 0; x < w.refreshChannelCount; x++ { w.refreshChannel <- err } w.refreshChannelCount = 0 w.refreshTimer = nil w.timerMutex.Unlock() }() err = listener.Refresh() if err != nil { utilLog.Info("Watcher: Recording error last build, setting rebuild on", "error", err) } else { w.lastError = -1 w.forceRefresh = false } utilLog.Info("Rebuilt, result", "error", err) return } // If watch.mode is set to eager, the application is rebuilt immediately // when a source file is changed. // This feature is available only in dev mode. func (w *Watcher) eagerRebuildEnabled() bool { return Config.BoolDefault("mode.dev", true) && Config.BoolDefault("watch", true) && Config.StringDefault("watch.mode", "normal") == "eager" } func (w *Watcher) rebuildRequired(ev fsnotify.Event, listener Listener) bool { // Ignore changes to dotfiles. if strings.HasPrefix(filepath.Base(ev.Name), ".") { return false } if dl, ok := listener.(DiscerningListener); ok { if !dl.WatchFile(ev.Name) || ev.Op&fsnotify.Chmod == fsnotify.Chmod { return false } } return true } var WatchFilter = func(c *Controller, fc []Filter) { if MainWatcher != nil { err := MainWatcher.Notify() if err != nil { c.Result = c.RenderError(err) return } } fc[0](c, fc[1:]) }