Use [chi](https://github.com/go-chi/chi) instead of the forked [macaron](https://gitea.com/macaron/macaron). Since macaron and chi have conflicts with session share, this big PR becomes a have-to thing. According my previous idea, we can replace macaron step by step but I'm wrong. :( Below is a list of big changes on this PR.
- [x] Define `context.ResponseWriter` interface with an implementation `context.Response`.
- [x] Use chi instead of macaron, and also a customize `Route` to wrap chi so that the router usage is similar as before.
- [x] Create different routers for `web`, `api`, `internal` and `install` so that the codes will be more clear and no magic .
- [x] Use https://github.com/unrolled/render instead of macaron's internal render
- [x] Use https://github.com/NYTimes/gziphandler instead of https://gitea.com/macaron/gzip
- [x] Use https://gitea.com/go-chi/session which is a modified version of https://gitea.com/macaron/session and removed `nodb` support since it will not be maintained. **BREAK**
- [x] Use https://gitea.com/go-chi/captcha which is a modified version of https://gitea.com/macaron/captcha
- [x] Use https://gitea.com/go-chi/cache which is a modified version of https://gitea.com/macaron/cache
- [x] Use https://gitea.com/go-chi/binding which is a modified version of https://gitea.com/macaron/binding
- [x] Use https://github.com/go-chi/cors instead of https://gitea.com/macaron/cors
- [x] Dropped https://gitea.com/macaron/i18n and make a new one in `code.gitea.io/gitea/modules/translation`
- [x] Move validation form structs from `code.gitea.io/gitea/modules/auth` to `code.gitea.io/gitea/modules/forms` to avoid dependency cycle.
- [x] Removed macaron log service because it's not need any more. **BREAK**
- [x] All form structs have to be get by `web.GetForm(ctx)` in the route function but not as a function parameter on routes definition.
- [x] Move Git HTTP protocol implementation to use routers directly.
- [x] Fix the problem that chi routes don't support trailing slash but macaron did.
- [x] `/api/v1/swagger` now will be redirect to `/api/swagger` but not render directly so that `APIContext` will not create a html render.
Notices:
- Chi router don't support request with trailing slash
- Integration test `TestUserHeatmap` maybe mysql version related. It's failed on my macOS(mysql 5.7.29 installed via brew) but succeed on CI.
Co-authored-by: 6543 <6543@obermui.de>
- path: modules/log/
linters:
- errcheck
- - path: routers/routes/macaron.go
+ - path: routers/routes/web.go
linters:
- dupl
- path: routers/api/v1/repo/issue_subscription.go
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/util"
- "gitea.com/macaron/session"
+ "gitea.com/go-chi/session"
archiver "github.com/mholt/archiver/v3"
"github.com/urfave/cli"
)
return err
}
}
- c := routes.NewChi()
- routes.RegisterInstallRoute(c)
+ c := routes.InstallRoutes()
err := listen(c, false)
select {
case <-graceful.GetManager().IsShutdown():
return err
}
}
- // Set up Chi routes
- c := routes.NewChi()
- c.Mount("/", routes.NormalRoutes())
- routes.DelegateToMacaron(c)
+ // Set up Chi routes
+ c := routes.NormalRoutes()
err := listen(c, true)
<-graceful.GetManager().Done()
log.Info("PID: %d Gitea Web Finished", os.Getpid())
//routers.GlobalInit()
external.RegisterParsers()
markup.Init()
- c := routes.NewChi()
- c.Mount("/", routes.NormalRoutes())
- routes.DelegateToMacaron(c)
+ c := routes.NormalRoutes()
log.Printf("[PR] Ready for testing !\n")
log.Printf("[PR] Login with user1, user2, user3, ... with pass: password\n")
## Session (`session`)
-- `PROVIDER`: **memory**: Session engine provider \[memory, file, redis, mysql, couchbase, memcache, nodb, postgres\].
+- `PROVIDER`: **memory**: Session engine provider \[memory, file, redis, mysql, couchbase, memcache, postgres\].
- `PROVIDER_CONFIG`: **data/sessions**: For file, the root path; for others, the connection string.
- `COOKIE_SECURE`: **false**: Enable this to force using HTTPS for all session access.
- `COOKIE_NAME`: **i\_like\_gitea**: The name of the cookie used for the session ID.
- `MODE`: **console**: Logging mode. For multiple modes, use a comma to separate values. You can configure each mode in per mode log subsections `\[log.modename\]`. By default the file mode will log to `$ROOT_PATH/gitea.log`.
- `LEVEL`: **Info**: General log level. \[Trace, Debug, Info, Warn, Error, Critical, Fatal, None\]
- `STACKTRACE_LEVEL`: **None**: Default log level at which to log create stack traces. \[Trace, Debug, Info, Warn, Error, Critical, Fatal, None\]
-- `REDIRECT_MACARON_LOG`: **false**: Redirects the Macaron log to its own logger or the default logger.
-- `MACARON`: **file**: Logging mode for the macaron logger, use a comma to separate values. Configure each mode in per mode log subsections `\[log.modename.macaron\]`. By default the file mode will log to `$ROOT_PATH/macaron.log`. (If you set this to `,` it will log to default gitea logger.)
- `ROUTER_LOG_LEVEL`: **Info**: The log level that the router should log at. (If you are setting the access log, its recommended to place this at Debug.)
- `ROUTER`: **console**: The mode or name of the log the router should log to. (If you set this to `,` it will log to default gitea logger.)
NB: You must `REDIRECT_MACARON_LOG` and have `DISABLE_ROUTER_LOG` set to `false` for this option to take effect. Configure each mode in per mode log subsections `\[log.modename.router\]`.
- `ACCESS`: **file**: Logging mode for the access logger, use a comma to separate values. Configure each mode in per mode log subsections `\[log.modename.access\]`. By default the file mode will log to `$ROOT_PATH/access.log`. (If you set this to `,` it will log to the default gitea logger.)
- `ACCESS_LOG_TEMPLATE`: **`{{.Ctx.RemoteAddr}} - {{.Identity}} {{.Start.Format "[02/Jan/2006:15:04:05 -0700]" }} "{{.Ctx.Req.Method}} {{.Ctx.Req.URL.RequestURI}} {{.Ctx.Req.Proto}}" {{.ResponseWriter.Status}} {{.ResponseWriter.Size}} "{{.Ctx.Req.Referer}}\" \"{{.Ctx.Req.UserAgent}}"`**: Sets the template used to create the access log.
- The following variables are available:
- - `Ctx`: the `macaron.Context` of the request.
+ - `Ctx`: the `context.Context` of the request.
- `Identity`: the SignedUserName or `"-"` if not logged in.
- `Start`: the start time of the request.
- `ResponseWriter`: the responseWriter from the request.
its subsection, but will default to the name. This allows you to have
multiple subloggers that will log to files.
-### The "Macaron" logger
-
-By default Macaron will log to its own go `log` instance. This writes
-to `os.Stdout`. You can redirect this log to a Gitea configurable logger
-through setting the `REDIRECT_MACARON_LOG` setting in the `[log]`
-section which you can configure the outputs of by setting the `MACARON`
-value in the `[log]` section of the configuration. `MACARON` defaults
-to `file` if unset.
-
-Please note, the macaron logger will log at `INFO` level, setting the
-`LEVEL` of this logger to `WARN` or above will result in no macaron logs.
-
-Each output sublogger for this logger is configured in
-`[log.sublogger.macaron]` sections. There are certain default values
-which will not be inherited from the `[log]` or relevant
-`[log.sublogger]` sections:
-
-- `FLAGS` is `stdflags` (Equal to
- `date,time,medfile,shortfuncname,levelinitial`)
-- `FILE_NAME` will default to `%(ROOT_PATH)/macaron.log`
-- `EXPRESSION` will default to `""`
-- `PREFIX` will default to `""`
-
-NB: You can redirect the macaron logger to send its events to the gitea
-log using the value: `MACARON = ,`
-
### The "Router" logger
-There are two types of Router log. By default Macaron send its own
-router log which will be directed to Macaron's go `log`, however if you
-`REDIRECT_MACARON_LOG` you will enable Gitea's router log. You can
-disable both types of Router log by setting `DISABLE_ROUTER_LOG`.
+You can disable Router log by setting `DISABLE_ROUTER_LOG`.
-If you enable the redirect, you can configure the outputs of this
+You can configure the outputs of this
router log by setting the `ROUTER` value in the `[log]` section of the
configuration. `ROUTER` will default to `console` if unset. The Gitea
Router logs the same data as the Macaron log but has slightly different
The template is passed following options:
-- `Ctx` is the `macaron.Context`
+- `Ctx` is the `context.Context`
- `Identity` is the `SignedUserName` or `"-"` if the user is not logged
in
- `Start` is the start time of the request
-- `ResponseWriter` is the `macaron.ResponseWriter`
+- `ResponseWriter` is the `http.ResponseWriter`
Caution must be taken when changing this template as it runs outside of
the standard panic recovery trap. The template should also be as simple
## Components
-* Web framework: [Macaron](http://go-macaron.com/)
+* Web framework: [Chi](http://github.com/go-chi/chi)
* ORM: [XORM](https://xorm.io)
* UI components:
* [Semantic UI](http://semantic-ui.com/)
## Composants
-* Framework web : [Macaron](http://go-macaron.com/)
+* Framework web : [Chi](http://github.com/go-chi/chi)
* ORM: [XORM](https://xorm.io)
* Interface graphique :
* [Semantic UI](http://semantic-ui.com/)
## 组件
-* Web框架: [Macaron](http://go-macaron.com/)
+* Web框架: [Chi](http://github.com/go-chi/chi)
* ORM: [XORM](https://xorm.io)
* UI组件:
* [Semantic UI](http://semantic-ui.com/)
## 元件
-* Web 框架: [Macaron](http://go-macaron.com/)
+* Web 框架: [Chi](http://github.com/go-chi/chi)
* ORM: [XORM](https://xorm.io)
* UI 元件:
* [Semantic UI](http://semantic-ui.com/)
require (
code.gitea.io/gitea-vet v0.2.1
code.gitea.io/sdk/gitea v0.13.1
+ gitea.com/go-chi/binding v0.0.0-20210113025129-03f1d313373c
+ gitea.com/go-chi/cache v0.0.0-20210110083709-82c4c9ce2d5e
+ gitea.com/go-chi/captcha v0.0.0-20210110083842-e7696c336a1e
gitea.com/go-chi/session v0.0.0-20210108030337-0cb48c5ba8ee
gitea.com/lunny/levelqueue v0.3.0
- gitea.com/macaron/binding v0.0.0-20190822013154-a5f53841ed2b
- gitea.com/macaron/cache v0.0.0-20200924044943-905232fba10b
- gitea.com/macaron/captcha v0.0.0-20200825161008-e8597820aaca
- gitea.com/macaron/cors v0.0.0-20190826180238-95aec09ea8b4
- gitea.com/macaron/csrf v0.0.0-20190822024205-3dc5a4474439
- gitea.com/macaron/gzip v0.0.0-20200827120000-efa5e8477cf5
- gitea.com/macaron/i18n v0.0.0-20200911004404-4ca3dd0cbd60
- gitea.com/macaron/inject v0.0.0-20190805023432-d4c86e31027a
- gitea.com/macaron/macaron v1.5.1-0.20201027213641-0db5d4584804
- gitea.com/macaron/session v0.0.0-20201103015045-a177a2701dee
- gitea.com/macaron/toolbox v0.0.0-20190822013122-05ff0fc766b7
+ github.com/NYTimes/gziphandler v1.1.1
github.com/PuerkitoBio/goquery v1.5.1
github.com/RoaringBitmap/roaring v0.5.5 // indirect
github.com/alecthomas/chroma v0.8.2
github.com/gliderlabs/ssh v0.3.1
github.com/glycerine/go-unsnap-stream v0.0.0-20190901134440-81cf024a9e0a // indirect
github.com/go-chi/chi v1.5.1
+ github.com/go-chi/cors v1.1.1
github.com/go-enry/go-enry/v2 v2.6.0
github.com/go-git/go-billy/v5 v5.0.0
github.com/go-git/go-git/v5 v5.2.0
code.gitea.io/sdk/gitea v0.13.1 h1:Y7bpH2iO6Q0KhhMJfjP/LZ0AmiYITeRQlCD8b0oYqhk=
code.gitea.io/sdk/gitea v0.13.1/go.mod h1:z3uwDV/b9Ls47NGukYM9XhnHtqPh/J+t40lsUrR6JDY=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
+gitea.com/go-chi/binding v0.0.0-20210113025129-03f1d313373c h1:NTtrGYjR40WUdkCdn26Y5LGFT52rIkFPkjmtgCAyiTs=
+gitea.com/go-chi/binding v0.0.0-20210113025129-03f1d313373c/go.mod h1:9bGA9dIsrz+wVQKH1DzvxuAvrudHaQ8Wx8hLme/GVGQ=
+gitea.com/go-chi/cache v0.0.0-20210110083709-82c4c9ce2d5e h1:zgPGaf3kXP0cVm9J0l8ZA2+XDzILYATg0CXbihR6N+o=
+gitea.com/go-chi/cache v0.0.0-20210110083709-82c4c9ce2d5e/go.mod h1:k2V/gPDEtXGjjMGuBJiapffAXTv76H4snSmlJRLUhH0=
+gitea.com/go-chi/captcha v0.0.0-20210110083842-e7696c336a1e h1:YjaQU6XFicdhPN+MlGolcXO8seYY2+EY5g7vZPB17CQ=
+gitea.com/go-chi/captcha v0.0.0-20210110083842-e7696c336a1e/go.mod h1:nfA7JaGv3hbGQ1ktdhAsZhdS84qKffI8NMlHr+Opsog=
gitea.com/go-chi/session v0.0.0-20210108030337-0cb48c5ba8ee h1:9U6HuKUBt/cGK6T/64dEuz0r7Yp97WAAEJvXHDlY3ws=
gitea.com/go-chi/session v0.0.0-20210108030337-0cb48c5ba8ee/go.mod h1:Ozg8IchVNb/Udg+ui39iHRYqVHSvf3C99ixdpLR8Vu0=
gitea.com/lunny/levelqueue v0.3.0 h1:MHn1GuSZkxvVEDMyAPqlc7A3cOW+q8RcGhRgH/xtm6I=
gitea.com/lunny/levelqueue v0.3.0/go.mod h1:HBqmLbz56JWpfEGG0prskAV97ATNRoj5LDmPicD22hU=
-gitea.com/lunny/log v0.0.0-20190322053110-01b5df579c4e h1:r1en/D7xJmcY24VkHkjkcJFa+7ZWubVWPBrvsHkmHxk=
-gitea.com/lunny/log v0.0.0-20190322053110-01b5df579c4e/go.mod h1:uJEsN4LQpeGYRCjuPXPZBClU7N5pWzGuyF4uqLpE/e0=
-gitea.com/lunny/nodb v0.0.0-20200923032308-3238c4655727 h1:ZF2Bd6rqVlwhIDhYiS0uGYcT+GaVNGjuKVJkTNqWMIs=
-gitea.com/lunny/nodb v0.0.0-20200923032308-3238c4655727/go.mod h1:h0OwsgcpJLSYtHcM5+Xciw9OEeuxi6ty4HDiO8C7aIY=
-gitea.com/macaron/binding v0.0.0-20190822013154-a5f53841ed2b h1:vXt85uYV17KURaUlhU7v4GbCShkqRZDSfo0TkC0YCjQ=
-gitea.com/macaron/binding v0.0.0-20190822013154-a5f53841ed2b/go.mod h1:Cxadig6POWpPYYSfg23E7jo35Yf0yvsdC1lifoKWmPo=
-gitea.com/macaron/cache v0.0.0-20190822004001-a6e7fee4ee76 h1:mMsMEg90c5KXQgRWsH8D6GHXfZIW1RAe5S9VYIb12lM=
-gitea.com/macaron/cache v0.0.0-20190822004001-a6e7fee4ee76/go.mod h1:NFHb9Of+LUnU86bU20CiXXg6ZlgCJ4XytP14UsHOXFs=
-gitea.com/macaron/cache v0.0.0-20200924044943-905232fba10b h1:2ZE0JE3bKVBcP1VTrWeE1jqWwCAMIzfOQm1U9EGbBKU=
-gitea.com/macaron/cache v0.0.0-20200924044943-905232fba10b/go.mod h1:W5hKG8T1GBfypp5CRQlgoJU4figIL0jhx02y4XA/NOA=
-gitea.com/macaron/captcha v0.0.0-20200825161008-e8597820aaca h1:f5P41nXmXd/YOh8f6098Q0F1Y0QfpyRPSSIkni2XH4Q=
-gitea.com/macaron/captcha v0.0.0-20200825161008-e8597820aaca/go.mod h1:J5h3N+1nKTXtU1x4GxexaQKgAz8UiWecNwi/CfX7CtQ=
-gitea.com/macaron/cors v0.0.0-20190826180238-95aec09ea8b4 h1:e2rAFDejB0qN8OrY4xP4XSu8/yT6QmWxDZpB3J7r2GU=
-gitea.com/macaron/cors v0.0.0-20190826180238-95aec09ea8b4/go.mod h1:rtOK4J20kpMD9XcNsnO5YA843YSTe/MUMbDj/TJ/Q7A=
-gitea.com/macaron/csrf v0.0.0-20190822024205-3dc5a4474439 h1:88c34YM29a1GlWLrLBaG/GTT2htDdJz1u3n9+lmPolg=
-gitea.com/macaron/csrf v0.0.0-20190822024205-3dc5a4474439/go.mod h1:IsQPHx73HnnqFBYiVHjg87q4XBZyGXXu77xANukvZuk=
-gitea.com/macaron/gzip v0.0.0-20200827120000-efa5e8477cf5 h1:6rbhThlqfOb+sSmhrsVFz3bZoAeoloe7TZqyeiPbbWI=
-gitea.com/macaron/gzip v0.0.0-20200827120000-efa5e8477cf5/go.mod h1:z8vCjuhqDfvzPUJDowGqbsgoeYBvDbl95S5k6y43Pxo=
-gitea.com/macaron/i18n v0.0.0-20200911004404-4ca3dd0cbd60 h1:tNWNe5HBIlsfapFMtT4twTbXQmInRQWmdWNi8Di1ct0=
-gitea.com/macaron/i18n v0.0.0-20200911004404-4ca3dd0cbd60/go.mod h1:g5ope1b+iWhBdHzAn6EJ9u9Gp3FRESxpG+CDf7HYc/A=
-gitea.com/macaron/inject v0.0.0-20190803172902-8375ba841591/go.mod h1:h6E4kLao1Yko6DOU6QDnQPcuoNzvbZqzj2mtPcEn1aM=
-gitea.com/macaron/inject v0.0.0-20190805023432-d4c86e31027a h1:aOKEXkDTnh4euoH0so/THLXeHtQuqHmDPb1xEk6Ehok=
-gitea.com/macaron/inject v0.0.0-20190805023432-d4c86e31027a/go.mod h1:h6E4kLao1Yko6DOU6QDnQPcuoNzvbZqzj2mtPcEn1aM=
-gitea.com/macaron/macaron v1.3.3-0.20190803174002-53e005ff4827/go.mod h1:/rvxMjIkOq4BM8uPUb+VHuU02ZfAO6R4+wD//tiCiRw=
-gitea.com/macaron/macaron v1.3.3-0.20190821202302-9646c0587edb/go.mod h1:0coI+mSPSwbsyAbOuFllVS38awuk9mevhLD52l50Gjs=
-gitea.com/macaron/macaron v1.5.0 h1:TvWEcHw1/zaHlo0GTuKEukLh3A99+QsU2mjBrXLXjVQ=
-gitea.com/macaron/macaron v1.5.0/go.mod h1:P7hfDbQjcW22lkYkXlxdRIfWOXxH2+K4EogN4Q0UlLY=
-gitea.com/macaron/macaron v1.5.1-0.20201027213641-0db5d4584804 h1:yUiJVZKzdXsBe2tumTAXHBZa1qPGoGXM3fBG4RJ5fQg=
-gitea.com/macaron/macaron v1.5.1-0.20201027213641-0db5d4584804/go.mod h1:P7hfDbQjcW22lkYkXlxdRIfWOXxH2+K4EogN4Q0UlLY=
-gitea.com/macaron/session v0.0.0-20190821211443-122c47c5f705/go.mod h1:1ujH0jD6Ca4iK9NL0Q2a7fG2chvXx5hVa7hBfABwpkA=
-gitea.com/macaron/session v0.0.0-20201103015045-a177a2701dee h1:8/N3a56RXRJ66nnep0z+T7oHCB0bY6lpvtjv9Y9FPhE=
-gitea.com/macaron/session v0.0.0-20201103015045-a177a2701dee/go.mod h1:5tJCkDbrwpGv+MQUSIZSOW0wFrkh0exsonJgOvBs1Dw=
-gitea.com/macaron/toolbox v0.0.0-20190822013122-05ff0fc766b7 h1:N9QFoeNsUXLhl14mefLzGluqV7w2mGU3u+iZU+jCeWk=
-gitea.com/macaron/toolbox v0.0.0-20190822013122-05ff0fc766b7/go.mod h1:kgsbFPPS4P+acDYDOPDa3N4IWWOuDJt5/INKRUz7aks=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
github.com/6543-forks/go-gogs-client v0.0.0-20210116182316-f2f8bc0ea9cc h1:FLylYVXDwK+YtrmXYnv2Q1Y5lQ9TU1Xp5F2vndIOTb4=
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
+github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
+github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/PuerkitoBio/goquery v1.5.1 h1:PSPBGne8NIUWw+/7vFBV+kG2J/5MOjbzc7154OaKCSE=
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
github.com/RoaringBitmap/roaring v0.5.5/go.mod h1:puNo5VdzwbaIQxSiDIwfXl4Hnc+fbovcX4IW/dSTtUk=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
-github.com/Unknwon/com v0.0.0-20190321035513-0fed4efef755/go.mod h1:voKvFVpXBJxdIPeqjoJuLK+UVcRlo/JLjeToGxPYu68=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
github.com/couchbase/ghistogram v0.1.0/go.mod h1:s1Jhy76zqfEecpNWJfWUiKZookAFaiGOEoyzgHt9i7k=
github.com/couchbase/go-couchbase v0.0.0-20201026062457-7b3be89bbd89 h1:uNLXQ6QO1TocD8BaN/KkRki0Xw0brCM1PKl/ZA5pgfs=
github.com/couchbase/go-couchbase v0.0.0-20201026062457-7b3be89bbd89/go.mod h1:+/bddYDxXsf9qt0xpDUtRR47A2GjaXmGGAqQ/k3GJ8A=
-github.com/couchbase/gomemcached v0.0.0-20190515232915-c4b4ca0eb21d/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c=
-github.com/couchbase/gomemcached v0.1.0 h1:whUde87n8CScx8ckMp2En5liqAlcuG3aKy/BQeBPu84=
-github.com/couchbase/gomemcached v0.1.0/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c=
github.com/couchbase/gomemcached v0.1.1 h1:xCS8ZglJDhrlQg3jmK7Rn1V8f7bPjXABLC05CgLQauc=
github.com/couchbase/gomemcached v0.1.1/go.mod h1:mxliKQxOv84gQ0bJWbI+w9Wxdpt9HjDvgW9MjCym5Vo=
-github.com/couchbase/goutils v0.0.0-20190315194238-f9d42b11473b/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
github.com/couchbase/goutils v0.0.0-20201030094643-5e82bb967e67 h1:NCqJ6fwen6YP0WlV/IyibaT0kPt3JEI1rA62V/UPKT4=
github.com/couchbase/goutils v0.0.0-20201030094643-5e82bb967e67/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
github.com/couchbase/moss v0.1.0/go.mod h1:9MaHIaRuy9pvLPUJxB8sh8OrLfyDczECVL37grCIubs=
github.com/couchbase/vellum v1.0.2 h1:BrbP0NKiyDdndMPec8Jjhy0U47CZ0Lgx3xUC2r9rZqw=
github.com/couchbase/vellum v1.0.2/go.mod h1:FcwrEivFpNi24R3jLOs3n+fs5RnuQnQqCLBJ1uAg1W4=
-github.com/couchbaselabs/go-couchbase v0.0.0-20190708161019-23e7ca2ce2b7 h1:1XjEY/gnjQ+AfXef2U6dxCquhiRzkEpxZuWqs+QxTL8=
-github.com/couchbaselabs/go-couchbase v0.0.0-20190708161019-23e7ca2ce2b7/go.mod h1:mby/05p8HE5yHEAKiIH/555NoblMs7PtW6NrYshDruc=
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-chi/chi v1.5.1 h1:kfTK3Cxd/dkMu/rKs5ZceWYp+t5CtiE7vmaTv3LjC6w=
github.com/go-chi/chi v1.5.1/go.mod h1:REp24E+25iKvxgeTfHmdUoL5x15kBiDBlnIl5bCwe2k=
+github.com/go-chi/cors v1.1.1 h1:eHuqxsIw89iXcWnWUN8R72JMibABJTN/4IOYI5WERvw=
+github.com/go-chi/cors v1.1.1/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I=
github.com/go-enry/go-enry/v2 v2.6.0 h1:nbGWQBpO+D+cJuRxNgSDFnFY9QWz3QM/CeZxU7VAH20=
github.com/go-enry/go-enry/v2 v2.6.0/go.mod h1:GVzIiAytiS5uT/QiuakK7TF1u4xDab87Y8V5EJRpsIQ=
github.com/go-enry/go-oniguruma v1.2.1 h1:k8aAMuJfMrqm/56SG2lV9Cfti6tC4x8673aHCcBk+eo=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99 h1:twflg0XRTjwKpxb/jFExr4HGq6on2dEOmnL6FV+fgPw=
github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
-github.com/klauspost/compress v1.9.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.11.3 h1:dB4Bn0tN3wdCzQxnS8r06kV74qN/TAfaIS0bVE8h3jc=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
-github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
github.com/smartystreets/assertions v1.1.1 h1:T/YLemO5Yp7KPzS+lVtu+WsHn8yoSwTfItdAd1r3cck=
github.com/smartystreets/assertions v1.1.1/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM=
github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs=
github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
-github.com/unknwon/i18n v0.0.0-20190805065654-5c6446a380b6/go.mod h1:+5rDk6sDGpl3azws3O+f+GpFSyN9GVr0K8cvQLQM2ZQ=
github.com/unknwon/i18n v0.0.0-20200823051745-09abd91c7f2c h1:679/gJXwrsHC3RATr0YYjZvDMJPYN7W9FGSGNoLmKxM=
github.com/unknwon/i18n v0.0.0-20200823051745-09abd91c7f2c/go.mod h1:+5rDk6sDGpl3azws3O+f+GpFSyN9GVr0K8cvQLQM2ZQ=
github.com/unknwon/paginater v0.0.0-20200328080006-042474bd0eae h1:ihaXiJkaca54IaCSnEXtE/uSZOmPxKZhDfVLrzZLFDs=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM=
-golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201217014255-9d1352758620 h1:3wPMTskHO3+O6jqTEXyFcsnuxMQOqYSaHsDxcbUXpqA=
golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
-gopkg.in/ini.v1 v1.44.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.44.2/go.mod h1:M3Cogqpuv0QCi3ExAY5V4uOt4qb/R3xZubo9m8lK5wg=
gopkg.in/ini.v1 v1.46.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/ini.v1 v1.60.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
"time"
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
+ auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/queue"
api "code.gitea.io/gitea/modules/structs"
"testing"
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
+ auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
issue_service "code.gitea.io/gitea/services/issue"
tag := "v1.1"
- urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/%s/",
+ urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/%s",
owner.Name, repo.Name, tag)
req := NewRequestf(t, "GET", urlStr)
nonexistingtag := "nonexistingtag"
- urlStr = fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/%s/",
+ urlStr = fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/%s",
owner.Name, repo.Name, nonexistingtag)
req = NewRequestf(t, "GET", urlStr)
session := loginUser(t, owner.LowerName)
token := getTokenForLoggedInUser(t, session)
- urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/delete-tag/?token=%s",
+ urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/delete-tag?token=%s",
owner.Name, repo.Name, token)
req := NewRequestf(t, http.MethodDelete, urlStr)
// Make sure that actual releases can't be deleted outright
createNewReleaseUsingAPI(t, session, token, owner, repo, "release-tag", "", "Release Tag", "test")
- urlStr = fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/release-tag/?token=%s",
+ urlStr = fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/release-tag?token=%s",
owner.Name, repo.Name, token)
req = NewRequestf(t, http.MethodDelete, urlStr)
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/routes"
- "gitea.com/macaron/session"
+ "gitea.com/go-chi/session"
"github.com/stretchr/testify/assert"
)
oldSessionConfig := setting.SessionConfig.ProviderConfig
defer func() {
setting.SessionConfig.ProviderConfig = oldSessionConfig
- c = routes.NewChi()
- c.Mount("/", routes.NormalRoutes())
- routes.DelegateToMacaron(c)
+ c = routes.NormalRoutes()
}()
var config session.Options
setting.SessionConfig.ProviderConfig = string(newConfigBytes)
- c = routes.NewChi()
- c.Mount("/", routes.NormalRoutes())
- routes.DelegateToMacaron(c)
+ c = routes.NormalRoutes()
t.Run("NoSessionOnViewIssue", func(t *testing.T) {
defer PrintCurrentTest(t)()
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers"
"code.gitea.io/gitea/routers/routes"
"github.com/PuerkitoBio/goquery"
- "github.com/go-chi/chi"
"github.com/stretchr/testify/assert"
)
-var c chi.Router
+var c *web.Route
type NilResponseRecorder struct {
httptest.ResponseRecorder
defer cancel()
initIntegrationTest()
- c = routes.NewChi()
- c.Mount("/", routes.NormalRoutes())
- routes.DelegateToMacaron(c)
+ c = routes.NormalRoutes()
// integration test settings...
if setting.Cfg != nil {
func NewRequestWithBody(t testing.TB, method, urlStr string, body io.Reader) *http.Request {
t.Helper()
+ if !strings.HasPrefix(urlStr, "http") && !strings.HasPrefix(urlStr, "/") {
+ urlStr = "/" + urlStr
+ }
request, err := http.NewRequest(method, urlStr, body)
assert.NoError(t, err)
request.RequestURI = urlStr
"code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
+ "code.gitea.io/gitea/routers/routes"
- "gitea.com/macaron/gzip"
gzipp "github.com/klauspost/compress/gzip"
"github.com/stretchr/testify/assert"
)
t.Skip()
return
}
- content := make([]byte, gzip.MinSize*10)
+ content := make([]byte, routes.GzipMinSize*10)
for i := range content {
content[i] = byte(i % 256)
}
t.Skip()
return
}
- b := make([]byte, gzip.MinSize*10)
+ b := make([]byte, routes.GzipMinSize*10)
for i := range b {
b[i] = byte(i % 256)
}
t.Skip()
return
}
- b := make([]byte, gzip.MinSize*10)
+ b := make([]byte, routes.GzipMinSize*10)
for i := range b {
b[i] = byte(i % 256)
}
"/user/login",
"/user/forgot_password",
"/api/swagger",
- "/api/v1/swagger",
"/user2/repo1",
"/user2/repo1/projects",
"/user2/repo1/projects/1",
"/user2/repo1/src/master/file.txt": "/user2/repo1/src/branch/master/file.txt",
"/user2/repo1/src/master/directory/file.txt": "/user2/repo1/src/branch/master/directory/file.txt",
"/user/avatar/Ghost/-1": "/img/avatar_default.png",
+ "/api/v1/swagger": "/api/swagger",
}
for link, redirectLink := range redirects {
req := NewRequest(t, "GET", link)
"/",
"/user/forgot_password",
"/api/swagger",
- "/api/v1/swagger",
"/issues",
"/issues?type=your_repositories&repos=[0]&sort=&state=open",
"/issues?type=assigned&repos=[0]&sort=&state=open",
+++ /dev/null
-// Copyright 2014 The Gogs Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package auth
-
-import (
- "gitea.com/macaron/binding"
- "gitea.com/macaron/macaron"
-)
-
-// AdminCreateUserForm form for admin to create user
-type AdminCreateUserForm struct {
- LoginType string `binding:"Required"`
- LoginName string
- UserName string `binding:"Required;AlphaDashDot;MaxSize(40)"`
- Email string `binding:"Required;Email;MaxSize(254)"`
- Password string `binding:"MaxSize(255)"`
- SendNotify bool
- MustChangePassword bool
-}
-
-// Validate validates form fields
-func (f *AdminCreateUserForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// AdminEditUserForm form for admin to create user
-type AdminEditUserForm struct {
- LoginType string `binding:"Required"`
- UserName string `binding:"AlphaDashDot;MaxSize(40)"`
- LoginName string
- FullName string `binding:"MaxSize(100)"`
- Email string `binding:"Required;Email;MaxSize(254)"`
- Password string `binding:"MaxSize(255)"`
- Website string `binding:"ValidUrl;MaxSize(255)"`
- Location string `binding:"MaxSize(50)"`
- MaxRepoCreation int
- Active bool
- Admin bool
- Restricted bool
- AllowGitHook bool
- AllowImportLocal bool
- AllowCreateOrganization bool
- ProhibitLogin bool
- Reset2FA bool `form:"reset_2fa"`
-}
-
-// Validate validates form fields
-func (f *AdminEditUserForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// AdminDashboardForm form for admin dashboard operations
-type AdminDashboardForm struct {
- Op string `binding:"required"`
- From string
-}
-
-// Validate validates form fields
-func (f *AdminDashboardForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
+++ /dev/null
-// Copyright 2014 The Gogs Authors. All rights reserved.
-// Copyright 2019 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package auth
-
-import (
- "reflect"
- "strings"
-
- "code.gitea.io/gitea/modules/validation"
-
- "gitea.com/macaron/binding"
- "gitea.com/macaron/macaron"
- "github.com/unknwon/com"
-)
-
-// IsAPIPath if URL is an api path
-func IsAPIPath(url string) bool {
- return strings.HasPrefix(url, "/api/")
-}
-
-// Form form binding interface
-type Form interface {
- binding.Validator
-}
-
-func init() {
- binding.SetNameMapper(com.ToSnakeCase)
-}
-
-// AssignForm assign form values back to the template data.
-func AssignForm(form interface{}, data map[string]interface{}) {
- typ := reflect.TypeOf(form)
- val := reflect.ValueOf(form)
-
- if typ.Kind() == reflect.Ptr {
- typ = typ.Elem()
- val = val.Elem()
- }
-
- for i := 0; i < typ.NumField(); i++ {
- field := typ.Field(i)
-
- fieldName := field.Tag.Get("form")
- // Allow ignored fields in the struct
- if fieldName == "-" {
- continue
- } else if len(fieldName) == 0 {
- fieldName = com.ToSnakeCase(field.Name)
- }
-
- data[fieldName] = val.Field(i).Interface()
- }
-}
-
-func getRuleBody(field reflect.StructField, prefix string) string {
- for _, rule := range strings.Split(field.Tag.Get("binding"), ";") {
- if strings.HasPrefix(rule, prefix) {
- return rule[len(prefix) : len(rule)-1]
- }
- }
- return ""
-}
-
-// GetSize get size int form tag
-func GetSize(field reflect.StructField) string {
- return getRuleBody(field, "Size(")
-}
-
-// GetMinSize get minimal size in form tag
-func GetMinSize(field reflect.StructField) string {
- return getRuleBody(field, "MinSize(")
-}
-
-// GetMaxSize get max size in form tag
-func GetMaxSize(field reflect.StructField) string {
- return getRuleBody(field, "MaxSize(")
-}
-
-// GetInclude get include in form tag
-func GetInclude(field reflect.StructField) string {
- return getRuleBody(field, "Include(")
-}
-
-func validate(errs binding.Errors, data map[string]interface{}, f Form, l macaron.Locale) binding.Errors {
- if errs.Len() == 0 {
- return errs
- }
-
- data["HasError"] = true
- // If the field with name errs[0].FieldNames[0] is not found in form
- // somehow, some code later on will panic on Data["ErrorMsg"].(string).
- // So initialize it to some default.
- data["ErrorMsg"] = l.Tr("form.unknown_error")
- AssignForm(f, data)
-
- typ := reflect.TypeOf(f)
- val := reflect.ValueOf(f)
-
- if typ.Kind() == reflect.Ptr {
- typ = typ.Elem()
- val = val.Elem()
- }
-
- if field, ok := typ.FieldByName(errs[0].FieldNames[0]); ok {
- fieldName := field.Tag.Get("form")
- if fieldName != "-" {
- data["Err_"+field.Name] = true
-
- trName := field.Tag.Get("locale")
- if len(trName) == 0 {
- trName = l.Tr("form." + field.Name)
- } else {
- trName = l.Tr(trName)
- }
-
- switch errs[0].Classification {
- case binding.ERR_REQUIRED:
- data["ErrorMsg"] = trName + l.Tr("form.require_error")
- case binding.ERR_ALPHA_DASH:
- data["ErrorMsg"] = trName + l.Tr("form.alpha_dash_error")
- case binding.ERR_ALPHA_DASH_DOT:
- data["ErrorMsg"] = trName + l.Tr("form.alpha_dash_dot_error")
- case validation.ErrGitRefName:
- data["ErrorMsg"] = trName + l.Tr("form.git_ref_name_error")
- case binding.ERR_SIZE:
- data["ErrorMsg"] = trName + l.Tr("form.size_error", GetSize(field))
- case binding.ERR_MIN_SIZE:
- data["ErrorMsg"] = trName + l.Tr("form.min_size_error", GetMinSize(field))
- case binding.ERR_MAX_SIZE:
- data["ErrorMsg"] = trName + l.Tr("form.max_size_error", GetMaxSize(field))
- case binding.ERR_EMAIL:
- data["ErrorMsg"] = trName + l.Tr("form.email_error")
- case binding.ERR_URL:
- data["ErrorMsg"] = trName + l.Tr("form.url_error")
- case binding.ERR_INCLUDE:
- data["ErrorMsg"] = trName + l.Tr("form.include_error", GetInclude(field))
- case validation.ErrGlobPattern:
- data["ErrorMsg"] = trName + l.Tr("form.glob_pattern_error", errs[0].Message)
- default:
- data["ErrorMsg"] = l.Tr("form.unknown_error") + " " + errs[0].Classification
- }
- return errs
- }
- }
- return errs
-}
+++ /dev/null
-// Copyright 2014 The Gogs Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package auth
-
-import (
- "gitea.com/macaron/binding"
- "gitea.com/macaron/macaron"
-)
-
-// AuthenticationForm form for authentication
-type AuthenticationForm struct {
- ID int64
- Type int `binding:"Range(2,7)"`
- Name string `binding:"Required;MaxSize(30)"`
- Host string
- Port int
- BindDN string
- BindPassword string
- UserBase string
- UserDN string
- AttributeUsername string
- AttributeName string
- AttributeSurname string
- AttributeMail string
- AttributeSSHPublicKey string
- AttributesInBind bool
- UsePagedSearch bool
- SearchPageSize int
- Filter string
- AdminFilter string
- GroupsEnabled bool
- GroupDN string
- GroupFilter string
- GroupMemberUID string
- UserUID string
- RestrictedFilter string
- AllowDeactivateAll bool
- IsActive bool
- IsSyncEnabled bool
- SMTPAuth string
- SMTPHost string
- SMTPPort int
- AllowedDomains string
- SecurityProtocol int `binding:"Range(0,2)"`
- TLS bool
- SkipVerify bool
- PAMServiceName string
- Oauth2Provider string
- Oauth2Key string
- Oauth2Secret string
- OpenIDConnectAutoDiscoveryURL string
- Oauth2UseCustomURL bool
- Oauth2TokenURL string
- Oauth2AuthURL string
- Oauth2ProfileURL string
- Oauth2EmailURL string
- Oauth2IconURL string
- SSPIAutoCreateUsers bool
- SSPIAutoActivateUsers bool
- SSPIStripDomainNames bool
- SSPISeparatorReplacement string `binding:"AlphaDashDot;MaxSize(5)"`
- SSPIDefaultLanguage string
-}
-
-// Validate validates fields
-func (f *AuthenticationForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
+++ /dev/null
-// Copyright 2014 The Gogs Authors. All rights reserved.
-// Copyright 2019 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package auth
-
-import (
- "code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/structs"
-
- "gitea.com/macaron/binding"
- "gitea.com/macaron/macaron"
-)
-
-// ________ .__ __ .__
-// \_____ \_______ _________ ____ |__|____________ _/ |_|__| ____ ____
-// / | \_ __ \/ ___\__ \ / \| \___ /\__ \\ __\ |/ _ \ / \
-// / | \ | \/ /_/ > __ \| | \ |/ / / __ \| | | ( <_> ) | \
-// \_______ /__| \___ (____ /___| /__/_____ \(____ /__| |__|\____/|___| /
-// \/ /_____/ \/ \/ \/ \/ \/
-
-// CreateOrgForm form for creating organization
-type CreateOrgForm struct {
- OrgName string `binding:"Required;AlphaDashDot;MaxSize(40)" locale:"org.org_name_holder"`
- Visibility structs.VisibleType
- RepoAdminChangeTeamAccess bool
-}
-
-// Validate validates the fields
-func (f *CreateOrgForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// UpdateOrgSettingForm form for updating organization settings
-type UpdateOrgSettingForm struct {
- Name string `binding:"Required;AlphaDashDot;MaxSize(40)" locale:"org.org_name_holder"`
- FullName string `binding:"MaxSize(100)"`
- Description string `binding:"MaxSize(255)"`
- Website string `binding:"ValidUrl;MaxSize(255)"`
- Location string `binding:"MaxSize(50)"`
- Visibility structs.VisibleType
- MaxRepoCreation int
- RepoAdminChangeTeamAccess bool
-}
-
-// Validate validates the fields
-func (f *UpdateOrgSettingForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// ___________
-// \__ ___/___ _____ _____
-// | |_/ __ \\__ \ / \
-// | |\ ___/ / __ \| Y Y \
-// |____| \___ >____ /__|_| /
-// \/ \/ \/
-
-// CreateTeamForm form for creating team
-type CreateTeamForm struct {
- TeamName string `binding:"Required;AlphaDashDot;MaxSize(30)"`
- Description string `binding:"MaxSize(255)"`
- Permission string
- Units []models.UnitType
- RepoAccess string
- CanCreateOrgRepo bool
-}
-
-// Validate validates the fields
-func (f *CreateTeamForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
+++ /dev/null
-// Copyright 2017 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package auth
-
-import (
- "gitea.com/macaron/binding"
- "gitea.com/macaron/macaron"
-)
-
-// NewBranchForm form for creating a new branch
-type NewBranchForm struct {
- NewBranchName string `binding:"Required;MaxSize(100);GitRefName"`
-}
-
-// Validate validates the fields
-func (f *NewBranchForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
+++ /dev/null
-// Copyright 2014 The Gogs Authors. All rights reserved.
-// Copyright 2017 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package auth
-
-import (
- "net/url"
- "strings"
-
- "code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/structs"
- "code.gitea.io/gitea/modules/util"
- "code.gitea.io/gitea/routers/utils"
-
- "gitea.com/macaron/binding"
- "gitea.com/macaron/macaron"
-)
-
-// _______________________________________ _________.______________________ _______________.___.
-// \______ \_ _____/\______ \_____ \ / _____/| \__ ___/\_____ \\______ \__ | |
-// | _/| __)_ | ___// | \ \_____ \ | | | | / | \| _// | |
-// | | \| \ | | / | \/ \| | | | / | \ | \\____ |
-// |____|_ /_______ / |____| \_______ /_______ /|___| |____| \_______ /____|_ // ______|
-// \/ \/ \/ \/ \/ \/ \/
-
-// CreateRepoForm form for creating repository
-type CreateRepoForm struct {
- UID int64 `binding:"Required"`
- RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"`
- Private bool
- Description string `binding:"MaxSize(255)"`
- DefaultBranch string `binding:"GitRefName;MaxSize(100)"`
- AutoInit bool
- Gitignores string
- IssueLabels string
- License string
- Readme string
- Template bool
-
- RepoTemplate int64
- GitContent bool
- Topics bool
- GitHooks bool
- Webhooks bool
- Avatar bool
- Labels bool
- TrustModel string
-}
-
-// Validate validates the fields
-func (f *CreateRepoForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// MigrateRepoForm form for migrating repository
-// this is used to interact with web ui
-type MigrateRepoForm struct {
- // required: true
- CloneAddr string `json:"clone_addr" binding:"Required"`
- Service structs.GitServiceType `json:"service"`
- AuthUsername string `json:"auth_username"`
- AuthPassword string `json:"auth_password"`
- AuthToken string `json:"auth_token"`
- // required: true
- UID int64 `json:"uid" binding:"Required"`
- // required: true
- RepoName string `json:"repo_name" binding:"Required;AlphaDashDot;MaxSize(100)"`
- Mirror bool `json:"mirror"`
- Private bool `json:"private"`
- Description string `json:"description" binding:"MaxSize(255)"`
- Wiki bool `json:"wiki"`
- Milestones bool `json:"milestones"`
- Labels bool `json:"labels"`
- Issues bool `json:"issues"`
- PullRequests bool `json:"pull_requests"`
- Releases bool `json:"releases"`
- MirrorInterval string `json:"mirror_interval"`
-}
-
-// Validate validates the fields
-func (f *MigrateRepoForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// ParseRemoteAddr checks if given remote address is valid,
-// and returns composed URL with needed username and password.
-// It also checks if given user has permission when remote address
-// is actually a local path.
-func ParseRemoteAddr(remoteAddr, authUsername, authPassword string, user *models.User) (string, error) {
- remoteAddr = strings.TrimSpace(remoteAddr)
- // Remote address can be HTTP/HTTPS/Git URL or local path.
- if strings.HasPrefix(remoteAddr, "http://") ||
- strings.HasPrefix(remoteAddr, "https://") ||
- strings.HasPrefix(remoteAddr, "git://") {
- u, err := url.Parse(remoteAddr)
- if err != nil {
- return "", models.ErrInvalidCloneAddr{IsURLError: true}
- }
- if len(authUsername)+len(authPassword) > 0 {
- u.User = url.UserPassword(authUsername, authPassword)
- }
- remoteAddr = u.String()
- if u.Scheme == "git" && u.Port() != "" && (strings.Contains(remoteAddr, "%0d") || strings.Contains(remoteAddr, "%0a")) {
- return "", models.ErrInvalidCloneAddr{IsURLError: true}
- }
- } else if !user.CanImportLocal() {
- return "", models.ErrInvalidCloneAddr{IsPermissionDenied: true}
- } else {
- isDir, err := util.IsDir(remoteAddr)
- if err != nil {
- log.Error("Unable to check if %s is a directory: %v", remoteAddr, err)
- return "", err
- }
- if !isDir {
- return "", models.ErrInvalidCloneAddr{IsInvalidPath: true}
- }
- }
-
- return remoteAddr, nil
-}
-
-// RepoSettingForm form for changing repository settings
-type RepoSettingForm struct {
- RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"`
- Description string `binding:"MaxSize(255)"`
- Website string `binding:"ValidUrl;MaxSize(255)"`
- Interval string
- MirrorAddress string
- MirrorUsername string
- MirrorPassword string
- Private bool
- Template bool
- EnablePrune bool
-
- // Advanced settings
- EnableWiki bool
- EnableExternalWiki bool
- ExternalWikiURL string
- EnableIssues bool
- EnableExternalTracker bool
- ExternalTrackerURL string
- TrackerURLFormat string
- TrackerIssueStyle string
- EnableProjects bool
- EnablePulls bool
- PullsIgnoreWhitespace bool
- PullsAllowMerge bool
- PullsAllowRebase bool
- PullsAllowRebaseMerge bool
- PullsAllowSquash bool
- EnableTimetracker bool
- AllowOnlyContributorsToTrackTime bool
- EnableIssueDependencies bool
- IsArchived bool
-
- // Signing Settings
- TrustModel string
-
- // Admin settings
- EnableHealthCheck bool
- EnableCloseIssuesViaCommitInAnyBranch bool
-}
-
-// Validate validates the fields
-func (f *RepoSettingForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// __________ .__
-// \______ \____________ ____ ____ | |__
-// | | _/\_ __ \__ \ / \_/ ___\| | \
-// | | \ | | \// __ \| | \ \___| Y \
-// |______ / |__| (____ /___| /\___ >___| /
-// \/ \/ \/ \/ \/
-
-// ProtectBranchForm form for changing protected branch settings
-type ProtectBranchForm struct {
- Protected bool
- EnablePush string
- WhitelistUsers string
- WhitelistTeams string
- WhitelistDeployKeys bool
- EnableMergeWhitelist bool
- MergeWhitelistUsers string
- MergeWhitelistTeams string
- EnableStatusCheck bool
- StatusCheckContexts []string
- RequiredApprovals int64
- EnableApprovalsWhitelist bool
- ApprovalsWhitelistUsers string
- ApprovalsWhitelistTeams string
- BlockOnRejectedReviews bool
- BlockOnOfficialReviewRequests bool
- BlockOnOutdatedBranch bool
- DismissStaleApprovals bool
- RequireSignedCommits bool
- ProtectedFilePatterns string
-}
-
-// Validate validates the fields
-func (f *ProtectBranchForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// __ __ ___. .__ .__ __
-// / \ / \ ____\_ |__ | |__ | |__ ____ | | __
-// \ \/\/ // __ \| __ \| | \| | \ / _ \| |/ /
-// \ /\ ___/| \_\ \ Y \ Y ( <_> ) <
-// \__/\ / \___ >___ /___| /___| /\____/|__|_ \
-// \/ \/ \/ \/ \/ \/
-
-// WebhookForm form for changing web hook
-type WebhookForm struct {
- Events string
- Create bool
- Delete bool
- Fork bool
- Issues bool
- IssueAssign bool
- IssueLabel bool
- IssueMilestone bool
- IssueComment bool
- Release bool
- Push bool
- PullRequest bool
- PullRequestAssign bool
- PullRequestLabel bool
- PullRequestMilestone bool
- PullRequestComment bool
- PullRequestReview bool
- PullRequestSync bool
- Repository bool
- Active bool
- BranchFilter string `binding:"GlobPattern"`
-}
-
-// PushOnly if the hook will be triggered when push
-func (f WebhookForm) PushOnly() bool {
- return f.Events == "push_only"
-}
-
-// SendEverything if the hook will be triggered any event
-func (f WebhookForm) SendEverything() bool {
- return f.Events == "send_everything"
-}
-
-// ChooseEvents if the hook will be triggered choose events
-func (f WebhookForm) ChooseEvents() bool {
- return f.Events == "choose_events"
-}
-
-// NewWebhookForm form for creating web hook
-type NewWebhookForm struct {
- PayloadURL string `binding:"Required;ValidUrl"`
- HTTPMethod string `binding:"Required;In(POST,GET)"`
- ContentType int `binding:"Required"`
- Secret string
- WebhookForm
-}
-
-// Validate validates the fields
-func (f *NewWebhookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// NewGogshookForm form for creating gogs hook
-type NewGogshookForm struct {
- PayloadURL string `binding:"Required;ValidUrl"`
- ContentType int `binding:"Required"`
- Secret string
- WebhookForm
-}
-
-// Validate validates the fields
-func (f *NewGogshookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// NewSlackHookForm form for creating slack hook
-type NewSlackHookForm struct {
- PayloadURL string `binding:"Required;ValidUrl"`
- Channel string `binding:"Required"`
- Username string
- IconURL string
- Color string
- WebhookForm
-}
-
-// Validate validates the fields
-func (f *NewSlackHookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// HasInvalidChannel validates the channel name is in the right format
-func (f NewSlackHookForm) HasInvalidChannel() bool {
- return !utils.IsValidSlackChannel(f.Channel)
-}
-
-// NewDiscordHookForm form for creating discord hook
-type NewDiscordHookForm struct {
- PayloadURL string `binding:"Required;ValidUrl"`
- Username string
- IconURL string
- WebhookForm
-}
-
-// Validate validates the fields
-func (f *NewDiscordHookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// NewDingtalkHookForm form for creating dingtalk hook
-type NewDingtalkHookForm struct {
- PayloadURL string `binding:"Required;ValidUrl"`
- WebhookForm
-}
-
-// Validate validates the fields
-func (f *NewDingtalkHookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// NewTelegramHookForm form for creating telegram hook
-type NewTelegramHookForm struct {
- BotToken string `binding:"Required"`
- ChatID string `binding:"Required"`
- WebhookForm
-}
-
-// Validate validates the fields
-func (f *NewTelegramHookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// NewMatrixHookForm form for creating Matrix hook
-type NewMatrixHookForm struct {
- HomeserverURL string `binding:"Required;ValidUrl"`
- RoomID string `binding:"Required"`
- AccessToken string `binding:"Required"`
- MessageType int
- WebhookForm
-}
-
-// Validate validates the fields
-func (f *NewMatrixHookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// NewMSTeamsHookForm form for creating MS Teams hook
-type NewMSTeamsHookForm struct {
- PayloadURL string `binding:"Required;ValidUrl"`
- WebhookForm
-}
-
-// Validate validates the fields
-func (f *NewMSTeamsHookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// NewFeishuHookForm form for creating feishu hook
-type NewFeishuHookForm struct {
- PayloadURL string `binding:"Required;ValidUrl"`
- WebhookForm
-}
-
-// Validate validates the fields
-func (f *NewFeishuHookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// .___
-// | | ______ ________ __ ____
-// | |/ ___// ___/ | \_/ __ \
-// | |\___ \ \___ \| | /\ ___/
-// |___/____ >____ >____/ \___ >
-// \/ \/ \/
-
-// CreateIssueForm form for creating issue
-type CreateIssueForm struct {
- Title string `binding:"Required;MaxSize(255)"`
- LabelIDs string `form:"label_ids"`
- AssigneeIDs string `form:"assignee_ids"`
- Ref string `form:"ref"`
- MilestoneID int64
- ProjectID int64
- AssigneeID int64
- Content string
- Files []string
-}
-
-// Validate validates the fields
-func (f *CreateIssueForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// CreateCommentForm form for creating comment
-type CreateCommentForm struct {
- Content string
- Status string `binding:"OmitEmpty;In(reopen,close)"`
- Files []string
-}
-
-// Validate validates the fields
-func (f *CreateCommentForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// ReactionForm form for adding and removing reaction
-type ReactionForm struct {
- Content string `binding:"Required"`
-}
-
-// Validate validates the fields
-func (f *ReactionForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// IssueLockForm form for locking an issue
-type IssueLockForm struct {
- Reason string `binding:"Required"`
-}
-
-// Validate validates the fields
-func (i *IssueLockForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, i, ctx.Locale)
-}
-
-// HasValidReason checks to make sure that the reason submitted in
-// the form matches any of the values in the config
-func (i IssueLockForm) HasValidReason() bool {
- if strings.TrimSpace(i.Reason) == "" {
- return true
- }
-
- for _, v := range setting.Repository.Issue.LockReasons {
- if v == i.Reason {
- return true
- }
- }
-
- return false
-}
-
-// __________ __ __
-// \______ \_______ ____ |__| ____ _____/ |_ ______
-// | ___/\_ __ \/ _ \ | |/ __ \_/ ___\ __\/ ___/
-// | | | | \( <_> ) | \ ___/\ \___| | \___ \
-// |____| |__| \____/\__| |\___ >\___ >__| /____ >
-// \______| \/ \/ \/
-
-// CreateProjectForm form for creating a project
-type CreateProjectForm struct {
- Title string `binding:"Required;MaxSize(100)"`
- Content string
- BoardType models.ProjectBoardType
-}
-
-// UserCreateProjectForm is a from for creating an individual or organization
-// form.
-type UserCreateProjectForm struct {
- Title string `binding:"Required;MaxSize(100)"`
- Content string
- BoardType models.ProjectBoardType
- UID int64 `binding:"Required"`
-}
-
-// EditProjectBoardTitleForm is a form for editing the title of a project's
-// board
-type EditProjectBoardTitleForm struct {
- Title string `binding:"Required;MaxSize(100)"`
-}
-
-// _____ .__.__ __
-// / \ |__| | ____ _______/ |_ ____ ____ ____
-// / \ / \| | | _/ __ \ / ___/\ __\/ _ \ / \_/ __ \
-// / Y \ | |_\ ___/ \___ \ | | ( <_> ) | \ ___/
-// \____|__ /__|____/\___ >____ > |__| \____/|___| /\___ >
-// \/ \/ \/ \/ \/
-
-// CreateMilestoneForm form for creating milestone
-type CreateMilestoneForm struct {
- Title string `binding:"Required;MaxSize(50)"`
- Content string
- Deadline string
-}
-
-// Validate validates the fields
-func (f *CreateMilestoneForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// .____ ___. .__
-// | | _____ \_ |__ ____ | |
-// | | \__ \ | __ \_/ __ \| |
-// | |___ / __ \| \_\ \ ___/| |__
-// |_______ (____ /___ /\___ >____/
-// \/ \/ \/ \/
-
-// CreateLabelForm form for creating label
-type CreateLabelForm struct {
- ID int64
- Title string `binding:"Required;MaxSize(50)" locale:"repo.issues.label_title"`
- Description string `binding:"MaxSize(200)" locale:"repo.issues.label_description"`
- Color string `binding:"Required;Size(7)" locale:"repo.issues.label_color"`
-}
-
-// Validate validates the fields
-func (f *CreateLabelForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// InitializeLabelsForm form for initializing labels
-type InitializeLabelsForm struct {
- TemplateName string `binding:"Required"`
-}
-
-// Validate validates the fields
-func (f *InitializeLabelsForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// __________ .__ .__ __________ __
-// \______ \__ __| | | | \______ \ ____ ________ __ ____ _______/ |_
-// | ___/ | \ | | | | _// __ \/ ____/ | \_/ __ \ / ___/\ __\
-// | | | | / |_| |__ | | \ ___< <_| | | /\ ___/ \___ \ | |
-// |____| |____/|____/____/ |____|_ /\___ >__ |____/ \___ >____ > |__|
-// \/ \/ |__| \/ \/
-
-// MergePullRequestForm form for merging Pull Request
-// swagger:model MergePullRequestOption
-type MergePullRequestForm struct {
- // required: true
- // enum: merge,rebase,rebase-merge,squash
- Do string `binding:"Required;In(merge,rebase,rebase-merge,squash)"`
- MergeTitleField string
- MergeMessageField string
- ForceMerge *bool `json:"force_merge,omitempty"`
-}
-
-// Validate validates the fields
-func (f *MergePullRequestForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// CodeCommentForm form for adding code comments for PRs
-type CodeCommentForm struct {
- Origin string `binding:"Required;In(timeline,diff)"`
- Content string `binding:"Required"`
- Side string `binding:"Required;In(previous,proposed)"`
- Line int64
- TreePath string `form:"path" binding:"Required"`
- IsReview bool `form:"is_review"`
- Reply int64 `form:"reply"`
- LatestCommitID string
-}
-
-// Validate validates the fields
-func (f *CodeCommentForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// SubmitReviewForm for submitting a finished code review
-type SubmitReviewForm struct {
- Content string
- Type string `binding:"Required;In(approve,comment,reject)"`
- CommitID string
-}
-
-// Validate validates the fields
-func (f *SubmitReviewForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// ReviewType will return the corresponding reviewtype for type
-func (f SubmitReviewForm) ReviewType() models.ReviewType {
- switch f.Type {
- case "approve":
- return models.ReviewTypeApprove
- case "comment":
- return models.ReviewTypeComment
- case "reject":
- return models.ReviewTypeReject
- default:
- return models.ReviewTypeUnknown
- }
-}
-
-// HasEmptyContent checks if the content of the review form is empty.
-func (f SubmitReviewForm) HasEmptyContent() bool {
- reviewType := f.ReviewType()
-
- return (reviewType == models.ReviewTypeComment || reviewType == models.ReviewTypeReject) &&
- len(strings.TrimSpace(f.Content)) == 0
-}
-
-// __________ .__
-// \______ \ ____ | | ____ _____ ______ ____
-// | _// __ \| | _/ __ \\__ \ / ___// __ \
-// | | \ ___/| |_\ ___/ / __ \_\___ \\ ___/
-// |____|_ /\___ >____/\___ >____ /____ >\___ >
-// \/ \/ \/ \/ \/ \/
-
-// NewReleaseForm form for creating release
-type NewReleaseForm struct {
- TagName string `binding:"Required;GitRefName;MaxSize(255)"`
- Target string `form:"tag_target" binding:"Required;MaxSize(255)"`
- Title string `binding:"Required;MaxSize(255)"`
- Content string
- Draft string
- Prerelease bool
- Files []string
-}
-
-// Validate validates the fields
-func (f *NewReleaseForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// EditReleaseForm form for changing release
-type EditReleaseForm struct {
- Title string `form:"title" binding:"Required;MaxSize(255)"`
- Content string `form:"content"`
- Draft string `form:"draft"`
- Prerelease bool `form:"prerelease"`
- Files []string
-}
-
-// Validate validates the fields
-func (f *EditReleaseForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// __ __.__ __ .__
-// / \ / \__| | _|__|
-// \ \/\/ / | |/ / |
-// \ /| | <| |
-// \__/\ / |__|__|_ \__|
-// \/ \/
-
-// NewWikiForm form for creating wiki
-type NewWikiForm struct {
- Title string `binding:"Required"`
- Content string `binding:"Required"`
- Message string
-}
-
-// Validate validates the fields
-// FIXME: use code generation to generate this method.
-func (f *NewWikiForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// ___________ .___.__ __
-// \_ _____/ __| _/|__|/ |_
-// | __)_ / __ | | \ __\
-// | \/ /_/ | | || |
-// /_______ /\____ | |__||__|
-// \/ \/
-
-// EditRepoFileForm form for changing repository file
-type EditRepoFileForm struct {
- TreePath string `binding:"Required;MaxSize(500)"`
- Content string
- CommitSummary string `binding:"MaxSize(100)"`
- CommitMessage string
- CommitChoice string `binding:"Required;MaxSize(50)"`
- NewBranchName string `binding:"GitRefName;MaxSize(100)"`
- LastCommit string
-}
-
-// Validate validates the fields
-func (f *EditRepoFileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// EditPreviewDiffForm form for changing preview diff
-type EditPreviewDiffForm struct {
- Content string
-}
-
-// Validate validates the fields
-func (f *EditPreviewDiffForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// ____ ___ .__ .___
-// | | \______ | | _________ __| _/
-// | | /\____ \| | / _ \__ \ / __ |
-// | | / | |_> > |_( <_> ) __ \_/ /_/ |
-// |______/ | __/|____/\____(____ /\____ |
-// |__| \/ \/
-//
-
-// UploadRepoFileForm form for uploading repository file
-type UploadRepoFileForm struct {
- TreePath string `binding:"MaxSize(500)"`
- CommitSummary string `binding:"MaxSize(100)"`
- CommitMessage string
- CommitChoice string `binding:"Required;MaxSize(50)"`
- NewBranchName string `binding:"GitRefName;MaxSize(100)"`
- Files []string
-}
-
-// Validate validates the fields
-func (f *UploadRepoFileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// RemoveUploadFileForm form for removing uploaded file
-type RemoveUploadFileForm struct {
- File string `binding:"Required;MaxSize(50)"`
-}
-
-// Validate validates the fields
-func (f *RemoveUploadFileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// ________ .__ __
-// \______ \ ____ | | _____/ |_ ____
-// | | \_/ __ \| | _/ __ \ __\/ __ \
-// | ` \ ___/| |_\ ___/| | \ ___/
-// /_______ /\___ >____/\___ >__| \___ >
-// \/ \/ \/ \/
-
-// DeleteRepoFileForm form for deleting repository file
-type DeleteRepoFileForm struct {
- CommitSummary string `binding:"MaxSize(100)"`
- CommitMessage string
- CommitChoice string `binding:"Required;MaxSize(50)"`
- NewBranchName string `binding:"GitRefName;MaxSize(100)"`
- LastCommit string
-}
-
-// Validate validates the fields
-func (f *DeleteRepoFileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// ___________.__ ___________ __
-// \__ ___/|__| _____ ____ \__ ___/___________ ____ | | __ ___________
-// | | | |/ \_/ __ \ | | \_ __ \__ \ _/ ___\| |/ // __ \_ __ \
-// | | | | Y Y \ ___/ | | | | \// __ \\ \___| <\ ___/| | \/
-// |____| |__|__|_| /\___ > |____| |__| (____ /\___ >__|_ \\___ >__|
-// \/ \/ \/ \/ \/ \/
-
-// AddTimeManuallyForm form that adds spent time manually.
-type AddTimeManuallyForm struct {
- Hours int `binding:"Range(0,1000)"`
- Minutes int `binding:"Range(0,1000)"`
-}
-
-// Validate validates the fields
-func (f *AddTimeManuallyForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// SaveTopicForm form for save topics for repository
-type SaveTopicForm struct {
- Topics []string `binding:"topics;Required;"`
-}
-
-// DeadlineForm hold the validation rules for deadlines
-type DeadlineForm struct {
- DateString string `form:"date" binding:"Required;Size(10)"`
-}
-
-// Validate validates the fields
-func (f *DeadlineForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
+++ /dev/null
-// Copyright 2018 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package auth
-
-import (
- "testing"
-
- "code.gitea.io/gitea/modules/setting"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestSubmitReviewForm_IsEmpty(t *testing.T) {
-
- cases := []struct {
- form SubmitReviewForm
- expected bool
- }{
- // Approved PR with a comment shouldn't count as empty
- {SubmitReviewForm{Type: "approve", Content: "Awesome"}, false},
-
- // Approved PR without a comment shouldn't count as empty
- {SubmitReviewForm{Type: "approve", Content: ""}, false},
-
- // Rejected PR without a comment should count as empty
- {SubmitReviewForm{Type: "reject", Content: ""}, true},
-
- // Rejected PR with a comment shouldn't count as empty
- {SubmitReviewForm{Type: "reject", Content: "Awesome"}, false},
-
- // Comment review on a PR with a comment shouldn't count as empty
- {SubmitReviewForm{Type: "comment", Content: "Awesome"}, false},
-
- // Comment review on a PR without a comment should count as empty
- {SubmitReviewForm{Type: "comment", Content: ""}, true},
- }
-
- for _, v := range cases {
- assert.Equal(t, v.expected, v.form.HasEmptyContent())
- }
-}
-
-func TestIssueLock_HasValidReason(t *testing.T) {
-
- // Init settings
- _ = setting.Repository
-
- cases := []struct {
- form IssueLockForm
- expected bool
- }{
- {IssueLockForm{""}, true}, // an empty reason is accepted
- {IssueLockForm{"Off-topic"}, true},
- {IssueLockForm{"Too heated"}, true},
- {IssueLockForm{"Spam"}, true},
- {IssueLockForm{"Resolved"}, true},
-
- {IssueLockForm{"ZZZZ"}, false},
- {IssueLockForm{"I want to lock this issue"}, false},
- }
-
- for _, v := range cases {
- assert.Equal(t, v.expected, v.form.HasValidReason())
- }
-}
"net/http"
"code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/modules/middlewares"
+ "code.gitea.io/gitea/modules/session"
)
// DataStore represents a data store
-type DataStore interface {
- GetData() map[string]interface{}
-}
+type DataStore middlewares.DataStore
// SessionStore represents a session store
-type SessionStore interface {
- Get(interface{}) interface{}
- Set(interface{}, interface{}) error
- Delete(interface{}) error
-}
+type SessionStore session.Store
// SingleSignOn represents a SSO authentication method (plugin) for HTTP requests.
type SingleSignOn interface {
// userIDFromToken returns the user id corresponding to the OAuth token.
func (o *OAuth2) userIDFromToken(req *http.Request, store DataStore) int64 {
+ _ = req.ParseForm()
+
// Check access token.
tokenSHA := req.Form.Get("token")
if len(tokenSHA) == 0 {
+++ /dev/null
-// Copyright 2014 The Gogs Authors. All rights reserved.
-// Copyright 2018 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package auth
-
-import (
- "mime/multipart"
- "strings"
-
- "code.gitea.io/gitea/modules/setting"
-
- "gitea.com/macaron/binding"
- "gitea.com/macaron/macaron"
-)
-
-// InstallForm form for installation page
-type InstallForm struct {
- DbType string `binding:"Required"`
- DbHost string
- DbUser string
- DbPasswd string
- DbName string
- SSLMode string
- Charset string `binding:"Required;In(utf8,utf8mb4)"`
- DbPath string
- DbSchema string
-
- AppName string `binding:"Required" locale:"install.app_name"`
- RepoRootPath string `binding:"Required"`
- LFSRootPath string
- RunUser string `binding:"Required"`
- Domain string `binding:"Required"`
- SSHPort int
- HTTPPort string `binding:"Required"`
- AppURL string `binding:"Required"`
- LogRootPath string `binding:"Required"`
-
- SMTPHost string
- SMTPFrom string
- SMTPUser string `binding:"OmitEmpty;MaxSize(254)" locale:"install.mailer_user"`
- SMTPPasswd string
- RegisterConfirm bool
- MailNotify bool
-
- OfflineMode bool
- DisableGravatar bool
- EnableFederatedAvatar bool
- EnableOpenIDSignIn bool
- EnableOpenIDSignUp bool
- DisableRegistration bool
- AllowOnlyExternalRegistration bool
- EnableCaptcha bool
- RequireSignInView bool
- DefaultKeepEmailPrivate bool
- DefaultAllowCreateOrganization bool
- DefaultEnableTimetracking bool
- NoReplyAddress string
-
- AdminName string `binding:"OmitEmpty;AlphaDashDot;MaxSize(30)" locale:"install.admin_name"`
- AdminPasswd string `binding:"OmitEmpty;MaxSize(255)" locale:"install.admin_password"`
- AdminConfirmPasswd string
- AdminEmail string `binding:"OmitEmpty;MinSize(3);MaxSize(254);Include(@)" locale:"install.admin_email"`
-}
-
-// Validate validates the fields
-func (f *InstallForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// _____ ____ _________________ ___
-// / _ \ | | \__ ___/ | \
-// / /_\ \| | / | | / ~ \
-// / | \ | / | | \ Y /
-// \____|__ /______/ |____| \___|_ /
-// \/ \/
-
-// RegisterForm form for registering
-type RegisterForm struct {
- UserName string `binding:"Required;AlphaDashDot;MaxSize(40)"`
- Email string `binding:"Required;Email;MaxSize(254)"`
- Password string `binding:"MaxSize(255)"`
- Retype string
- GRecaptchaResponse string `form:"g-recaptcha-response"`
- HcaptchaResponse string `form:"h-captcha-response"`
-}
-
-// Validate validates the fields
-func (f *RegisterForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// IsEmailDomainWhitelisted validates that the email address
-// provided by the user matches what has been configured .
-// If the domain whitelist from the config is empty, it marks the
-// email as whitelisted
-func (f RegisterForm) IsEmailDomainWhitelisted() bool {
- if len(setting.Service.EmailDomainWhitelist) == 0 {
- return true
- }
-
- n := strings.LastIndex(f.Email, "@")
- if n <= 0 {
- return false
- }
-
- domain := strings.ToLower(f.Email[n+1:])
-
- for _, v := range setting.Service.EmailDomainWhitelist {
- if strings.ToLower(v) == domain {
- return true
- }
- }
-
- return false
-}
-
-// MustChangePasswordForm form for updating your password after account creation
-// by an admin
-type MustChangePasswordForm struct {
- Password string `binding:"Required;MaxSize(255)"`
- Retype string
-}
-
-// Validate validates the fields
-func (f *MustChangePasswordForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// SignInForm form for signing in with user/password
-type SignInForm struct {
- UserName string `binding:"Required;MaxSize(254)"`
- // TODO remove required from password for SecondFactorAuthentication
- Password string `binding:"Required;MaxSize(255)"`
- Remember bool
-}
-
-// Validate validates the fields
-func (f *SignInForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// AuthorizationForm form for authorizing oauth2 clients
-type AuthorizationForm struct {
- ResponseType string `binding:"Required;In(code)"`
- ClientID string `binding:"Required"`
- RedirectURI string
- State string
- Scope string
- Nonce string
-
- // PKCE support
- CodeChallengeMethod string // S256, plain
- CodeChallenge string
-}
-
-// Validate validates the fields
-func (f *AuthorizationForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// GrantApplicationForm form for authorizing oauth2 clients
-type GrantApplicationForm struct {
- ClientID string `binding:"Required"`
- RedirectURI string
- State string
- Scope string
- Nonce string
-}
-
-// Validate validates the fields
-func (f *GrantApplicationForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// AccessTokenForm for issuing access tokens from authorization codes or refresh tokens
-type AccessTokenForm struct {
- GrantType string `json:"grant_type"`
- ClientID string `json:"client_id"`
- ClientSecret string `json:"client_secret"`
- RedirectURI string `json:"redirect_uri"`
- Code string `json:"code"`
- RefreshToken string `json:"refresh_token"`
-
- // PKCE support
- CodeVerifier string `json:"code_verifier"`
-}
-
-// Validate validates the fields
-func (f *AccessTokenForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// __________________________________________.___ _______ ________ _________
-// / _____/\_ _____/\__ ___/\__ ___/| |\ \ / _____/ / _____/
-// \_____ \ | __)_ | | | | | |/ | \/ \ ___ \_____ \
-// / \ | \ | | | | | / | \ \_\ \/ \
-// /_______ //_______ / |____| |____| |___\____|__ /\______ /_______ /
-// \/ \/ \/ \/ \/
-
-// UpdateProfileForm form for updating profile
-type UpdateProfileForm struct {
- Name string `binding:"AlphaDashDot;MaxSize(40)"`
- FullName string `binding:"MaxSize(100)"`
- KeepEmailPrivate bool
- Website string `binding:"ValidUrl;MaxSize(255)"`
- Location string `binding:"MaxSize(50)"`
- Language string
- Description string `binding:"MaxSize(255)"`
- KeepActivityPrivate bool
-}
-
-// Validate validates the fields
-func (f *UpdateProfileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// Avatar types
-const (
- AvatarLocal string = "local"
- AvatarByMail string = "bymail"
-)
-
-// AvatarForm form for changing avatar
-type AvatarForm struct {
- Source string
- Avatar *multipart.FileHeader
- Gravatar string `binding:"OmitEmpty;Email;MaxSize(254)"`
- Federavatar bool
-}
-
-// Validate validates the fields
-func (f *AvatarForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// AddEmailForm form for adding new email
-type AddEmailForm struct {
- Email string `binding:"Required;Email;MaxSize(254)"`
-}
-
-// Validate validates the fields
-func (f *AddEmailForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// UpdateThemeForm form for updating a users' theme
-type UpdateThemeForm struct {
- Theme string `binding:"Required;MaxSize(30)"`
-}
-
-// Validate validates the field
-func (f *UpdateThemeForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// IsThemeExists checks if the theme is a theme available in the config.
-func (f UpdateThemeForm) IsThemeExists() bool {
- var exists bool
-
- for _, v := range setting.UI.Themes {
- if strings.EqualFold(v, f.Theme) {
- exists = true
- break
- }
- }
-
- return exists
-}
-
-// ChangePasswordForm form for changing password
-type ChangePasswordForm struct {
- OldPassword string `form:"old_password" binding:"MaxSize(255)"`
- Password string `form:"password" binding:"Required;MaxSize(255)"`
- Retype string `form:"retype"`
-}
-
-// Validate validates the fields
-func (f *ChangePasswordForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// AddOpenIDForm is for changing openid uri
-type AddOpenIDForm struct {
- Openid string `binding:"Required;MaxSize(256)"`
-}
-
-// Validate validates the fields
-func (f *AddOpenIDForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// AddKeyForm form for adding SSH/GPG key
-type AddKeyForm struct {
- Type string `binding:"OmitEmpty"`
- Title string `binding:"Required;MaxSize(50)"`
- Content string `binding:"Required"`
- IsWritable bool
-}
-
-// Validate validates the fields
-func (f *AddKeyForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// NewAccessTokenForm form for creating access token
-type NewAccessTokenForm struct {
- Name string `binding:"Required;MaxSize(255)"`
-}
-
-// Validate validates the fields
-func (f *NewAccessTokenForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// EditOAuth2ApplicationForm form for editing oauth2 applications
-type EditOAuth2ApplicationForm struct {
- Name string `binding:"Required;MaxSize(255)" form:"application_name"`
- RedirectURI string `binding:"Required" form:"redirect_uri"`
-}
-
-// Validate validates the fields
-func (f *EditOAuth2ApplicationForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// TwoFactorAuthForm for logging in with 2FA token.
-type TwoFactorAuthForm struct {
- Passcode string `binding:"Required"`
-}
-
-// Validate validates the fields
-func (f *TwoFactorAuthForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// TwoFactorScratchAuthForm for logging in with 2FA scratch token.
-type TwoFactorScratchAuthForm struct {
- Token string `binding:"Required"`
-}
-
-// Validate validates the fields
-func (f *TwoFactorScratchAuthForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// U2FRegistrationForm for reserving an U2F name
-type U2FRegistrationForm struct {
- Name string `binding:"Required"`
-}
-
-// Validate validates the fields
-func (f *U2FRegistrationForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// U2FDeleteForm for deleting U2F keys
-type U2FDeleteForm struct {
- ID int64 `binding:"Required"`
-}
-
-// Validate validates the fields
-func (f *U2FDeleteForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
+++ /dev/null
-// Copyright 2017 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package auth
-
-import (
- "gitea.com/macaron/binding"
- "gitea.com/macaron/macaron"
-)
-
-// SignInOpenIDForm form for signing in with OpenID
-type SignInOpenIDForm struct {
- Openid string `binding:"Required;MaxSize(256)"`
- Remember bool
-}
-
-// Validate validates the fields
-func (f *SignInOpenIDForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// SignUpOpenIDForm form for signin up with OpenID
-type SignUpOpenIDForm struct {
- UserName string `binding:"Required;AlphaDashDot;MaxSize(40)"`
- Email string `binding:"Required;Email;MaxSize(254)"`
- GRecaptchaResponse string `form:"g-recaptcha-response"`
- HcaptchaResponse string `form:"h-captcha-response"`
-}
-
-// Validate validates the fields
-func (f *SignUpOpenIDForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
-
-// ConnectOpenIDForm form for connecting an existing account to an OpenID URI
-type ConnectOpenIDForm struct {
- UserName string `binding:"Required;MaxSize(254)"`
- Password string `binding:"Required;MaxSize(255)"`
-}
-
-// Validate validates the fields
-func (f *ConnectOpenIDForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
- return validate(errs, ctx.Data, f, ctx.Locale)
-}
+++ /dev/null
-// Copyright 2018 The Gogs Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package auth
-
-import (
- "testing"
-
- "code.gitea.io/gitea/modules/setting"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestRegisterForm_IsDomainWhiteList_Empty(t *testing.T) {
- _ = setting.Service
-
- setting.Service.EmailDomainWhitelist = []string{}
-
- form := RegisterForm{}
-
- assert.True(t, form.IsEmailDomainWhitelisted())
-}
-
-func TestRegisterForm_IsDomainWhiteList_InvalidEmail(t *testing.T) {
- _ = setting.Service
-
- setting.Service.EmailDomainWhitelist = []string{"gitea.io"}
-
- tt := []struct {
- email string
- }{
- {"securitygieqqq"},
- {"hdudhdd"},
- }
-
- for _, v := range tt {
- form := RegisterForm{Email: v.email}
-
- assert.False(t, form.IsEmailDomainWhitelisted())
- }
-}
-
-func TestRegisterForm_IsDomainWhiteList_ValidEmail(t *testing.T) {
- _ = setting.Service
-
- setting.Service.EmailDomainWhitelist = []string{"gitea.io"}
-
- tt := []struct {
- email string
- valid bool
- }{
- {"security@gitea.io", true},
- {"security@gITea.io", true},
- {"hdudhdd", false},
- {"seee@example.com", false},
- }
-
- for _, v := range tt {
- form := RegisterForm{Email: v.email}
-
- assert.Equal(t, v.valid, form.IsEmailDomainWhitelisted())
- }
-}
"code.gitea.io/gitea/modules/setting"
- mc "gitea.com/macaron/cache"
+ mc "gitea.com/go-chi/cache"
- _ "gitea.com/macaron/cache/memcache" // memcache plugin for cache
+ _ "gitea.com/go-chi/cache/memcache" // memcache plugin for cache
)
var (
)
func newCache(cacheConfig setting.Cache) (mc.Cache, error) {
- return mc.NewCacher(cacheConfig.Adapter, mc.Options{
+ return mc.NewCacher(mc.Options{
Adapter: cacheConfig.Adapter,
AdapterConfig: cacheConfig.Conn,
Interval: cacheConfig.Interval,
"code.gitea.io/gitea/modules/nosql"
- "gitea.com/macaron/cache"
+ "gitea.com/go-chi/cache"
"github.com/go-redis/redis/v7"
"github.com/unknwon/com"
)
package context
import (
+ "context"
"fmt"
+ "html"
"net/http"
"net/url"
"strings"
"code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/modules/auth/sso"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/middlewares"
"code.gitea.io/gitea/modules/setting"
- "gitea.com/macaron/csrf"
- "gitea.com/macaron/macaron"
+ "gitea.com/go-chi/session"
)
// APIContext is a specific macaron context for API service
if status == http.StatusInternalServerError {
log.ErrorWithSkip(1, "%s: %s", title, message)
- if macaron.Env == macaron.PROD && !(ctx.User != nil && ctx.User.IsAdmin) {
+ if setting.IsProd() && !(ctx.User != nil && ctx.User.IsAdmin) {
message = ""
}
}
log.ErrorWithSkip(1, "InternalServerError: %v", err)
var message string
- if macaron.Env != macaron.PROD || (ctx.User != nil && ctx.User.IsAdmin) {
+ if !setting.IsProd() || (ctx.User != nil && ctx.User.IsAdmin) {
message = err.Error()
}
})
}
+var (
+ apiContextKey interface{} = "default_api_context"
+)
+
+// WithAPIContext set up api context in request
+func WithAPIContext(req *http.Request, ctx *APIContext) *http.Request {
+ return req.WithContext(context.WithValue(req.Context(), apiContextKey, ctx))
+}
+
+// GetAPIContext returns a context for API routes
+func GetAPIContext(req *http.Request) *APIContext {
+ return req.Context().Value(apiContextKey).(*APIContext)
+}
+
func genAPILinks(curURL *url.URL, total, pageSize, curPage int) []string {
page := NewPagination(total, pageSize, curPage, 0)
paginater := page.Paginater
headerToken := ctx.Req.Header.Get(ctx.csrf.GetHeaderName())
formValueToken := ctx.Req.FormValue(ctx.csrf.GetFormName())
if len(headerToken) > 0 || len(formValueToken) > 0 {
- csrf.Validate(ctx.Context.Context, ctx.csrf)
+ Validate(ctx.Context, ctx.csrf)
} else {
ctx.Context.Error(401, "Missing CSRF token.")
}
}
// APIContexter returns apicontext as macaron middleware
-func APIContexter() macaron.Handler {
- return func(c *Context) {
- ctx := &APIContext{
- Context: c,
- }
- c.Map(ctx)
+func APIContexter() func(http.Handler) http.Handler {
+ var csrfOpts = getCsrfOpts()
+
+ return func(next http.Handler) http.Handler {
+
+ return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ var locale = middlewares.Locale(w, req)
+ var ctx = APIContext{
+ Context: &Context{
+ Resp: NewResponse(w),
+ Data: map[string]interface{}{},
+ Locale: locale,
+ Session: session.GetSession(req),
+ Repo: &Repository{
+ PullRequest: &PullRequest{},
+ },
+ Org: &Organization{},
+ },
+ Org: &APIOrganization{},
+ }
+
+ ctx.Req = WithAPIContext(WithContext(req, ctx.Context), &ctx)
+ ctx.csrf = Csrfer(csrfOpts, ctx.Context)
+
+ // If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid.
+ if ctx.Req.Method == "POST" && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") {
+ if err := ctx.Req.ParseMultipartForm(setting.Attachment.MaxSize << 20); err != nil && !strings.Contains(err.Error(), "EOF") { // 32MB max size
+ ctx.InternalServerError(err)
+ return
+ }
+ }
+
+ // Get user from session if logged in.
+ ctx.User, ctx.IsBasicAuth = sso.SignedInUser(ctx.Req, ctx.Resp, &ctx, ctx.Session)
+ if ctx.User != nil {
+ ctx.IsSigned = true
+ ctx.Data["IsSigned"] = ctx.IsSigned
+ ctx.Data["SignedUser"] = ctx.User
+ ctx.Data["SignedUserID"] = ctx.User.ID
+ ctx.Data["SignedUserName"] = ctx.User.Name
+ ctx.Data["IsAdmin"] = ctx.User.IsAdmin
+ } else {
+ ctx.Data["SignedUserID"] = int64(0)
+ ctx.Data["SignedUserName"] = ""
+ }
+
+ ctx.Resp.Header().Set(`X-Frame-Options`, `SAMEORIGIN`)
+
+ ctx.Data["CsrfToken"] = html.EscapeString(ctx.csrf.GetToken())
+
+ next.ServeHTTP(ctx.Resp, ctx.Req)
+ })
}
}
// ReferencesGitRepo injects the GitRepo into the Context
-func ReferencesGitRepo(allowEmpty bool) macaron.Handler {
- return func(ctx *APIContext) {
- // Empty repository does not have reference information.
- if !allowEmpty && ctx.Repo.Repository.IsEmpty {
- return
- }
-
- // For API calls.
- if ctx.Repo.GitRepo == nil {
- repoPath := models.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
- gitRepo, err := git.OpenRepository(repoPath)
- if err != nil {
- ctx.Error(500, "RepoRef Invalid repo "+repoPath, err)
+func ReferencesGitRepo(allowEmpty bool) func(http.Handler) http.Handler {
+ return func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ ctx := GetAPIContext(req)
+ // Empty repository does not have reference information.
+ if !allowEmpty && ctx.Repo.Repository.IsEmpty {
return
}
- ctx.Repo.GitRepo = gitRepo
- // We opened it, we should close it
- defer func() {
- // If it's been set to nil then assume someone else has closed it.
- if ctx.Repo.GitRepo != nil {
- ctx.Repo.GitRepo.Close()
+
+ // For API calls.
+ if ctx.Repo.GitRepo == nil {
+ repoPath := models.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
+ gitRepo, err := git.OpenRepository(repoPath)
+ if err != nil {
+ ctx.Error(500, "RepoRef Invalid repo "+repoPath, err)
+ return
}
- }()
- }
+ ctx.Repo.GitRepo = gitRepo
+ // We opened it, we should close it
+ defer func() {
+ // If it's been set to nil then assume someone else has closed it.
+ if ctx.Repo.GitRepo != nil {
+ ctx.Repo.GitRepo.Close()
+ }
+ }()
+ }
- ctx.Next()
+ next.ServeHTTP(w, req)
+ })
}
}
}
// RepoRefForAPI handles repository reference names when the ref name is not explicitly given
-func RepoRefForAPI() macaron.Handler {
- return func(ctx *APIContext) {
+func RepoRefForAPI(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ ctx := GetAPIContext(req)
// Empty repository does not have reference information.
if ctx.Repo.Repository.IsEmpty {
return
return
}
- ctx.Next()
- }
+ next.ServeHTTP(w, req)
+ })
}
import (
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
-
- "gitea.com/macaron/csrf"
- "gitea.com/macaron/macaron"
)
// ToggleOptions contains required or check options
}
// Toggle returns toggle options as middleware
-func Toggle(options *ToggleOptions) macaron.Handler {
+func Toggle(options *ToggleOptions) func(ctx *Context) {
return func(ctx *Context) {
- isAPIPath := auth.IsAPIPath(ctx.Req.URL.Path)
-
// Check prohibit login users.
if ctx.IsSigned {
if !ctx.User.IsActive && setting.Service.RegisterEmailConfirm {
ctx.Data["Title"] = ctx.Tr("auth.active_your_account")
- if isAPIPath {
- ctx.JSON(403, map[string]string{
- "message": "This account is not activated.",
- })
- return
- }
ctx.HTML(200, "user/auth/activate")
return
- } else if !ctx.User.IsActive || ctx.User.ProhibitLogin {
+ }
+ if !ctx.User.IsActive || ctx.User.ProhibitLogin {
log.Info("Failed authentication attempt for %s from %s", ctx.User.Name, ctx.RemoteAddr())
ctx.Data["Title"] = ctx.Tr("auth.prohibit_login")
- if isAPIPath {
- ctx.JSON(403, map[string]string{
- "message": "This account is prohibited from signing in, please contact your site administrator.",
- })
- return
- }
ctx.HTML(200, "user/auth/prohibit_login")
return
}
if ctx.User.MustChangePassword {
- if isAPIPath {
- ctx.JSON(403, map[string]string{
- "message": "You must change your password. Change it at: " + setting.AppURL + "/user/change_password",
- })
- return
- }
if ctx.Req.URL.Path != "/user/settings/change_password" {
ctx.Data["Title"] = ctx.Tr("auth.must_change_password")
ctx.Data["ChangePasscodeLink"] = setting.AppSubURL + "/user/change_password"
return
}
- if !options.SignOutRequired && !options.DisableCSRF && ctx.Req.Method == "POST" && !auth.IsAPIPath(ctx.Req.URL.Path) {
- csrf.Validate(ctx.Context, ctx.csrf)
+ if !options.SignOutRequired && !options.DisableCSRF && ctx.Req.Method == "POST" {
+ Validate(ctx, ctx.csrf)
if ctx.Written() {
return
}
if options.SignInRequired {
if !ctx.IsSigned {
- // Restrict API calls with error message.
- if isAPIPath {
- ctx.JSON(403, map[string]string{
- "message": "Only signed in user is allowed to call APIs.",
- })
- return
- }
if ctx.Req.URL.Path != "/user/events" {
ctx.SetCookie("redirect_to", setting.AppSubURL+ctx.Req.URL.RequestURI(), 0, setting.AppSubURL)
}
ctx.HTML(200, "user/auth/activate")
return
}
- if ctx.IsSigned && isAPIPath && ctx.IsBasicAuth {
+ }
+
+ // Redirect to log in page if auto-signin info is provided and has not signed in.
+ if !options.SignOutRequired && !ctx.IsSigned &&
+ len(ctx.GetCookie(setting.CookieUserName)) > 0 {
+ if ctx.Req.URL.Path != "/user/events" {
+ ctx.SetCookie("redirect_to", setting.AppSubURL+ctx.Req.URL.RequestURI(), 0, setting.AppSubURL)
+ }
+ ctx.Redirect(setting.AppSubURL + "/user/login")
+ return
+ }
+
+ if options.AdminRequired {
+ if !ctx.User.IsAdmin {
+ ctx.Error(403)
+ return
+ }
+ ctx.Data["PageIsAdmin"] = true
+ }
+ }
+}
+
+// ToggleAPI returns toggle options as middleware
+func ToggleAPI(options *ToggleOptions) func(ctx *APIContext) {
+ return func(ctx *APIContext) {
+ // Check prohibit login users.
+ if ctx.IsSigned {
+ if !ctx.User.IsActive && setting.Service.RegisterEmailConfirm {
+ ctx.Data["Title"] = ctx.Tr("auth.active_your_account")
+ ctx.JSON(403, map[string]string{
+ "message": "This account is not activated.",
+ })
+ return
+ }
+ if !ctx.User.IsActive || ctx.User.ProhibitLogin {
+ log.Info("Failed authentication attempt for %s from %s", ctx.User.Name, ctx.RemoteAddr())
+ ctx.Data["Title"] = ctx.Tr("auth.prohibit_login")
+ ctx.JSON(403, map[string]string{
+ "message": "This account is prohibited from signing in, please contact your site administrator.",
+ })
+ return
+ }
+
+ if ctx.User.MustChangePassword {
+ ctx.JSON(403, map[string]string{
+ "message": "You must change your password. Change it at: " + setting.AppURL + "/user/change_password",
+ })
+ return
+ }
+ }
+
+ // Redirect to dashboard if user tries to visit any non-login page.
+ if options.SignOutRequired && ctx.IsSigned && ctx.Req.URL.RequestURI() != "/" {
+ ctx.Redirect(setting.AppSubURL + "/")
+ return
+ }
+
+ if options.SignInRequired {
+ if !ctx.IsSigned {
+ // Restrict API calls with error message.
+ ctx.JSON(403, map[string]string{
+ "message": "Only signed in user is allowed to call APIs.",
+ })
+ return
+ } else if !ctx.User.IsActive && setting.Service.RegisterEmailConfirm {
+ ctx.Data["Title"] = ctx.Tr("auth.active_your_account")
+ ctx.HTML(200, "user/auth/activate")
+ return
+ }
+ if ctx.IsSigned && ctx.IsBasicAuth {
twofa, err := models.GetTwoFactorByUID(ctx.User.ID)
if err != nil {
if models.IsErrTwoFactorNotEnrolled(err) {
return // No 2FA enrollment for this user
}
- ctx.Error(500)
+ ctx.InternalServerError(err)
return
}
otpHeader := ctx.Req.Header.Get("X-Gitea-OTP")
ok, err := twofa.ValidateTOTP(otpHeader)
if err != nil {
- ctx.Error(500)
+ ctx.InternalServerError(err)
return
}
if !ok {
}
}
- // Redirect to log in page if auto-signin info is provided and has not signed in.
- if !options.SignOutRequired && !ctx.IsSigned && !isAPIPath &&
- len(ctx.GetCookie(setting.CookieUserName)) > 0 {
- if ctx.Req.URL.Path != "/user/events" {
- ctx.SetCookie("redirect_to", setting.AppSubURL+ctx.Req.URL.RequestURI(), 0, setting.AppSubURL)
- }
- ctx.Redirect(setting.AppSubURL + "/user/login")
- return
- }
-
if options.AdminRequired {
if !ctx.User.IsAdmin {
- ctx.Error(403)
+ ctx.JSON(403, map[string]string{
+ "message": "You have no permission to request for this.",
+ })
return
}
ctx.Data["PageIsAdmin"] = true
--- /dev/null
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package context
+
+import (
+ "sync"
+
+ "code.gitea.io/gitea/modules/setting"
+
+ "gitea.com/go-chi/captcha"
+)
+
+var imageCaptchaOnce sync.Once
+var cpt *captcha.Captcha
+
+// GetImageCaptcha returns global image captcha
+func GetImageCaptcha() *captcha.Captcha {
+ imageCaptchaOnce.Do(func() {
+ cpt = captcha.NewCaptcha(captcha.Options{
+ SubURL: setting.AppSubURL,
+ })
+ })
+ return cpt
+}
package context
import (
+ "context"
+ "crypto/sha256"
+ "encoding/hex"
+ "encoding/json"
"html"
"html/template"
"io"
"net/http"
"net/url"
"path"
+ "strconv"
"strings"
"time"
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/auth/sso"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/middlewares"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
+ "code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/util"
- "gitea.com/macaron/cache"
- "gitea.com/macaron/csrf"
- "gitea.com/macaron/i18n"
- "gitea.com/macaron/macaron"
- "gitea.com/macaron/session"
+ "gitea.com/go-chi/cache"
+ "gitea.com/go-chi/session"
+ "github.com/go-chi/chi"
"github.com/unknwon/com"
+ "github.com/unknwon/i18n"
+ "github.com/unrolled/render"
+ "golang.org/x/crypto/pbkdf2"
)
+// Render represents a template render
+type Render interface {
+ TemplateLookup(tmpl string) *template.Template
+ HTML(w io.Writer, status int, name string, binding interface{}, htmlOpt ...render.HTMLOptions) error
+}
+
// Context represents context of a request.
type Context struct {
- *macaron.Context
+ Resp ResponseWriter
+ Req *http.Request
+ Data map[string]interface{}
+ Render Render
+ translation.Locale
Cache cache.Cache
- csrf csrf.CSRF
- Flash *session.Flash
+ csrf CSRF
+ Flash *middlewares.Flash
Session session.Store
Link string // current request URL
// HTML calls Context.HTML and converts template name to string.
func (ctx *Context) HTML(status int, name base.TplName) {
log.Debug("Template: %s", name)
- ctx.Context.HTML(status, string(name))
+ if err := ctx.Render.HTML(ctx.Resp, status, string(name), ctx.Data); err != nil {
+ ctx.ServerError("Render failed", err)
+ }
+}
+
+// HTMLString render content to a string but not http.ResponseWriter
+func (ctx *Context) HTMLString(name string, data interface{}) (string, error) {
+ var buf strings.Builder
+ err := ctx.Render.HTML(&buf, 200, string(name), data)
+ return buf.String(), err
}
// RenderWithErr used for page has form validation but need to prompt error to users.
func (ctx *Context) RenderWithErr(msg string, tpl base.TplName, form interface{}) {
if form != nil {
- auth.AssignForm(form, ctx.Data)
+ middlewares.AssignForm(form, ctx.Data)
}
ctx.Flash.ErrorMsg = msg
ctx.Data["Flash"] = ctx.Flash
func (ctx *Context) notFoundInternal(title string, err error) {
if err != nil {
log.ErrorWithSkip(2, "%s: %v", title, err)
- if macaron.Env != macaron.PROD {
+ if !setting.IsProd() {
ctx.Data["ErrorMsg"] = err
}
}
func (ctx *Context) serverErrorInternal(title string, err error) {
if err != nil {
log.ErrorWithSkip(2, "%s: %v", title, err)
- if macaron.Env != macaron.PROD {
+ if !setting.IsProd() {
ctx.Data["ErrorMsg"] = err
}
}
ctx.serverErrorInternal(title, err)
}
+// Header returns a header
+func (ctx *Context) Header() http.Header {
+ return ctx.Resp.Header()
+}
+
+// FIXME: We should differ Query and Form, currently we just use form as query
+// Currently to be compatible with macaron, we keep it.
+
+// Query returns request form as string with default
+func (ctx *Context) Query(key string, defaults ...string) string {
+ return (*Forms)(ctx.Req).MustString(key, defaults...)
+}
+
+// QueryTrim returns request form as string with default and trimmed spaces
+func (ctx *Context) QueryTrim(key string, defaults ...string) string {
+ return (*Forms)(ctx.Req).MustTrimmed(key, defaults...)
+}
+
+// QueryStrings returns request form as strings with default
+func (ctx *Context) QueryStrings(key string, defaults ...[]string) []string {
+ return (*Forms)(ctx.Req).MustStrings(key, defaults...)
+}
+
+// QueryInt returns request form as int with default
+func (ctx *Context) QueryInt(key string, defaults ...int) int {
+ return (*Forms)(ctx.Req).MustInt(key, defaults...)
+}
+
+// QueryInt64 returns request form as int64 with default
+func (ctx *Context) QueryInt64(key string, defaults ...int64) int64 {
+ return (*Forms)(ctx.Req).MustInt64(key, defaults...)
+}
+
+// QueryBool returns request form as bool with default
+func (ctx *Context) QueryBool(key string, defaults ...bool) bool {
+ return (*Forms)(ctx.Req).MustBool(key, defaults...)
+}
+
// HandleText handles HTTP status code
func (ctx *Context) HandleText(status int, title string) {
if (status/100 == 4) || (status/100 == 5) {
ctx.Resp.Header().Set("Cache-Control", "must-revalidate")
ctx.Resp.Header().Set("Pragma", "public")
ctx.Resp.Header().Set("Access-Control-Expose-Headers", "Content-Disposition")
- http.ServeContent(ctx.Resp, ctx.Req.Request, name, modtime, r)
+ http.ServeContent(ctx.Resp, ctx.Req, name, modtime, r)
+}
+
+// PlainText render content as plain text
+func (ctx *Context) PlainText(status int, bs []byte) {
+ ctx.Resp.WriteHeader(status)
+ ctx.Resp.Header().Set("Content-Type", "text/plain;charset=utf8")
+ if _, err := ctx.Resp.Write(bs); err != nil {
+ ctx.ServerError("Render JSON failed", err)
+ }
+}
+
+// ServeFile serves given file to response.
+func (ctx *Context) ServeFile(file string, names ...string) {
+ var name string
+ if len(names) > 0 {
+ name = names[0]
+ } else {
+ name = path.Base(file)
+ }
+ ctx.Resp.Header().Set("Content-Description", "File Transfer")
+ ctx.Resp.Header().Set("Content-Type", "application/octet-stream")
+ ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+name)
+ ctx.Resp.Header().Set("Content-Transfer-Encoding", "binary")
+ ctx.Resp.Header().Set("Expires", "0")
+ ctx.Resp.Header().Set("Cache-Control", "must-revalidate")
+ ctx.Resp.Header().Set("Pragma", "public")
+ http.ServeFile(ctx.Resp, ctx.Req, file)
+}
+
+// Error returned an error to web browser
+func (ctx *Context) Error(status int, contents ...string) {
+ var v = http.StatusText(status)
+ if len(contents) > 0 {
+ v = contents[0]
+ }
+ http.Error(ctx.Resp, v, status)
+}
+
+// JSON render content as JSON
+func (ctx *Context) JSON(status int, content interface{}) {
+ ctx.Resp.WriteHeader(status)
+ ctx.Resp.Header().Set("Content-Type", "application/json;charset=utf8")
+ if err := json.NewEncoder(ctx.Resp).Encode(content); err != nil {
+ ctx.ServerError("Render JSON failed", err)
+ }
+}
+
+// Redirect redirect the request
+func (ctx *Context) Redirect(location string, status ...int) {
+ code := http.StatusFound
+ if len(status) == 1 {
+ code = status[0]
+ }
+
+ http.Redirect(ctx.Resp, ctx.Req, location, code)
+}
+
+// SetCookie set cookies to web browser
+func (ctx *Context) SetCookie(name string, value string, others ...interface{}) {
+ middlewares.SetCookie(ctx.Resp, name, value, others...)
+}
+
+// GetCookie returns given cookie value from request header.
+func (ctx *Context) GetCookie(name string) string {
+ return middlewares.GetCookie(ctx.Req, name)
+}
+
+// GetSuperSecureCookie returns given cookie value from request header with secret string.
+func (ctx *Context) GetSuperSecureCookie(secret, name string) (string, bool) {
+ val := ctx.GetCookie(name)
+ if val == "" {
+ return "", false
+ }
+
+ text, err := hex.DecodeString(val)
+ if err != nil {
+ return "", false
+ }
+
+ key := pbkdf2.Key([]byte(secret), []byte(secret), 1000, 16, sha256.New)
+ text, err = com.AESGCMDecrypt(key, text)
+ return string(text), err == nil
+}
+
+// SetSuperSecureCookie sets given cookie value to response header with secret string.
+func (ctx *Context) SetSuperSecureCookie(secret, name, value string, others ...interface{}) {
+ key := pbkdf2.Key([]byte(secret), []byte(secret), 1000, 16, sha256.New)
+ text, err := com.AESGCMEncrypt(key, []byte(value))
+ if err != nil {
+ panic("error encrypting cookie: " + err.Error())
+ }
+
+ ctx.SetCookie(name, hex.EncodeToString(text), others...)
+}
+
+// GetCookieInt returns cookie result in int type.
+func (ctx *Context) GetCookieInt(name string) int {
+ r, _ := strconv.Atoi(ctx.GetCookie(name))
+ return r
+}
+
+// GetCookieInt64 returns cookie result in int64 type.
+func (ctx *Context) GetCookieInt64(name string) int64 {
+ r, _ := strconv.ParseInt(ctx.GetCookie(name), 10, 64)
+ return r
+}
+
+// GetCookieFloat64 returns cookie result in float64 type.
+func (ctx *Context) GetCookieFloat64(name string) float64 {
+ v, _ := strconv.ParseFloat(ctx.GetCookie(name), 64)
+ return v
+}
+
+// RemoteAddr returns the client machie ip address
+func (ctx *Context) RemoteAddr() string {
+ return ctx.Req.RemoteAddr
+}
+
+// Params returns the param on route
+func (ctx *Context) Params(p string) string {
+ s, _ := url.PathUnescape(chi.URLParam(ctx.Req, strings.TrimPrefix(p, ":")))
+ return s
+}
+
+// ParamsInt64 returns the param on route as int64
+func (ctx *Context) ParamsInt64(p string) int64 {
+ v, _ := strconv.ParseInt(ctx.Params(p), 10, 64)
+ return v
+}
+
+// SetParams set params into routes
+func (ctx *Context) SetParams(k, v string) {
+ chiCtx := chi.RouteContext(ctx.Req.Context())
+ chiCtx.URLParams.Add(strings.TrimPrefix(k, ":"), url.PathEscape(v))
+}
+
+// Write writes data to webbrowser
+func (ctx *Context) Write(bs []byte) (int, error) {
+ return ctx.Resp.Write(bs)
+}
+
+// Written returns true if there are something sent to web browser
+func (ctx *Context) Written() bool {
+ return ctx.Resp.Status() > 0
+}
+
+// Status writes status code
+func (ctx *Context) Status(status int) {
+ ctx.Resp.WriteHeader(status)
+}
+
+// Handler represents a custom handler
+type Handler func(*Context)
+
+// enumerate all content
+var (
+ contextKey interface{} = "default_context"
+)
+
+// WithContext set up install context in request
+func WithContext(req *http.Request, ctx *Context) *http.Request {
+ return req.WithContext(context.WithValue(req.Context(), contextKey, ctx))
+}
+
+// GetContext retrieves install context from request
+func GetContext(req *http.Request) *Context {
+ return req.Context().Value(contextKey).(*Context)
+}
+
+func getCsrfOpts() CsrfOptions {
+ return CsrfOptions{
+ Secret: setting.SecretKey,
+ Cookie: setting.CSRFCookieName,
+ SetCookie: true,
+ Secure: setting.SessionConfig.Secure,
+ CookieHTTPOnly: setting.CSRFCookieHTTPOnly,
+ Header: "X-Csrf-Token",
+ CookieDomain: setting.SessionConfig.Domain,
+ CookiePath: setting.SessionConfig.CookiePath,
+ }
}
// Contexter initializes a classic context for a request.
-func Contexter() macaron.Handler {
- return func(c *macaron.Context, l i18n.Locale, cache cache.Cache, sess session.Store, f *session.Flash, x csrf.CSRF) {
- ctx := &Context{
- Context: c,
- Cache: cache,
- csrf: x,
- Flash: f,
- Session: sess,
- Link: setting.AppSubURL + strings.TrimSuffix(c.Req.URL.EscapedPath(), "/"),
- Repo: &Repository{
- PullRequest: &PullRequest{},
- },
- Org: &Organization{},
+func Contexter() func(next http.Handler) http.Handler {
+ rnd := templates.HTMLRenderer()
+
+ var c cache.Cache
+ var err error
+ if setting.CacheService.Enabled {
+ c, err = cache.NewCacher(cache.Options{
+ Adapter: setting.CacheService.Adapter,
+ AdapterConfig: setting.CacheService.Conn,
+ Interval: setting.CacheService.Interval,
+ })
+ if err != nil {
+ panic(err)
}
- ctx.Data["Language"] = ctx.Locale.Language()
- c.Data["Link"] = ctx.Link
- ctx.Data["CurrentURL"] = setting.AppSubURL + c.Req.URL.RequestURI()
- ctx.Data["PageStartTime"] = time.Now()
- // Quick responses appropriate go-get meta with status 200
- // regardless of if user have access to the repository,
- // or the repository does not exist at all.
- // This is particular a workaround for "go get" command which does not respect
- // .netrc file.
- if ctx.Query("go-get") == "1" {
- ownerName := c.Params(":username")
- repoName := c.Params(":reponame")
- trimmedRepoName := strings.TrimSuffix(repoName, ".git")
-
- if ownerName == "" || trimmedRepoName == "" {
- _, _ = c.Write([]byte(`<!doctype html>
+ }
+
+ var csrfOpts = getCsrfOpts()
+ //var flashEncryptionKey, _ = NewSecret()
+
+ return func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
+ var locale = middlewares.Locale(resp, req)
+ var startTime = time.Now()
+ var link = setting.AppSubURL + strings.TrimSuffix(req.URL.EscapedPath(), "/")
+ var ctx = Context{
+ Resp: NewResponse(resp),
+ Cache: c,
+ Locale: locale,
+ Link: link,
+ Render: rnd,
+ Session: session.GetSession(req),
+ Repo: &Repository{
+ PullRequest: &PullRequest{},
+ },
+ Org: &Organization{},
+ Data: map[string]interface{}{
+ "CurrentURL": setting.AppSubURL + req.URL.RequestURI(),
+ "PageStartTime": startTime,
+ "TmplLoadTimes": func() string {
+ return time.Since(startTime).String()
+ },
+ "Link": link,
+ },
+ }
+
+ ctx.Req = WithContext(req, &ctx)
+ ctx.csrf = Csrfer(csrfOpts, &ctx)
+
+ // Get flash.
+ flashCookie := ctx.GetCookie("macaron_flash")
+ vals, _ := url.ParseQuery(flashCookie)
+ if len(vals) > 0 {
+ f := &middlewares.Flash{
+ DataStore: &ctx,
+ Values: vals,
+ ErrorMsg: vals.Get("error"),
+ SuccessMsg: vals.Get("success"),
+ InfoMsg: vals.Get("info"),
+ WarningMsg: vals.Get("warning"),
+ }
+ ctx.Data["Flash"] = f
+ }
+
+ f := &middlewares.Flash{
+ DataStore: &ctx,
+ Values: url.Values{},
+ ErrorMsg: "",
+ WarningMsg: "",
+ InfoMsg: "",
+ SuccessMsg: "",
+ }
+ ctx.Resp.Before(func(resp ResponseWriter) {
+ if flash := f.Encode(); len(flash) > 0 {
+ if err == nil {
+ middlewares.SetCookie(resp, "macaron_flash", flash, 0,
+ setting.SessionConfig.CookiePath,
+ middlewares.Domain(setting.SessionConfig.Domain),
+ middlewares.HTTPOnly(true),
+ middlewares.Secure(setting.SessionConfig.Secure),
+ //middlewares.SameSite(opt.SameSite), FIXME: we need a samesite config
+ )
+ return
+ }
+ }
+
+ ctx.SetCookie("macaron_flash", "", -1,
+ setting.SessionConfig.CookiePath,
+ middlewares.Domain(setting.SessionConfig.Domain),
+ middlewares.HTTPOnly(true),
+ middlewares.Secure(setting.SessionConfig.Secure),
+ //middlewares.SameSite(), FIXME: we need a samesite config
+ )
+ })
+
+ ctx.Flash = f
+
+ // Quick responses appropriate go-get meta with status 200
+ // regardless of if user have access to the repository,
+ // or the repository does not exist at all.
+ // This is particular a workaround for "go get" command which does not respect
+ // .netrc file.
+ if ctx.Query("go-get") == "1" {
+ ownerName := ctx.Params(":username")
+ repoName := ctx.Params(":reponame")
+ trimmedRepoName := strings.TrimSuffix(repoName, ".git")
+
+ if ownerName == "" || trimmedRepoName == "" {
+ _, _ = ctx.Write([]byte(`<!doctype html>
<html>
<body>
invalid import path
</body>
</html>
`))
- c.WriteHeader(400)
- return
- }
- branchName := "master"
-
- repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName)
- if err == nil && len(repo.DefaultBranch) > 0 {
- branchName = repo.DefaultBranch
- }
- prefix := setting.AppURL + path.Join(url.PathEscape(ownerName), url.PathEscape(repoName), "src", "branch", util.PathEscapeSegments(branchName))
-
- appURL, _ := url.Parse(setting.AppURL)
-
- insecure := ""
- if appURL.Scheme == string(setting.HTTP) {
- insecure = "--insecure "
- }
- c.Header().Set("Content-Type", "text/html")
- c.WriteHeader(http.StatusOK)
- _, _ = c.Write([]byte(com.Expand(`<!doctype html>
+ ctx.Status(400)
+ return
+ }
+ branchName := "master"
+
+ repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName)
+ if err == nil && len(repo.DefaultBranch) > 0 {
+ branchName = repo.DefaultBranch
+ }
+ prefix := setting.AppURL + path.Join(url.PathEscape(ownerName), url.PathEscape(repoName), "src", "branch", util.PathEscapeSegments(branchName))
+
+ appURL, _ := url.Parse(setting.AppURL)
+
+ insecure := ""
+ if appURL.Scheme == string(setting.HTTP) {
+ insecure = "--insecure "
+ }
+ ctx.Header().Set("Content-Type", "text/html")
+ ctx.Status(http.StatusOK)
+ _, _ = ctx.Write([]byte(com.Expand(`<!doctype html>
<html>
<head>
<meta name="go-import" content="{GoGetImport} git {CloneLink}">
</body>
</html>
`, map[string]string{
- "GoGetImport": ComposeGoGetImport(ownerName, trimmedRepoName),
- "CloneLink": models.ComposeHTTPSCloneURL(ownerName, repoName),
- "GoDocDirectory": prefix + "{/dir}",
- "GoDocFile": prefix + "{/dir}/{file}#L{line}",
- "Insecure": insecure,
- })))
- return
- }
-
- // Get user from session if logged in.
- ctx.User, ctx.IsBasicAuth = sso.SignedInUser(ctx.Req.Request, c.Resp, ctx, ctx.Session)
-
- if ctx.User != nil {
- ctx.IsSigned = true
- ctx.Data["IsSigned"] = ctx.IsSigned
- ctx.Data["SignedUser"] = ctx.User
- ctx.Data["SignedUserID"] = ctx.User.ID
- ctx.Data["SignedUserName"] = ctx.User.Name
- ctx.Data["IsAdmin"] = ctx.User.IsAdmin
- } else {
- ctx.Data["SignedUserID"] = int64(0)
- ctx.Data["SignedUserName"] = ""
- }
-
- // If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid.
- if ctx.Req.Method == "POST" && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") {
- if err := ctx.Req.ParseMultipartForm(setting.Attachment.MaxSize << 20); err != nil && !strings.Contains(err.Error(), "EOF") { // 32MB max size
- ctx.ServerError("ParseMultipartForm", err)
+ "GoGetImport": ComposeGoGetImport(ownerName, trimmedRepoName),
+ "CloneLink": models.ComposeHTTPSCloneURL(ownerName, repoName),
+ "GoDocDirectory": prefix + "{/dir}",
+ "GoDocFile": prefix + "{/dir}/{file}#L{line}",
+ "Insecure": insecure,
+ })))
return
}
- }
-
- ctx.Resp.Header().Set(`X-Frame-Options`, `SAMEORIGIN`)
-
- ctx.Data["CsrfToken"] = html.EscapeString(x.GetToken())
- ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + ctx.Data["CsrfToken"].(string) + `">`)
- log.Debug("Session ID: %s", sess.ID())
- log.Debug("CSRF Token: %v", ctx.Data["CsrfToken"])
-
- ctx.Data["IsLandingPageHome"] = setting.LandingPageURL == setting.LandingPageHome
- ctx.Data["IsLandingPageExplore"] = setting.LandingPageURL == setting.LandingPageExplore
- ctx.Data["IsLandingPageOrganizations"] = setting.LandingPageURL == setting.LandingPageOrganizations
- ctx.Data["ShowRegistrationButton"] = setting.Service.ShowRegistrationButton
- ctx.Data["ShowMilestonesDashboardPage"] = setting.Service.ShowMilestonesDashboardPage
- ctx.Data["ShowFooterBranding"] = setting.ShowFooterBranding
- ctx.Data["ShowFooterVersion"] = setting.ShowFooterVersion
+ // If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid.
+ if ctx.Req.Method == "POST" && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") {
+ if err := ctx.Req.ParseMultipartForm(setting.Attachment.MaxSize << 20); err != nil && !strings.Contains(err.Error(), "EOF") { // 32MB max size
+ ctx.ServerError("ParseMultipartForm", err)
+ return
+ }
+ }
- ctx.Data["EnableSwagger"] = setting.API.EnableSwagger
- ctx.Data["EnableOpenIDSignIn"] = setting.Service.EnableOpenIDSignIn
- ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations
+ // Get user from session if logged in.
+ ctx.User, ctx.IsBasicAuth = sso.SignedInUser(ctx.Req, ctx.Resp, &ctx, ctx.Session)
+
+ if ctx.User != nil {
+ ctx.IsSigned = true
+ ctx.Data["IsSigned"] = ctx.IsSigned
+ ctx.Data["SignedUser"] = ctx.User
+ ctx.Data["SignedUserID"] = ctx.User.ID
+ ctx.Data["SignedUserName"] = ctx.User.Name
+ ctx.Data["IsAdmin"] = ctx.User.IsAdmin
+ } else {
+ ctx.Data["SignedUserID"] = int64(0)
+ ctx.Data["SignedUserName"] = ""
+ }
- ctx.Data["ManifestData"] = setting.ManifestData
+ ctx.Resp.Header().Set(`X-Frame-Options`, `SAMEORIGIN`)
+
+ ctx.Data["CsrfToken"] = html.EscapeString(ctx.csrf.GetToken())
+ ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + ctx.Data["CsrfToken"].(string) + `">`)
+ log.Debug("Session ID: %s", ctx.Session.ID())
+ log.Debug("CSRF Token: %v", ctx.Data["CsrfToken"])
+
+ ctx.Data["IsLandingPageHome"] = setting.LandingPageURL == setting.LandingPageHome
+ ctx.Data["IsLandingPageExplore"] = setting.LandingPageURL == setting.LandingPageExplore
+ ctx.Data["IsLandingPageOrganizations"] = setting.LandingPageURL == setting.LandingPageOrganizations
+
+ ctx.Data["ShowRegistrationButton"] = setting.Service.ShowRegistrationButton
+ ctx.Data["ShowMilestonesDashboardPage"] = setting.Service.ShowMilestonesDashboardPage
+ ctx.Data["ShowFooterBranding"] = setting.ShowFooterBranding
+ ctx.Data["ShowFooterVersion"] = setting.ShowFooterVersion
+
+ ctx.Data["EnableSwagger"] = setting.API.EnableSwagger
+ ctx.Data["EnableOpenIDSignIn"] = setting.Service.EnableOpenIDSignIn
+ ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations
+
+ ctx.Data["ManifestData"] = setting.ManifestData
+
+ ctx.Data["i18n"] = locale
+ ctx.Data["Tr"] = i18n.Tr
+ ctx.Data["Lang"] = locale.Language()
+ ctx.Data["AllLangs"] = translation.AllLangs()
+ for _, lang := range translation.AllLangs() {
+ if lang.Lang == locale.Language() {
+ ctx.Data["LangName"] = lang.Name
+ break
+ }
+ }
- c.Map(ctx)
+ next.ServeHTTP(ctx.Resp, ctx.Req)
+ })
}
}
--- /dev/null
+// Copyright 2013 Martini Authors
+// Copyright 2014 The Macaron Authors
+// Copyright 2021 The Gitea Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+// a middleware that generates and validates CSRF tokens.
+
+package context
+
+import (
+ "net/http"
+ "time"
+
+ "github.com/unknwon/com"
+)
+
+// CSRF represents a CSRF service and is used to get the current token and validate a suspect token.
+type CSRF interface {
+ // Return HTTP header to search for token.
+ GetHeaderName() string
+ // Return form value to search for token.
+ GetFormName() string
+ // Return cookie name to search for token.
+ GetCookieName() string
+ // Return cookie path
+ GetCookiePath() string
+ // Return the flag value used for the csrf token.
+ GetCookieHTTPOnly() bool
+ // Return the token.
+ GetToken() string
+ // Validate by token.
+ ValidToken(t string) bool
+ // Error replies to the request with a custom function when ValidToken fails.
+ Error(w http.ResponseWriter)
+}
+
+type csrf struct {
+ // Header name value for setting and getting csrf token.
+ Header string
+ // Form name value for setting and getting csrf token.
+ Form string
+ // Cookie name value for setting and getting csrf token.
+ Cookie string
+ //Cookie domain
+ CookieDomain string
+ //Cookie path
+ CookiePath string
+ // Cookie HttpOnly flag value used for the csrf token.
+ CookieHTTPOnly bool
+ // Token generated to pass via header, cookie, or hidden form value.
+ Token string
+ // This value must be unique per user.
+ ID string
+ // Secret used along with the unique id above to generate the Token.
+ Secret string
+ // ErrorFunc is the custom function that replies to the request when ValidToken fails.
+ ErrorFunc func(w http.ResponseWriter)
+}
+
+// GetHeaderName returns the name of the HTTP header for csrf token.
+func (c *csrf) GetHeaderName() string {
+ return c.Header
+}
+
+// GetFormName returns the name of the form value for csrf token.
+func (c *csrf) GetFormName() string {
+ return c.Form
+}
+
+// GetCookieName returns the name of the cookie for csrf token.
+func (c *csrf) GetCookieName() string {
+ return c.Cookie
+}
+
+// GetCookiePath returns the path of the cookie for csrf token.
+func (c *csrf) GetCookiePath() string {
+ return c.CookiePath
+}
+
+// GetCookieHTTPOnly returns the flag value used for the csrf token.
+func (c *csrf) GetCookieHTTPOnly() bool {
+ return c.CookieHTTPOnly
+}
+
+// GetToken returns the current token. This is typically used
+// to populate a hidden form in an HTML template.
+func (c *csrf) GetToken() string {
+ return c.Token
+}
+
+// ValidToken validates the passed token against the existing Secret and ID.
+func (c *csrf) ValidToken(t string) bool {
+ return ValidToken(t, c.Secret, c.ID, "POST")
+}
+
+// Error replies to the request when ValidToken fails.
+func (c *csrf) Error(w http.ResponseWriter) {
+ c.ErrorFunc(w)
+}
+
+// CsrfOptions maintains options to manage behavior of Generate.
+type CsrfOptions struct {
+ // The global secret value used to generate Tokens.
+ Secret string
+ // HTTP header used to set and get token.
+ Header string
+ // Form value used to set and get token.
+ Form string
+ // Cookie value used to set and get token.
+ Cookie string
+ // Cookie domain.
+ CookieDomain string
+ // Cookie path.
+ CookiePath string
+ CookieHTTPOnly bool
+ // SameSite set the cookie SameSite type
+ SameSite http.SameSite
+ // Key used for getting the unique ID per user.
+ SessionKey string
+ // oldSessionKey saves old value corresponding to SessionKey.
+ oldSessionKey string
+ // If true, send token via X-CSRFToken header.
+ SetHeader bool
+ // If true, send token via _csrf cookie.
+ SetCookie bool
+ // Set the Secure flag to true on the cookie.
+ Secure bool
+ // Disallow Origin appear in request header.
+ Origin bool
+ // The function called when Validate fails.
+ ErrorFunc func(w http.ResponseWriter)
+ // Cookie life time. Default is 0
+ CookieLifeTime int
+}
+
+func prepareOptions(options []CsrfOptions) CsrfOptions {
+ var opt CsrfOptions
+ if len(options) > 0 {
+ opt = options[0]
+ }
+
+ // Defaults.
+ if len(opt.Secret) == 0 {
+ opt.Secret = string(com.RandomCreateBytes(10))
+ }
+ if len(opt.Header) == 0 {
+ opt.Header = "X-CSRFToken"
+ }
+ if len(opt.Form) == 0 {
+ opt.Form = "_csrf"
+ }
+ if len(opt.Cookie) == 0 {
+ opt.Cookie = "_csrf"
+ }
+ if len(opt.CookiePath) == 0 {
+ opt.CookiePath = "/"
+ }
+ if len(opt.SessionKey) == 0 {
+ opt.SessionKey = "uid"
+ }
+ opt.oldSessionKey = "_old_" + opt.SessionKey
+ if opt.ErrorFunc == nil {
+ opt.ErrorFunc = func(w http.ResponseWriter) {
+ http.Error(w, "Invalid csrf token.", http.StatusBadRequest)
+ }
+ }
+
+ return opt
+}
+
+// Csrfer maps CSRF to each request. If this request is a Get request, it will generate a new token.
+// Additionally, depending on options set, generated tokens will be sent via Header and/or Cookie.
+func Csrfer(opt CsrfOptions, ctx *Context) CSRF {
+ opt = prepareOptions([]CsrfOptions{opt})
+ x := &csrf{
+ Secret: opt.Secret,
+ Header: opt.Header,
+ Form: opt.Form,
+ Cookie: opt.Cookie,
+ CookieDomain: opt.CookieDomain,
+ CookiePath: opt.CookiePath,
+ CookieHTTPOnly: opt.CookieHTTPOnly,
+ ErrorFunc: opt.ErrorFunc,
+ }
+
+ if opt.Origin && len(ctx.Req.Header.Get("Origin")) > 0 {
+ return x
+ }
+
+ x.ID = "0"
+ uid := ctx.Session.Get(opt.SessionKey)
+ if uid != nil {
+ x.ID = com.ToStr(uid)
+ }
+
+ needsNew := false
+ oldUID := ctx.Session.Get(opt.oldSessionKey)
+ if oldUID == nil || oldUID.(string) != x.ID {
+ needsNew = true
+ _ = ctx.Session.Set(opt.oldSessionKey, x.ID)
+ } else {
+ // If cookie present, map existing token, else generate a new one.
+ if val := ctx.GetCookie(opt.Cookie); len(val) > 0 {
+ // FIXME: test coverage.
+ x.Token = val
+ } else {
+ needsNew = true
+ }
+ }
+
+ if needsNew {
+ // FIXME: actionId.
+ x.Token = GenerateToken(x.Secret, x.ID, "POST")
+ if opt.SetCookie {
+ var expires interface{}
+ if opt.CookieLifeTime == 0 {
+ expires = time.Now().AddDate(0, 0, 1)
+ }
+ ctx.SetCookie(opt.Cookie, x.Token, opt.CookieLifeTime, opt.CookiePath, opt.CookieDomain, opt.Secure, opt.CookieHTTPOnly, expires,
+ func(c *http.Cookie) {
+ c.SameSite = opt.SameSite
+ },
+ )
+ }
+ }
+
+ if opt.SetHeader {
+ ctx.Resp.Header().Add(opt.Header, x.Token)
+ }
+ return x
+}
+
+// Validate should be used as a per route middleware. It attempts to get a token from a "X-CSRFToken"
+// HTTP header and then a "_csrf" form value. If one of these is found, the token will be validated
+// using ValidToken. If this validation fails, custom Error is sent in the reply.
+// If neither a header or form value is found, http.StatusBadRequest is sent.
+func Validate(ctx *Context, x CSRF) {
+ if token := ctx.Req.Header.Get(x.GetHeaderName()); len(token) > 0 {
+ if !x.ValidToken(token) {
+ ctx.SetCookie(x.GetCookieName(), "", -1, x.GetCookiePath())
+ x.Error(ctx.Resp)
+ }
+ return
+ }
+ if token := ctx.Req.FormValue(x.GetFormName()); len(token) > 0 {
+ if !x.ValidToken(token) {
+ ctx.SetCookie(x.GetCookieName(), "", -1, x.GetCookiePath())
+ x.Error(ctx.Resp)
+ }
+ return
+ }
+
+ http.Error(ctx.Resp, "Bad Request: no CSRF token present", http.StatusBadRequest)
+}
--- /dev/null
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package context
+
+import (
+ "errors"
+ "net/http"
+ "net/url"
+ "strconv"
+ "strings"
+ "text/template"
+
+ "code.gitea.io/gitea/modules/log"
+)
+
+// Forms a new enhancement of http.Request
+type Forms http.Request
+
+// Values returns http.Request values
+func (f *Forms) Values() url.Values {
+ return (*http.Request)(f).Form
+}
+
+// String returns request form as string
+func (f *Forms) String(key string) (string, error) {
+ return (*http.Request)(f).FormValue(key), nil
+}
+
+// Trimmed returns request form as string with trimed spaces left and right
+func (f *Forms) Trimmed(key string) (string, error) {
+ return strings.TrimSpace((*http.Request)(f).FormValue(key)), nil
+}
+
+// Strings returns request form as strings
+func (f *Forms) Strings(key string) ([]string, error) {
+ if (*http.Request)(f).Form == nil {
+ if err := (*http.Request)(f).ParseMultipartForm(32 << 20); err != nil {
+ return nil, err
+ }
+ }
+ if v, ok := (*http.Request)(f).Form[key]; ok {
+ return v, nil
+ }
+ return nil, errors.New("not exist")
+}
+
+// Escape returns request form as escaped string
+func (f *Forms) Escape(key string) (string, error) {
+ return template.HTMLEscapeString((*http.Request)(f).FormValue(key)), nil
+}
+
+// Int returns request form as int
+func (f *Forms) Int(key string) (int, error) {
+ return strconv.Atoi((*http.Request)(f).FormValue(key))
+}
+
+// Int32 returns request form as int32
+func (f *Forms) Int32(key string) (int32, error) {
+ v, err := strconv.ParseInt((*http.Request)(f).FormValue(key), 10, 32)
+ return int32(v), err
+}
+
+// Int64 returns request form as int64
+func (f *Forms) Int64(key string) (int64, error) {
+ return strconv.ParseInt((*http.Request)(f).FormValue(key), 10, 64)
+}
+
+// Uint returns request form as uint
+func (f *Forms) Uint(key string) (uint, error) {
+ v, err := strconv.ParseUint((*http.Request)(f).FormValue(key), 10, 64)
+ return uint(v), err
+}
+
+// Uint32 returns request form as uint32
+func (f *Forms) Uint32(key string) (uint32, error) {
+ v, err := strconv.ParseUint((*http.Request)(f).FormValue(key), 10, 32)
+ return uint32(v), err
+}
+
+// Uint64 returns request form as uint64
+func (f *Forms) Uint64(key string) (uint64, error) {
+ return strconv.ParseUint((*http.Request)(f).FormValue(key), 10, 64)
+}
+
+// Bool returns request form as bool
+func (f *Forms) Bool(key string) (bool, error) {
+ return strconv.ParseBool((*http.Request)(f).FormValue(key))
+}
+
+// Float32 returns request form as float32
+func (f *Forms) Float32(key string) (float32, error) {
+ v, err := strconv.ParseFloat((*http.Request)(f).FormValue(key), 64)
+ return float32(v), err
+}
+
+// Float64 returns request form as float64
+func (f *Forms) Float64(key string) (float64, error) {
+ return strconv.ParseFloat((*http.Request)(f).FormValue(key), 64)
+}
+
+// MustString returns request form as string with default
+func (f *Forms) MustString(key string, defaults ...string) string {
+ if v := (*http.Request)(f).FormValue(key); len(v) > 0 {
+ return v
+ }
+ if len(defaults) > 0 {
+ return defaults[0]
+ }
+ return ""
+}
+
+// MustTrimmed returns request form as string with default
+func (f *Forms) MustTrimmed(key string, defaults ...string) string {
+ return strings.TrimSpace(f.MustString(key, defaults...))
+}
+
+// MustStrings returns request form as strings with default
+func (f *Forms) MustStrings(key string, defaults ...[]string) []string {
+ if (*http.Request)(f).Form == nil {
+ if err := (*http.Request)(f).ParseMultipartForm(32 << 20); err != nil {
+ log.Error("ParseMultipartForm: %v", err)
+ return []string{}
+ }
+ }
+
+ if v, ok := (*http.Request)(f).Form[key]; ok {
+ return v
+ }
+ if len(defaults) > 0 {
+ return defaults[0]
+ }
+ return []string{}
+}
+
+// MustEscape returns request form as escaped string with default
+func (f *Forms) MustEscape(key string, defaults ...string) string {
+ if v := (*http.Request)(f).FormValue(key); len(v) > 0 {
+ return template.HTMLEscapeString(v)
+ }
+ if len(defaults) > 0 {
+ return defaults[0]
+ }
+ return ""
+}
+
+// MustInt returns request form as int with default
+func (f *Forms) MustInt(key string, defaults ...int) int {
+ v, err := strconv.Atoi((*http.Request)(f).FormValue(key))
+ if len(defaults) > 0 && err != nil {
+ return defaults[0]
+ }
+ return v
+}
+
+// MustInt32 returns request form as int32 with default
+func (f *Forms) MustInt32(key string, defaults ...int32) int32 {
+ v, err := strconv.ParseInt((*http.Request)(f).FormValue(key), 10, 32)
+ if len(defaults) > 0 && err != nil {
+ return defaults[0]
+ }
+ return int32(v)
+}
+
+// MustInt64 returns request form as int64 with default
+func (f *Forms) MustInt64(key string, defaults ...int64) int64 {
+ v, err := strconv.ParseInt((*http.Request)(f).FormValue(key), 10, 64)
+ if len(defaults) > 0 && err != nil {
+ return defaults[0]
+ }
+ return v
+}
+
+// MustUint returns request form as uint with default
+func (f *Forms) MustUint(key string, defaults ...uint) uint {
+ v, err := strconv.ParseUint((*http.Request)(f).FormValue(key), 10, 64)
+ if len(defaults) > 0 && err != nil {
+ return defaults[0]
+ }
+ return uint(v)
+}
+
+// MustUint32 returns request form as uint32 with default
+func (f *Forms) MustUint32(key string, defaults ...uint32) uint32 {
+ v, err := strconv.ParseUint((*http.Request)(f).FormValue(key), 10, 32)
+ if len(defaults) > 0 && err != nil {
+ return defaults[0]
+ }
+ return uint32(v)
+}
+
+// MustUint64 returns request form as uint64 with default
+func (f *Forms) MustUint64(key string, defaults ...uint64) uint64 {
+ v, err := strconv.ParseUint((*http.Request)(f).FormValue(key), 10, 64)
+ if len(defaults) > 0 && err != nil {
+ return defaults[0]
+ }
+ return v
+}
+
+// MustFloat32 returns request form as float32 with default
+func (f *Forms) MustFloat32(key string, defaults ...float32) float32 {
+ v, err := strconv.ParseFloat((*http.Request)(f).FormValue(key), 32)
+ if len(defaults) > 0 && err != nil {
+ return defaults[0]
+ }
+ return float32(v)
+}
+
+// MustFloat64 returns request form as float64 with default
+func (f *Forms) MustFloat64(key string, defaults ...float64) float64 {
+ v, err := strconv.ParseFloat((*http.Request)(f).FormValue(key), 64)
+ if len(defaults) > 0 && err != nil {
+ return defaults[0]
+ }
+ return v
+}
+
+// MustBool returns request form as bool with default
+func (f *Forms) MustBool(key string, defaults ...bool) bool {
+ v, err := strconv.ParseBool((*http.Request)(f).FormValue(key))
+ if len(defaults) > 0 && err != nil {
+ return defaults[0]
+ }
+ return v
+}
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/setting"
-
- "gitea.com/macaron/macaron"
)
// Organization contains organization context
}
// OrgAssignment returns a macaron middleware to handle organization assignment
-func OrgAssignment(args ...bool) macaron.Handler {
+func OrgAssignment(args ...bool) func(ctx *Context) {
return func(ctx *Context) {
HandleOrgAssignment(ctx, args...)
}
import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
-
- "gitea.com/macaron/macaron"
)
// RequireRepoAdmin returns a macaron middleware for requiring repository admin permission
-func RequireRepoAdmin() macaron.Handler {
+func RequireRepoAdmin() func(ctx *Context) {
return func(ctx *Context) {
if !ctx.IsSigned || !ctx.Repo.IsAdmin() {
ctx.NotFound(ctx.Req.URL.RequestURI(), nil)
}
// RequireRepoWriter returns a macaron middleware for requiring repository write to the specify unitType
-func RequireRepoWriter(unitType models.UnitType) macaron.Handler {
+func RequireRepoWriter(unitType models.UnitType) func(ctx *Context) {
return func(ctx *Context) {
if !ctx.Repo.CanWrite(unitType) {
ctx.NotFound(ctx.Req.URL.RequestURI(), nil)
}
// RequireRepoWriterOr returns a macaron middleware for requiring repository write to one of the unit permission
-func RequireRepoWriterOr(unitTypes ...models.UnitType) macaron.Handler {
+func RequireRepoWriterOr(unitTypes ...models.UnitType) func(ctx *Context) {
return func(ctx *Context) {
for _, unitType := range unitTypes {
if ctx.Repo.CanWrite(unitType) {
}
// RequireRepoReader returns a macaron middleware for requiring repository read to the specify unitType
-func RequireRepoReader(unitType models.UnitType) macaron.Handler {
+func RequireRepoReader(unitType models.UnitType) func(ctx *Context) {
return func(ctx *Context) {
if !ctx.Repo.CanRead(unitType) {
if log.IsTrace() {
}
// RequireRepoReaderOr returns a macaron middleware for requiring repository write to one of the unit permission
-func RequireRepoReaderOr(unitTypes ...models.UnitType) macaron.Handler {
+func RequireRepoReaderOr(unitTypes ...models.UnitType) func(ctx *Context) {
return func(ctx *Context) {
for _, unitType := range unitTypes {
if ctx.Repo.CanRead(unitType) {
--- /dev/null
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package context
+
+import (
+ "context"
+ "net/http"
+)
+
+// PrivateContext represents a context for private routes
+type PrivateContext struct {
+ *Context
+}
+
+var (
+ privateContextKey interface{} = "default_private_context"
+)
+
+// WithPrivateContext set up private context in request
+func WithPrivateContext(req *http.Request, ctx *PrivateContext) *http.Request {
+ return req.WithContext(context.WithValue(req.Context(), privateContextKey, ctx))
+}
+
+// GetPrivateContext returns a context for Private routes
+func GetPrivateContext(req *http.Request) *PrivateContext {
+ return req.Context().Value(privateContextKey).(*PrivateContext)
+}
+
+// PrivateContexter returns apicontext as macaron middleware
+func PrivateContexter() func(http.Handler) http.Handler {
+ return func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ ctx := &PrivateContext{
+ Context: &Context{
+ Resp: NewResponse(w),
+ Data: map[string]interface{}{},
+ },
+ }
+ ctx.Req = WithPrivateContext(req, ctx)
+ next.ServeHTTP(ctx.Resp, ctx.Req)
+ })
+ }
+}
import (
"fmt"
"io/ioutil"
+ "net/http"
"net/url"
"path"
"strings"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
- "gitea.com/macaron/macaron"
"github.com/editorconfig/editorconfig-core-go/v2"
"github.com/unknwon/com"
)
}
// RepoMustNotBeArchived checks if a repo is archived
-func RepoMustNotBeArchived() macaron.Handler {
+func RepoMustNotBeArchived() func(ctx *Context) {
return func(ctx *Context) {
if ctx.Repo.Repository.IsArchived {
ctx.NotFound("IsArchived", fmt.Errorf(ctx.Tr("repo.archive.title")))
}
// RepoIDAssignment returns a macaron handler which assigns the repo to the context.
-func RepoIDAssignment() macaron.Handler {
+func RepoIDAssignment() func(ctx *Context) {
return func(ctx *Context) {
repoID := ctx.ParamsInt64(":repoid")
}
// RepoAssignment returns a macaron to handle repository assignment
-func RepoAssignment() macaron.Handler {
- return func(ctx *Context) {
- var (
- owner *models.User
- err error
- )
-
- userName := ctx.Params(":username")
- repoName := ctx.Params(":reponame")
+func RepoAssignment() func(http.Handler) http.Handler {
+ return func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ var (
+ owner *models.User
+ err error
+ ctx = GetContext(req)
+ )
+
+ userName := ctx.Params(":username")
+ repoName := ctx.Params(":reponame")
+ repoName = strings.TrimSuffix(repoName, ".git")
+
+ // Check if the user is the same as the repository owner
+ if ctx.IsSigned && ctx.User.LowerName == strings.ToLower(userName) {
+ owner = ctx.User
+ } else {
+ owner, err = models.GetUserByName(userName)
+ if err != nil {
+ if models.IsErrUserNotExist(err) {
+ if ctx.Query("go-get") == "1" {
+ EarlyResponseForGoGetMeta(ctx)
+ return
+ }
+ ctx.NotFound("GetUserByName", nil)
+ } else {
+ ctx.ServerError("GetUserByName", err)
+ }
+ return
+ }
+ }
+ ctx.Repo.Owner = owner
+ ctx.Data["Username"] = ctx.Repo.Owner.Name
- // Check if the user is the same as the repository owner
- if ctx.IsSigned && ctx.User.LowerName == strings.ToLower(userName) {
- owner = ctx.User
- } else {
- owner, err = models.GetUserByName(userName)
+ // Get repository.
+ repo, err := models.GetRepositoryByName(owner.ID, repoName)
if err != nil {
- if models.IsErrUserNotExist(err) {
- redirectUserID, err := models.LookupUserRedirect(userName)
+ if models.IsErrRepoNotExist(err) {
+ redirectRepoID, err := models.LookupRepoRedirect(owner.ID, repoName)
if err == nil {
- RedirectToUser(ctx, userName, redirectUserID)
- } else if models.IsErrUserRedirectNotExist(err) {
+ RedirectToRepo(ctx, redirectRepoID)
+ } else if models.IsErrRepoRedirectNotExist(err) {
if ctx.Query("go-get") == "1" {
EarlyResponseForGoGetMeta(ctx)
return
}
- ctx.NotFound("GetUserByName", nil)
+ ctx.NotFound("GetRepositoryByName", nil)
} else {
- ctx.ServerError("LookupUserRedirect", err)
+ ctx.ServerError("LookupRepoRedirect", err)
}
} else {
- ctx.ServerError("GetUserByName", err)
+ ctx.ServerError("GetRepositoryByName", err)
}
return
}
- }
- ctx.Repo.Owner = owner
- ctx.Data["Username"] = ctx.Repo.Owner.Name
+ repo.Owner = owner
- // Get repository.
- repo, err := models.GetRepositoryByName(owner.ID, repoName)
- if err != nil {
- if models.IsErrRepoNotExist(err) {
- redirectRepoID, err := models.LookupRepoRedirect(owner.ID, repoName)
- if err == nil {
- RedirectToRepo(ctx, redirectRepoID)
- } else if models.IsErrRepoRedirectNotExist(err) {
- if ctx.Query("go-get") == "1" {
- EarlyResponseForGoGetMeta(ctx)
- return
- }
- ctx.NotFound("GetRepositoryByName", nil)
- } else {
- ctx.ServerError("LookupRepoRedirect", err)
- }
- } else {
- ctx.ServerError("GetRepositoryByName", err)
+ repoAssignment(ctx, repo)
+ if ctx.Written() {
+ return
}
- return
- }
- repo.Owner = owner
- repoAssignment(ctx, repo)
- if ctx.Written() {
- return
- }
+ ctx.Repo.RepoLink = repo.Link()
+ ctx.Data["RepoLink"] = ctx.Repo.RepoLink
+ ctx.Data["RepoRelPath"] = ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name
- ctx.Repo.RepoLink = repo.Link()
- ctx.Data["RepoLink"] = ctx.Repo.RepoLink
- ctx.Data["RepoRelPath"] = ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name
+ unit, err := ctx.Repo.Repository.GetUnit(models.UnitTypeExternalTracker)
+ if err == nil {
+ ctx.Data["RepoExternalIssuesLink"] = unit.ExternalTrackerConfig().ExternalTrackerURL
+ }
- unit, err := ctx.Repo.Repository.GetUnit(models.UnitTypeExternalTracker)
- if err == nil {
- ctx.Data["RepoExternalIssuesLink"] = unit.ExternalTrackerConfig().ExternalTrackerURL
- }
+ ctx.Data["NumTags"], err = models.GetReleaseCountByRepoID(ctx.Repo.Repository.ID, models.FindReleasesOptions{
+ IncludeTags: true,
+ })
+ if err != nil {
+ ctx.ServerError("GetReleaseCountByRepoID", err)
+ return
+ }
+ ctx.Data["NumReleases"], err = models.GetReleaseCountByRepoID(ctx.Repo.Repository.ID, models.FindReleasesOptions{})
+ if err != nil {
+ ctx.ServerError("GetReleaseCountByRepoID", err)
+ return
+ }
- ctx.Data["NumTags"], err = models.GetReleaseCountByRepoID(ctx.Repo.Repository.ID, models.FindReleasesOptions{
- IncludeTags: true,
- })
- if err != nil {
- ctx.ServerError("GetReleaseCountByRepoID", err)
- return
- }
- ctx.Data["NumReleases"], err = models.GetReleaseCountByRepoID(ctx.Repo.Repository.ID, models.FindReleasesOptions{})
- if err != nil {
- ctx.ServerError("GetReleaseCountByRepoID", err)
- return
- }
+ ctx.Data["Title"] = owner.Name + "/" + repo.Name
+ ctx.Data["Repository"] = repo
+ ctx.Data["Owner"] = ctx.Repo.Repository.Owner
+ ctx.Data["IsRepositoryOwner"] = ctx.Repo.IsOwner()
+ ctx.Data["IsRepositoryAdmin"] = ctx.Repo.IsAdmin()
+ ctx.Data["RepoOwnerIsOrganization"] = repo.Owner.IsOrganization()
+ ctx.Data["CanWriteCode"] = ctx.Repo.CanWrite(models.UnitTypeCode)
+ ctx.Data["CanWriteIssues"] = ctx.Repo.CanWrite(models.UnitTypeIssues)
+ ctx.Data["CanWritePulls"] = ctx.Repo.CanWrite(models.UnitTypePullRequests)
+
+ if ctx.Data["CanSignedUserFork"], err = ctx.Repo.Repository.CanUserFork(ctx.User); err != nil {
+ ctx.ServerError("CanUserFork", err)
+ return
+ }
- ctx.Data["Title"] = owner.Name + "/" + repo.Name
- ctx.Data["Repository"] = repo
- ctx.Data["Owner"] = ctx.Repo.Repository.Owner
- ctx.Data["IsRepositoryOwner"] = ctx.Repo.IsOwner()
- ctx.Data["IsRepositoryAdmin"] = ctx.Repo.IsAdmin()
- ctx.Data["RepoOwnerIsOrganization"] = repo.Owner.IsOrganization()
- ctx.Data["CanWriteCode"] = ctx.Repo.CanWrite(models.UnitTypeCode)
- ctx.Data["CanWriteIssues"] = ctx.Repo.CanWrite(models.UnitTypeIssues)
- ctx.Data["CanWritePulls"] = ctx.Repo.CanWrite(models.UnitTypePullRequests)
-
- if ctx.Data["CanSignedUserFork"], err = ctx.Repo.Repository.CanUserFork(ctx.User); err != nil {
- ctx.ServerError("CanUserFork", err)
- return
- }
+ ctx.Data["DisableSSH"] = setting.SSH.Disabled
+ ctx.Data["ExposeAnonSSH"] = setting.SSH.ExposeAnonymous
+ ctx.Data["DisableHTTP"] = setting.Repository.DisableHTTPGit
+ ctx.Data["RepoSearchEnabled"] = setting.Indexer.RepoIndexerEnabled
+ ctx.Data["CloneLink"] = repo.CloneLink()
+ ctx.Data["WikiCloneLink"] = repo.WikiCloneLink()
- ctx.Data["DisableSSH"] = setting.SSH.Disabled
- ctx.Data["ExposeAnonSSH"] = setting.SSH.ExposeAnonymous
- ctx.Data["DisableHTTP"] = setting.Repository.DisableHTTPGit
- ctx.Data["RepoSearchEnabled"] = setting.Indexer.RepoIndexerEnabled
- ctx.Data["CloneLink"] = repo.CloneLink()
- ctx.Data["WikiCloneLink"] = repo.WikiCloneLink()
+ if ctx.IsSigned {
+ ctx.Data["IsWatchingRepo"] = models.IsWatching(ctx.User.ID, repo.ID)
+ ctx.Data["IsStaringRepo"] = models.IsStaring(ctx.User.ID, repo.ID)
+ }
- if ctx.IsSigned {
- ctx.Data["IsWatchingRepo"] = models.IsWatching(ctx.User.ID, repo.ID)
- ctx.Data["IsStaringRepo"] = models.IsStaring(ctx.User.ID, repo.ID)
- }
+ if repo.IsFork {
+ RetrieveBaseRepo(ctx, repo)
+ if ctx.Written() {
+ return
+ }
+ }
- if repo.IsFork {
- RetrieveBaseRepo(ctx, repo)
- if ctx.Written() {
- return
+ if repo.IsGenerated() {
+ RetrieveTemplateRepo(ctx, repo)
+ if ctx.Written() {
+ return
+ }
}
- }
- if repo.IsGenerated() {
- RetrieveTemplateRepo(ctx, repo)
- if ctx.Written() {
+ // Disable everything when the repo is being created
+ if ctx.Repo.Repository.IsBeingCreated() {
+ ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
return
}
- }
-
- // Disable everything when the repo is being created
- if ctx.Repo.Repository.IsBeingCreated() {
- ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
- return
- }
- gitRepo, err := git.OpenRepository(models.RepoPath(userName, repoName))
- if err != nil {
- ctx.ServerError("RepoAssignment Invalid repo "+models.RepoPath(userName, repoName), err)
- return
- }
- ctx.Repo.GitRepo = gitRepo
-
- // We opened it, we should close it
- defer func() {
- // If it's been set to nil then assume someone else has closed it.
- if ctx.Repo.GitRepo != nil {
- ctx.Repo.GitRepo.Close()
+ gitRepo, err := git.OpenRepository(models.RepoPath(userName, repoName))
+ if err != nil {
+ ctx.ServerError("RepoAssignment Invalid repo "+models.RepoPath(userName, repoName), err)
+ return
}
- }()
+ ctx.Repo.GitRepo = gitRepo
- // Stop at this point when the repo is empty.
- if ctx.Repo.Repository.IsEmpty {
- ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
- ctx.Next()
- return
- }
+ // We opened it, we should close it
+ defer func() {
+ // If it's been set to nil then assume someone else has closed it.
+ if ctx.Repo.GitRepo != nil {
+ ctx.Repo.GitRepo.Close()
+ }
+ }()
- tags, err := ctx.Repo.GitRepo.GetTags()
- if err != nil {
- ctx.ServerError("GetTags", err)
- return
- }
- ctx.Data["Tags"] = tags
+ // Stop at this point when the repo is empty.
+ if ctx.Repo.Repository.IsEmpty {
+ ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
+ next.ServeHTTP(w, req)
+ return
+ }
- brs, err := ctx.Repo.GitRepo.GetBranches()
- if err != nil {
- ctx.ServerError("GetBranches", err)
- return
- }
- ctx.Data["Branches"] = brs
- ctx.Data["BranchesCount"] = len(brs)
-
- ctx.Data["TagName"] = ctx.Repo.TagName
-
- // If not branch selected, try default one.
- // If default branch doesn't exists, fall back to some other branch.
- if len(ctx.Repo.BranchName) == 0 {
- if len(ctx.Repo.Repository.DefaultBranch) > 0 && gitRepo.IsBranchExist(ctx.Repo.Repository.DefaultBranch) {
- ctx.Repo.BranchName = ctx.Repo.Repository.DefaultBranch
- } else if len(brs) > 0 {
- ctx.Repo.BranchName = brs[0]
+ tags, err := ctx.Repo.GitRepo.GetTags()
+ if err != nil {
+ ctx.ServerError("GetTags", err)
+ return
}
- }
- ctx.Data["BranchName"] = ctx.Repo.BranchName
- ctx.Data["CommitID"] = ctx.Repo.CommitID
-
- // People who have push access or have forked repository can propose a new pull request.
- canPush := ctx.Repo.CanWrite(models.UnitTypeCode) || (ctx.IsSigned && ctx.User.HasForkedRepo(ctx.Repo.Repository.ID))
- canCompare := false
-
- // Pull request is allowed if this is a fork repository
- // and base repository accepts pull requests.
- if repo.BaseRepo != nil && repo.BaseRepo.AllowsPulls() {
- canCompare = true
- ctx.Data["BaseRepo"] = repo.BaseRepo
- ctx.Repo.PullRequest.BaseRepo = repo.BaseRepo
- ctx.Repo.PullRequest.Allowed = canPush
- ctx.Repo.PullRequest.HeadInfo = ctx.Repo.Owner.Name + ":" + ctx.Repo.BranchName
- } else if repo.AllowsPulls() {
- // Or, this is repository accepts pull requests between branches.
- canCompare = true
- ctx.Data["BaseRepo"] = repo
- ctx.Repo.PullRequest.BaseRepo = repo
- ctx.Repo.PullRequest.Allowed = canPush
- ctx.Repo.PullRequest.SameRepo = true
- ctx.Repo.PullRequest.HeadInfo = ctx.Repo.BranchName
- }
- ctx.Data["CanCompareOrPull"] = canCompare
- ctx.Data["PullRequestCtx"] = ctx.Repo.PullRequest
+ ctx.Data["Tags"] = tags
- if ctx.Query("go-get") == "1" {
- ctx.Data["GoGetImport"] = ComposeGoGetImport(owner.Name, repo.Name)
- prefix := setting.AppURL + path.Join(owner.Name, repo.Name, "src", "branch", ctx.Repo.BranchName)
- ctx.Data["GoDocDirectory"] = prefix + "{/dir}"
- ctx.Data["GoDocFile"] = prefix + "{/dir}/{file}#L{line}"
- }
- ctx.Next()
+ brs, err := ctx.Repo.GitRepo.GetBranches()
+ if err != nil {
+ ctx.ServerError("GetBranches", err)
+ return
+ }
+ ctx.Data["Branches"] = brs
+ ctx.Data["BranchesCount"] = len(brs)
+
+ ctx.Data["TagName"] = ctx.Repo.TagName
+
+ // If not branch selected, try default one.
+ // If default branch doesn't exists, fall back to some other branch.
+ if len(ctx.Repo.BranchName) == 0 {
+ if len(ctx.Repo.Repository.DefaultBranch) > 0 && gitRepo.IsBranchExist(ctx.Repo.Repository.DefaultBranch) {
+ ctx.Repo.BranchName = ctx.Repo.Repository.DefaultBranch
+ } else if len(brs) > 0 {
+ ctx.Repo.BranchName = brs[0]
+ }
+ }
+ ctx.Data["BranchName"] = ctx.Repo.BranchName
+ ctx.Data["CommitID"] = ctx.Repo.CommitID
+
+ // People who have push access or have forked repository can propose a new pull request.
+ canPush := ctx.Repo.CanWrite(models.UnitTypeCode) || (ctx.IsSigned && ctx.User.HasForkedRepo(ctx.Repo.Repository.ID))
+ canCompare := false
+
+ // Pull request is allowed if this is a fork repository
+ // and base repository accepts pull requests.
+ if repo.BaseRepo != nil && repo.BaseRepo.AllowsPulls() {
+ canCompare = true
+ ctx.Data["BaseRepo"] = repo.BaseRepo
+ ctx.Repo.PullRequest.BaseRepo = repo.BaseRepo
+ ctx.Repo.PullRequest.Allowed = canPush
+ ctx.Repo.PullRequest.HeadInfo = ctx.Repo.Owner.Name + ":" + ctx.Repo.BranchName
+ } else if repo.AllowsPulls() {
+ // Or, this is repository accepts pull requests between branches.
+ canCompare = true
+ ctx.Data["BaseRepo"] = repo
+ ctx.Repo.PullRequest.BaseRepo = repo
+ ctx.Repo.PullRequest.Allowed = canPush
+ ctx.Repo.PullRequest.SameRepo = true
+ ctx.Repo.PullRequest.HeadInfo = ctx.Repo.BranchName
+ }
+ ctx.Data["CanCompareOrPull"] = canCompare
+ ctx.Data["PullRequestCtx"] = ctx.Repo.PullRequest
+
+ if ctx.Query("go-get") == "1" {
+ ctx.Data["GoGetImport"] = ComposeGoGetImport(owner.Name, repo.Name)
+ prefix := setting.AppURL + path.Join(owner.Name, repo.Name, "src", "branch", ctx.Repo.BranchName)
+ ctx.Data["GoDocDirectory"] = prefix + "{/dir}"
+ ctx.Data["GoDocFile"] = prefix + "{/dir}/{file}#L{line}"
+ }
+ next.ServeHTTP(w, req)
+ })
}
}
// RepoRef handles repository reference names when the ref name is not
// explicitly given
-func RepoRef() macaron.Handler {
+func RepoRef() func(http.Handler) http.Handler {
// since no ref name is explicitly specified, ok to just use branch
return RepoRefByType(RepoRefBranch)
}
// RepoRefByType handles repository reference name for a specific type
// of repository reference
-func RepoRefByType(refType RepoRefType) macaron.Handler {
- return func(ctx *Context) {
- // Empty repository does not have reference information.
- if ctx.Repo.Repository.IsEmpty {
- return
- }
-
- var (
- refName string
- err error
- )
-
- if ctx.Repo.GitRepo == nil {
- repoPath := models.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
- ctx.Repo.GitRepo, err = git.OpenRepository(repoPath)
- if err != nil {
- ctx.ServerError("RepoRef Invalid repo "+repoPath, err)
+func RepoRefByType(refType RepoRefType) func(http.Handler) http.Handler {
+ return func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ ctx := GetContext(req)
+ // Empty repository does not have reference information.
+ if ctx.Repo.Repository.IsEmpty {
return
}
- // We opened it, we should close it
- defer func() {
- // If it's been set to nil then assume someone else has closed it.
- if ctx.Repo.GitRepo != nil {
- ctx.Repo.GitRepo.Close()
- }
- }()
- }
- // Get default branch.
- if len(ctx.Params("*")) == 0 {
- refName = ctx.Repo.Repository.DefaultBranch
- ctx.Repo.BranchName = refName
- if !ctx.Repo.GitRepo.IsBranchExist(refName) {
- brs, err := ctx.Repo.GitRepo.GetBranches()
+ var (
+ refName string
+ err error
+ )
+
+ if ctx.Repo.GitRepo == nil {
+ repoPath := models.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
+ ctx.Repo.GitRepo, err = git.OpenRepository(repoPath)
if err != nil {
- ctx.ServerError("GetBranches", err)
- return
- } else if len(brs) == 0 {
- err = fmt.Errorf("No branches in non-empty repository %s",
- ctx.Repo.GitRepo.Path)
- ctx.ServerError("GetBranches", err)
+ ctx.ServerError("RepoRef Invalid repo "+repoPath, err)
return
}
- refName = brs[0]
- }
- ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName)
- if err != nil {
- ctx.ServerError("GetBranchCommit", err)
- return
+ // We opened it, we should close it
+ defer func() {
+ // If it's been set to nil then assume someone else has closed it.
+ if ctx.Repo.GitRepo != nil {
+ ctx.Repo.GitRepo.Close()
+ }
+ }()
}
- ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
- ctx.Repo.IsViewBranch = true
-
- } else {
- refName = getRefName(ctx, refType)
- ctx.Repo.BranchName = refName
- if refType.RefTypeIncludesBranches() && ctx.Repo.GitRepo.IsBranchExist(refName) {
- ctx.Repo.IsViewBranch = true
+ // Get default branch.
+ if len(ctx.Params("*")) == 0 {
+ refName = ctx.Repo.Repository.DefaultBranch
+ ctx.Repo.BranchName = refName
+ if !ctx.Repo.GitRepo.IsBranchExist(refName) {
+ brs, err := ctx.Repo.GitRepo.GetBranches()
+ if err != nil {
+ ctx.ServerError("GetBranches", err)
+ return
+ } else if len(brs) == 0 {
+ err = fmt.Errorf("No branches in non-empty repository %s",
+ ctx.Repo.GitRepo.Path)
+ ctx.ServerError("GetBranches", err)
+ return
+ }
+ refName = brs[0]
+ }
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName)
if err != nil {
ctx.ServerError("GetBranchCommit", err)
return
}
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
+ ctx.Repo.IsViewBranch = true
- } else if refType.RefTypeIncludesTags() && ctx.Repo.GitRepo.IsTagExist(refName) {
- ctx.Repo.IsViewTag = true
- ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetTagCommit(refName)
- if err != nil {
- ctx.ServerError("GetTagCommit", err)
+ } else {
+ refName = getRefName(ctx, refType)
+ ctx.Repo.BranchName = refName
+ if refType.RefTypeIncludesBranches() && ctx.Repo.GitRepo.IsBranchExist(refName) {
+ ctx.Repo.IsViewBranch = true
+
+ ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName)
+ if err != nil {
+ ctx.ServerError("GetBranchCommit", err)
+ return
+ }
+ ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
+
+ } else if refType.RefTypeIncludesTags() && ctx.Repo.GitRepo.IsTagExist(refName) {
+ ctx.Repo.IsViewTag = true
+ ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetTagCommit(refName)
+ if err != nil {
+ ctx.ServerError("GetTagCommit", err)
+ return
+ }
+ ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
+ } else if len(refName) >= 7 && len(refName) <= 40 {
+ ctx.Repo.IsViewCommit = true
+ ctx.Repo.CommitID = refName
+
+ ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName)
+ if err != nil {
+ ctx.NotFound("GetCommit", err)
+ return
+ }
+ // If short commit ID add canonical link header
+ if len(refName) < 40 {
+ ctx.Header().Set("Link", fmt.Sprintf("<%s>; rel=\"canonical\"",
+ util.URLJoin(setting.AppURL, strings.Replace(ctx.Req.URL.RequestURI(), refName, ctx.Repo.Commit.ID.String(), 1))))
+ }
+ } else {
+ ctx.NotFound("RepoRef invalid repo", fmt.Errorf("branch or tag not exist: %s", refName))
return
}
- ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
- } else if len(refName) >= 7 && len(refName) <= 40 {
- ctx.Repo.IsViewCommit = true
- ctx.Repo.CommitID = refName
- ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName)
- if err != nil {
- ctx.NotFound("GetCommit", err)
+ if refType == RepoRefLegacy {
+ // redirect from old URL scheme to new URL scheme
+ ctx.Redirect(path.Join(
+ setting.AppSubURL,
+ strings.TrimSuffix(ctx.Req.URL.Path, ctx.Params("*")),
+ ctx.Repo.BranchNameSubURL(),
+ ctx.Repo.TreePath))
return
}
- // If short commit ID add canonical link header
- if len(refName) < 40 {
- ctx.Header().Set("Link", fmt.Sprintf("<%s>; rel=\"canonical\"",
- util.URLJoin(setting.AppURL, strings.Replace(ctx.Req.URL.RequestURI(), refName, ctx.Repo.Commit.ID.String(), 1))))
- }
- } else {
- ctx.NotFound("RepoRef invalid repo", fmt.Errorf("branch or tag not exist: %s", refName))
- return
}
- if refType == RepoRefLegacy {
- // redirect from old URL scheme to new URL scheme
- ctx.Redirect(path.Join(
- setting.AppSubURL,
- strings.TrimSuffix(ctx.Req.URL.Path, ctx.Params("*")),
- ctx.Repo.BranchNameSubURL(),
- ctx.Repo.TreePath))
+ ctx.Data["BranchName"] = ctx.Repo.BranchName
+ ctx.Data["BranchNameSubURL"] = ctx.Repo.BranchNameSubURL()
+ ctx.Data["CommitID"] = ctx.Repo.CommitID
+ ctx.Data["TreePath"] = ctx.Repo.TreePath
+ ctx.Data["IsViewBranch"] = ctx.Repo.IsViewBranch
+ ctx.Data["IsViewTag"] = ctx.Repo.IsViewTag
+ ctx.Data["IsViewCommit"] = ctx.Repo.IsViewCommit
+ ctx.Data["CanCreateBranch"] = ctx.Repo.CanCreateBranch()
+
+ ctx.Repo.CommitsCount, err = ctx.Repo.GetCommitsCount()
+ if err != nil {
+ ctx.ServerError("GetCommitsCount", err)
return
}
- }
+ ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
- ctx.Data["BranchName"] = ctx.Repo.BranchName
- ctx.Data["BranchNameSubURL"] = ctx.Repo.BranchNameSubURL()
- ctx.Data["CommitID"] = ctx.Repo.CommitID
- ctx.Data["TreePath"] = ctx.Repo.TreePath
- ctx.Data["IsViewBranch"] = ctx.Repo.IsViewBranch
- ctx.Data["IsViewTag"] = ctx.Repo.IsViewTag
- ctx.Data["IsViewCommit"] = ctx.Repo.IsViewCommit
- ctx.Data["CanCreateBranch"] = ctx.Repo.CanCreateBranch()
-
- ctx.Repo.CommitsCount, err = ctx.Repo.GetCommitsCount()
- if err != nil {
- ctx.ServerError("GetCommitsCount", err)
- return
- }
- ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
-
- ctx.Next()
+ next.ServeHTTP(w, req)
+ })
}
}
// GitHookService checks if repository Git hooks service has been enabled.
-func GitHookService() macaron.Handler {
+func GitHookService() func(ctx *Context) {
return func(ctx *Context) {
if !ctx.User.CanEditGitHook() {
ctx.NotFound("GitHookService", nil)
}
// UnitTypes returns a macaron middleware to set unit types to context variables.
-func UnitTypes() macaron.Handler {
+func UnitTypes() func(ctx *Context) {
return func(ctx *Context) {
ctx.Data["UnitTypeCode"] = models.UnitTypeCode
ctx.Data["UnitTypeIssues"] = models.UnitTypeIssues
http.ResponseWriter
Flush()
Status() int
+ Before(func(ResponseWriter))
}
var (
// Response represents a response
type Response struct {
http.ResponseWriter
- status int
+ status int
+ befores []func(ResponseWriter)
+ beforeExecuted bool
}
// Write writes bytes to HTTP endpoint
func (r *Response) Write(bs []byte) (int, error) {
+ if !r.beforeExecuted {
+ for _, before := range r.befores {
+ before(r)
+ }
+ r.beforeExecuted = true
+ }
size, err := r.ResponseWriter.Write(bs)
if err != nil {
return 0, err
// WriteHeader write status code
func (r *Response) WriteHeader(statusCode int) {
+ if !r.beforeExecuted {
+ for _, before := range r.befores {
+ before(r)
+ }
+ r.beforeExecuted = true
+ }
r.status = statusCode
r.ResponseWriter.WriteHeader(statusCode)
}
return r.status
}
+// Before allows for a function to be called before the ResponseWriter has been written to. This is
+// useful for setting headers or any other operations that must happen before a response has been written.
+func (r *Response) Before(f func(ResponseWriter)) {
+ r.befores = append(r.befores, f)
+}
+
// NewResponse creates a response
func NewResponse(resp http.ResponseWriter) *Response {
if v, ok := resp.(*Response); ok {
return v
}
- return &Response{resp, 0}
+ return &Response{
+ ResponseWriter: resp,
+ status: 0,
+ befores: make([]func(ResponseWriter), 0),
+ }
}
--- /dev/null
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package context
+
+import (
+ "crypto/aes"
+ "crypto/cipher"
+ "crypto/rand"
+ "crypto/sha256"
+ "encoding/base64"
+ "errors"
+ "io"
+)
+
+// NewSecret creates a new secret
+func NewSecret() (string, error) {
+ return NewSecretWithLength(32)
+}
+
+// NewSecretWithLength creates a new secret for a given length
+func NewSecretWithLength(length int64) (string, error) {
+ return randomString(length)
+}
+
+func randomBytes(len int64) ([]byte, error) {
+ b := make([]byte, len)
+ if _, err := rand.Read(b); err != nil {
+ return nil, err
+ }
+ return b, nil
+}
+
+func randomString(len int64) (string, error) {
+ b, err := randomBytes(len)
+ return base64.URLEncoding.EncodeToString(b), err
+}
+
+// AesEncrypt encrypts text and given key with AES.
+func AesEncrypt(key, text []byte) ([]byte, error) {
+ block, err := aes.NewCipher(key)
+ if err != nil {
+ return nil, err
+ }
+ b := base64.StdEncoding.EncodeToString(text)
+ ciphertext := make([]byte, aes.BlockSize+len(b))
+ iv := ciphertext[:aes.BlockSize]
+ if _, err := io.ReadFull(rand.Reader, iv); err != nil {
+ return nil, err
+ }
+ cfb := cipher.NewCFBEncrypter(block, iv)
+ cfb.XORKeyStream(ciphertext[aes.BlockSize:], []byte(b))
+ return ciphertext, nil
+}
+
+// AesDecrypt decrypts text and given key with AES.
+func AesDecrypt(key, text []byte) ([]byte, error) {
+ block, err := aes.NewCipher(key)
+ if err != nil {
+ return nil, err
+ }
+ if len(text) < aes.BlockSize {
+ return nil, errors.New("ciphertext too short")
+ }
+ iv := text[:aes.BlockSize]
+ text = text[aes.BlockSize:]
+ cfb := cipher.NewCFBDecrypter(block, iv)
+ cfb.XORKeyStream(text, text)
+ data, err := base64.StdEncoding.DecodeString(string(text))
+ if err != nil {
+ return nil, err
+ }
+ return data, nil
+}
+
+// EncryptSecret encrypts a string with given key into a hex string
+func EncryptSecret(key string, str string) (string, error) {
+ keyHash := sha256.Sum256([]byte(key))
+ plaintext := []byte(str)
+ ciphertext, err := AesEncrypt(keyHash[:], plaintext)
+ if err != nil {
+ return "", err
+ }
+ return base64.StdEncoding.EncodeToString(ciphertext), nil
+}
+
+// DecryptSecret decrypts a previously encrypted hex string
+func DecryptSecret(key string, cipherhex string) (string, error) {
+ keyHash := sha256.Sum256([]byte(key))
+ ciphertext, err := base64.StdEncoding.DecodeString(cipherhex)
+ if err != nil {
+ return "", err
+ }
+ plaintext, err := AesDecrypt(keyHash[:], ciphertext)
+ if err != nil {
+ return "", err
+ }
+ return string(plaintext), nil
+}
--- /dev/null
+// Copyright 2012 Google Inc. All Rights Reserved.
+// Copyright 2014 The Macaron Authors
+// Copyright 2020 The Gitea Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package context
+
+import (
+ "bytes"
+ "crypto/hmac"
+ "crypto/sha1"
+ "crypto/subtle"
+ "encoding/base64"
+ "fmt"
+ "strconv"
+ "strings"
+ "time"
+)
+
+// Timeout represents the duration that XSRF tokens are valid.
+// It is exported so clients may set cookie timeouts that match generated tokens.
+const Timeout = 24 * time.Hour
+
+// clean sanitizes a string for inclusion in a token by replacing all ":"s.
+func clean(s string) string {
+ return strings.ReplaceAll(s, ":", "_")
+}
+
+// GenerateToken returns a URL-safe secure XSRF token that expires in 24 hours.
+//
+// key is a secret key for your application.
+// userID is a unique identifier for the user.
+// actionID is the action the user is taking (e.g. POSTing to a particular path).
+func GenerateToken(key, userID, actionID string) string {
+ return generateTokenAtTime(key, userID, actionID, time.Now())
+}
+
+// generateTokenAtTime is like Generate, but returns a token that expires 24 hours from now.
+func generateTokenAtTime(key, userID, actionID string, now time.Time) string {
+ h := hmac.New(sha1.New, []byte(key))
+ fmt.Fprintf(h, "%s:%s:%d", clean(userID), clean(actionID), now.UnixNano())
+ tok := fmt.Sprintf("%s:%d", h.Sum(nil), now.UnixNano())
+ return base64.RawURLEncoding.EncodeToString([]byte(tok))
+}
+
+// ValidToken returns true if token is a valid, unexpired token returned by Generate.
+func ValidToken(token, key, userID, actionID string) bool {
+ return validTokenAtTime(token, key, userID, actionID, time.Now())
+}
+
+// validTokenAtTime is like Valid, but it uses now to check if the token is expired.
+func validTokenAtTime(token, key, userID, actionID string, now time.Time) bool {
+ // Decode the token.
+ data, err := base64.RawURLEncoding.DecodeString(token)
+ if err != nil {
+ return false
+ }
+
+ // Extract the issue time of the token.
+ sep := bytes.LastIndex(data, []byte{':'})
+ if sep < 0 {
+ return false
+ }
+ nanos, err := strconv.ParseInt(string(data[sep+1:]), 10, 64)
+ if err != nil {
+ return false
+ }
+ issueTime := time.Unix(0, nanos)
+
+ // Check that the token is not expired.
+ if now.Sub(issueTime) >= Timeout {
+ return false
+ }
+
+ // Check that the token is not from the future.
+ // Allow 1 minute grace period in case the token is being verified on a
+ // machine whose clock is behind the machine that issued the token.
+ if issueTime.After(now.Add(1 * time.Minute)) {
+ return false
+ }
+
+ expected := generateTokenAtTime(key, userID, actionID, issueTime)
+
+ // Check that the token matches the expected value.
+ // Use constant time comparison to avoid timing attacks.
+ return subtle.ConstantTimeCompare([]byte(token), []byte(expected)) == 1
+}
--- /dev/null
+// Copyright 2012 Google Inc. All Rights Reserved.
+// Copyright 2014 The Macaron Authors
+// Copyright 2020 The Gitea Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package context
+
+import (
+ "encoding/base64"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+)
+
+const (
+ key = "quay"
+ userID = "12345678"
+ actionID = "POST /form"
+)
+
+var (
+ now = time.Now()
+ oneMinuteFromNow = now.Add(1 * time.Minute)
+)
+
+func Test_ValidToken(t *testing.T) {
+ t.Run("Validate token", func(t *testing.T) {
+ tok := generateTokenAtTime(key, userID, actionID, now)
+ assert.True(t, validTokenAtTime(tok, key, userID, actionID, oneMinuteFromNow))
+ assert.True(t, validTokenAtTime(tok, key, userID, actionID, now.Add(Timeout-1*time.Nanosecond)))
+ assert.True(t, validTokenAtTime(tok, key, userID, actionID, now.Add(-1*time.Minute)))
+ })
+}
+
+// Test_SeparatorReplacement tests that separators are being correctly substituted
+func Test_SeparatorReplacement(t *testing.T) {
+ t.Run("Test two separator replacements", func(t *testing.T) {
+ assert.NotEqual(t, generateTokenAtTime("foo:bar", "baz", "wah", now),
+ generateTokenAtTime("foo", "bar:baz", "wah", now))
+ })
+}
+
+func Test_InvalidToken(t *testing.T) {
+ t.Run("Test invalid tokens", func(t *testing.T) {
+ invalidTokenTests := []struct {
+ name, key, userID, actionID string
+ t time.Time
+ }{
+ {"Bad key", "foobar", userID, actionID, oneMinuteFromNow},
+ {"Bad userID", key, "foobar", actionID, oneMinuteFromNow},
+ {"Bad actionID", key, userID, "foobar", oneMinuteFromNow},
+ {"Expired", key, userID, actionID, now.Add(Timeout)},
+ {"More than 1 minute from the future", key, userID, actionID, now.Add(-1*time.Nanosecond - 1*time.Minute)},
+ }
+
+ tok := generateTokenAtTime(key, userID, actionID, now)
+ for _, itt := range invalidTokenTests {
+ assert.False(t, validTokenAtTime(tok, itt.key, itt.userID, itt.actionID, itt.t))
+ }
+ })
+}
+
+// Test_ValidateBadData primarily tests that no unexpected panics are triggered during parsing
+func Test_ValidateBadData(t *testing.T) {
+ t.Run("Validate bad data", func(t *testing.T) {
+ badDataTests := []struct {
+ name, tok string
+ }{
+ {"Invalid Base64", "ASDab24(@)$*=="},
+ {"No delimiter", base64.URLEncoding.EncodeToString([]byte("foobar12345678"))},
+ {"Invalid time", base64.URLEncoding.EncodeToString([]byte("foobar:foobar"))},
+ }
+
+ for _, bdt := range badDataTests {
+ assert.False(t, validTokenAtTime(bdt.tok, key, userID, actionID, oneMinuteFromNow))
+ }
+ })
+}
--- /dev/null
+// Copyright 2014 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package forms
+
+import (
+ "net/http"
+
+ "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/middlewares"
+
+ "gitea.com/go-chi/binding"
+)
+
+// AdminCreateUserForm form for admin to create user
+type AdminCreateUserForm struct {
+ LoginType string `binding:"Required"`
+ LoginName string
+ UserName string `binding:"Required;AlphaDashDot;MaxSize(40)"`
+ Email string `binding:"Required;Email;MaxSize(254)"`
+ Password string `binding:"MaxSize(255)"`
+ SendNotify bool
+ MustChangePassword bool
+}
+
+// Validate validates form fields
+func (f *AdminCreateUserForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// AdminEditUserForm form for admin to create user
+type AdminEditUserForm struct {
+ LoginType string `binding:"Required"`
+ UserName string `binding:"AlphaDashDot;MaxSize(40)"`
+ LoginName string
+ FullName string `binding:"MaxSize(100)"`
+ Email string `binding:"Required;Email;MaxSize(254)"`
+ Password string `binding:"MaxSize(255)"`
+ Website string `binding:"ValidUrl;MaxSize(255)"`
+ Location string `binding:"MaxSize(50)"`
+ MaxRepoCreation int
+ Active bool
+ Admin bool
+ Restricted bool
+ AllowGitHook bool
+ AllowImportLocal bool
+ AllowCreateOrganization bool
+ ProhibitLogin bool
+ Reset2FA bool `form:"reset_2fa"`
+}
+
+// Validate validates form fields
+func (f *AdminEditUserForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// AdminDashboardForm form for admin dashboard operations
+type AdminDashboardForm struct {
+ Op string `binding:"required"`
+ From string
+}
+
+// Validate validates form fields
+func (f *AdminDashboardForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
--- /dev/null
+// Copyright 2014 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package forms
+
+import (
+ "net/http"
+
+ "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/middlewares"
+
+ "gitea.com/go-chi/binding"
+)
+
+// AuthenticationForm form for authentication
+type AuthenticationForm struct {
+ ID int64
+ Type int `binding:"Range(2,7)"`
+ Name string `binding:"Required;MaxSize(30)"`
+ Host string
+ Port int
+ BindDN string
+ BindPassword string
+ UserBase string
+ UserDN string
+ AttributeUsername string
+ AttributeName string
+ AttributeSurname string
+ AttributeMail string
+ AttributeSSHPublicKey string
+ AttributesInBind bool
+ UsePagedSearch bool
+ SearchPageSize int
+ Filter string
+ AdminFilter string
+ GroupsEnabled bool
+ GroupDN string
+ GroupFilter string
+ GroupMemberUID string
+ UserUID string
+ RestrictedFilter string
+ AllowDeactivateAll bool
+ IsActive bool
+ IsSyncEnabled bool
+ SMTPAuth string
+ SMTPHost string
+ SMTPPort int
+ AllowedDomains string
+ SecurityProtocol int `binding:"Range(0,2)"`
+ TLS bool
+ SkipVerify bool
+ PAMServiceName string
+ Oauth2Provider string
+ Oauth2Key string
+ Oauth2Secret string
+ OpenIDConnectAutoDiscoveryURL string
+ Oauth2UseCustomURL bool
+ Oauth2TokenURL string
+ Oauth2AuthURL string
+ Oauth2ProfileURL string
+ Oauth2EmailURL string
+ Oauth2IconURL string
+ SSPIAutoCreateUsers bool
+ SSPIAutoActivateUsers bool
+ SSPIStripDomainNames bool
+ SSPISeparatorReplacement string `binding:"AlphaDashDot;MaxSize(5)"`
+ SSPIDefaultLanguage string
+}
+
+// Validate validates fields
+func (f *AuthenticationForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
--- /dev/null
+// Copyright 2014 The Gogs Authors. All rights reserved.
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package forms
+
+import (
+ "net/http"
+
+ "code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/middlewares"
+ "code.gitea.io/gitea/modules/structs"
+
+ "gitea.com/go-chi/binding"
+)
+
+// ________ .__ __ .__
+// \_____ \_______ _________ ____ |__|____________ _/ |_|__| ____ ____
+// / | \_ __ \/ ___\__ \ / \| \___ /\__ \\ __\ |/ _ \ / \
+// / | \ | \/ /_/ > __ \| | \ |/ / / __ \| | | ( <_> ) | \
+// \_______ /__| \___ (____ /___| /__/_____ \(____ /__| |__|\____/|___| /
+// \/ /_____/ \/ \/ \/ \/ \/
+
+// CreateOrgForm form for creating organization
+type CreateOrgForm struct {
+ OrgName string `binding:"Required;AlphaDashDot;MaxSize(40)" locale:"org.org_name_holder"`
+ Visibility structs.VisibleType
+ RepoAdminChangeTeamAccess bool
+}
+
+// Validate validates the fields
+func (f *CreateOrgForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// UpdateOrgSettingForm form for updating organization settings
+type UpdateOrgSettingForm struct {
+ Name string `binding:"Required;AlphaDashDot;MaxSize(40)" locale:"org.org_name_holder"`
+ FullName string `binding:"MaxSize(100)"`
+ Description string `binding:"MaxSize(255)"`
+ Website string `binding:"ValidUrl;MaxSize(255)"`
+ Location string `binding:"MaxSize(50)"`
+ Visibility structs.VisibleType
+ MaxRepoCreation int
+ RepoAdminChangeTeamAccess bool
+}
+
+// Validate validates the fields
+func (f *UpdateOrgSettingForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// ___________
+// \__ ___/___ _____ _____
+// | |_/ __ \\__ \ / \
+// | |\ ___/ / __ \| Y Y \
+// |____| \___ >____ /__|_| /
+// \/ \/ \/
+
+// CreateTeamForm form for creating team
+type CreateTeamForm struct {
+ TeamName string `binding:"Required;AlphaDashDot;MaxSize(30)"`
+ Description string `binding:"MaxSize(255)"`
+ Permission string
+ Units []models.UnitType
+ RepoAccess string
+ CanCreateOrgRepo bool
+}
+
+// Validate validates the fields
+func (f *CreateTeamForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
--- /dev/null
+// Copyright 2017 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package forms
+
+import (
+ "net/http"
+
+ "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/middlewares"
+
+ "gitea.com/go-chi/binding"
+)
+
+// NewBranchForm form for creating a new branch
+type NewBranchForm struct {
+ NewBranchName string `binding:"Required;MaxSize(100);GitRefName"`
+}
+
+// Validate validates the fields
+func (f *NewBranchForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
--- /dev/null
+// Copyright 2014 The Gogs Authors. All rights reserved.
+// Copyright 2017 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package forms
+
+import (
+ "net/http"
+ "net/url"
+ "strings"
+
+ "code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/middlewares"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/routers/utils"
+
+ "gitea.com/go-chi/binding"
+)
+
+// _______________________________________ _________.______________________ _______________.___.
+// \______ \_ _____/\______ \_____ \ / _____/| \__ ___/\_____ \\______ \__ | |
+// | _/| __)_ | ___// | \ \_____ \ | | | | / | \| _// | |
+// | | \| \ | | / | \/ \| | | | / | \ | \\____ |
+// |____|_ /_______ / |____| \_______ /_______ /|___| |____| \_______ /____|_ // ______|
+// \/ \/ \/ \/ \/ \/ \/
+
+// CreateRepoForm form for creating repository
+type CreateRepoForm struct {
+ UID int64 `binding:"Required"`
+ RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"`
+ Private bool
+ Description string `binding:"MaxSize(255)"`
+ DefaultBranch string `binding:"GitRefName;MaxSize(100)"`
+ AutoInit bool
+ Gitignores string
+ IssueLabels string
+ License string
+ Readme string
+ Template bool
+
+ RepoTemplate int64
+ GitContent bool
+ Topics bool
+ GitHooks bool
+ Webhooks bool
+ Avatar bool
+ Labels bool
+ TrustModel string
+}
+
+// Validate validates the fields
+func (f *CreateRepoForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// MigrateRepoForm form for migrating repository
+// this is used to interact with web ui
+type MigrateRepoForm struct {
+ // required: true
+ CloneAddr string `json:"clone_addr" binding:"Required"`
+ Service structs.GitServiceType `json:"service"`
+ AuthUsername string `json:"auth_username"`
+ AuthPassword string `json:"auth_password"`
+ AuthToken string `json:"auth_token"`
+ // required: true
+ UID int64 `json:"uid" binding:"Required"`
+ // required: true
+ RepoName string `json:"repo_name" binding:"Required;AlphaDashDot;MaxSize(100)"`
+ Mirror bool `json:"mirror"`
+ Private bool `json:"private"`
+ Description string `json:"description" binding:"MaxSize(255)"`
+ Wiki bool `json:"wiki"`
+ Milestones bool `json:"milestones"`
+ Labels bool `json:"labels"`
+ Issues bool `json:"issues"`
+ PullRequests bool `json:"pull_requests"`
+ Releases bool `json:"releases"`
+ MirrorInterval string `json:"mirror_interval"`
+}
+
+// Validate validates the fields
+func (f *MigrateRepoForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// ParseRemoteAddr checks if given remote address is valid,
+// and returns composed URL with needed username and password.
+// It also checks if given user has permission when remote address
+// is actually a local path.
+func ParseRemoteAddr(remoteAddr, authUsername, authPassword string, user *models.User) (string, error) {
+ remoteAddr = strings.TrimSpace(remoteAddr)
+ // Remote address can be HTTP/HTTPS/Git URL or local path.
+ if strings.HasPrefix(remoteAddr, "http://") ||
+ strings.HasPrefix(remoteAddr, "https://") ||
+ strings.HasPrefix(remoteAddr, "git://") {
+ u, err := url.Parse(remoteAddr)
+ if err != nil {
+ return "", models.ErrInvalidCloneAddr{IsURLError: true}
+ }
+ if len(authUsername)+len(authPassword) > 0 {
+ u.User = url.UserPassword(authUsername, authPassword)
+ }
+ remoteAddr = u.String()
+ if u.Scheme == "git" && u.Port() != "" && (strings.Contains(remoteAddr, "%0d") || strings.Contains(remoteAddr, "%0a")) {
+ return "", models.ErrInvalidCloneAddr{IsURLError: true}
+ }
+ } else if !user.CanImportLocal() {
+ return "", models.ErrInvalidCloneAddr{IsPermissionDenied: true}
+ } else {
+ isDir, err := util.IsDir(remoteAddr)
+ if err != nil {
+ log.Error("Unable to check if %s is a directory: %v", remoteAddr, err)
+ return "", err
+ }
+ if !isDir {
+ return "", models.ErrInvalidCloneAddr{IsInvalidPath: true}
+ }
+ }
+
+ return remoteAddr, nil
+}
+
+// RepoSettingForm form for changing repository settings
+type RepoSettingForm struct {
+ RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"`
+ Description string `binding:"MaxSize(255)"`
+ Website string `binding:"ValidUrl;MaxSize(255)"`
+ Interval string
+ MirrorAddress string
+ MirrorUsername string
+ MirrorPassword string
+ Private bool
+ Template bool
+ EnablePrune bool
+
+ // Advanced settings
+ EnableWiki bool
+ EnableExternalWiki bool
+ ExternalWikiURL string
+ EnableIssues bool
+ EnableExternalTracker bool
+ ExternalTrackerURL string
+ TrackerURLFormat string
+ TrackerIssueStyle string
+ EnableProjects bool
+ EnablePulls bool
+ PullsIgnoreWhitespace bool
+ PullsAllowMerge bool
+ PullsAllowRebase bool
+ PullsAllowRebaseMerge bool
+ PullsAllowSquash bool
+ EnableTimetracker bool
+ AllowOnlyContributorsToTrackTime bool
+ EnableIssueDependencies bool
+ IsArchived bool
+
+ // Signing Settings
+ TrustModel string
+
+ // Admin settings
+ EnableHealthCheck bool
+ EnableCloseIssuesViaCommitInAnyBranch bool
+}
+
+// Validate validates the fields
+func (f *RepoSettingForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// __________ .__
+// \______ \____________ ____ ____ | |__
+// | | _/\_ __ \__ \ / \_/ ___\| | \
+// | | \ | | \// __ \| | \ \___| Y \
+// |______ / |__| (____ /___| /\___ >___| /
+// \/ \/ \/ \/ \/
+
+// ProtectBranchForm form for changing protected branch settings
+type ProtectBranchForm struct {
+ Protected bool
+ EnablePush string
+ WhitelistUsers string
+ WhitelistTeams string
+ WhitelistDeployKeys bool
+ EnableMergeWhitelist bool
+ MergeWhitelistUsers string
+ MergeWhitelistTeams string
+ EnableStatusCheck bool
+ StatusCheckContexts []string
+ RequiredApprovals int64
+ EnableApprovalsWhitelist bool
+ ApprovalsWhitelistUsers string
+ ApprovalsWhitelistTeams string
+ BlockOnRejectedReviews bool
+ BlockOnOfficialReviewRequests bool
+ BlockOnOutdatedBranch bool
+ DismissStaleApprovals bool
+ RequireSignedCommits bool
+ ProtectedFilePatterns string
+}
+
+// Validate validates the fields
+func (f *ProtectBranchForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// __ __ ___. .__ .__ __
+// / \ / \ ____\_ |__ | |__ | |__ ____ | | __
+// \ \/\/ // __ \| __ \| | \| | \ / _ \| |/ /
+// \ /\ ___/| \_\ \ Y \ Y ( <_> ) <
+// \__/\ / \___ >___ /___| /___| /\____/|__|_ \
+// \/ \/ \/ \/ \/ \/
+
+// WebhookForm form for changing web hook
+type WebhookForm struct {
+ Events string
+ Create bool
+ Delete bool
+ Fork bool
+ Issues bool
+ IssueAssign bool
+ IssueLabel bool
+ IssueMilestone bool
+ IssueComment bool
+ Release bool
+ Push bool
+ PullRequest bool
+ PullRequestAssign bool
+ PullRequestLabel bool
+ PullRequestMilestone bool
+ PullRequestComment bool
+ PullRequestReview bool
+ PullRequestSync bool
+ Repository bool
+ Active bool
+ BranchFilter string `binding:"GlobPattern"`
+}
+
+// PushOnly if the hook will be triggered when push
+func (f WebhookForm) PushOnly() bool {
+ return f.Events == "push_only"
+}
+
+// SendEverything if the hook will be triggered any event
+func (f WebhookForm) SendEverything() bool {
+ return f.Events == "send_everything"
+}
+
+// ChooseEvents if the hook will be triggered choose events
+func (f WebhookForm) ChooseEvents() bool {
+ return f.Events == "choose_events"
+}
+
+// NewWebhookForm form for creating web hook
+type NewWebhookForm struct {
+ PayloadURL string `binding:"Required;ValidUrl"`
+ HTTPMethod string `binding:"Required;In(POST,GET)"`
+ ContentType int `binding:"Required"`
+ Secret string
+ WebhookForm
+}
+
+// Validate validates the fields
+func (f *NewWebhookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// NewGogshookForm form for creating gogs hook
+type NewGogshookForm struct {
+ PayloadURL string `binding:"Required;ValidUrl"`
+ ContentType int `binding:"Required"`
+ Secret string
+ WebhookForm
+}
+
+// Validate validates the fields
+func (f *NewGogshookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// NewSlackHookForm form for creating slack hook
+type NewSlackHookForm struct {
+ PayloadURL string `binding:"Required;ValidUrl"`
+ Channel string `binding:"Required"`
+ Username string
+ IconURL string
+ Color string
+ WebhookForm
+}
+
+// Validate validates the fields
+func (f *NewSlackHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// HasInvalidChannel validates the channel name is in the right format
+func (f NewSlackHookForm) HasInvalidChannel() bool {
+ return !utils.IsValidSlackChannel(f.Channel)
+}
+
+// NewDiscordHookForm form for creating discord hook
+type NewDiscordHookForm struct {
+ PayloadURL string `binding:"Required;ValidUrl"`
+ Username string
+ IconURL string
+ WebhookForm
+}
+
+// Validate validates the fields
+func (f *NewDiscordHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// NewDingtalkHookForm form for creating dingtalk hook
+type NewDingtalkHookForm struct {
+ PayloadURL string `binding:"Required;ValidUrl"`
+ WebhookForm
+}
+
+// Validate validates the fields
+func (f *NewDingtalkHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// NewTelegramHookForm form for creating telegram hook
+type NewTelegramHookForm struct {
+ BotToken string `binding:"Required"`
+ ChatID string `binding:"Required"`
+ WebhookForm
+}
+
+// Validate validates the fields
+func (f *NewTelegramHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// NewMatrixHookForm form for creating Matrix hook
+type NewMatrixHookForm struct {
+ HomeserverURL string `binding:"Required;ValidUrl"`
+ RoomID string `binding:"Required"`
+ AccessToken string `binding:"Required"`
+ MessageType int
+ WebhookForm
+}
+
+// Validate validates the fields
+func (f *NewMatrixHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// NewMSTeamsHookForm form for creating MS Teams hook
+type NewMSTeamsHookForm struct {
+ PayloadURL string `binding:"Required;ValidUrl"`
+ WebhookForm
+}
+
+// Validate validates the fields
+func (f *NewMSTeamsHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// NewFeishuHookForm form for creating feishu hook
+type NewFeishuHookForm struct {
+ PayloadURL string `binding:"Required;ValidUrl"`
+ WebhookForm
+}
+
+// Validate validates the fields
+func (f *NewFeishuHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// .___
+// | | ______ ________ __ ____
+// | |/ ___// ___/ | \_/ __ \
+// | |\___ \ \___ \| | /\ ___/
+// |___/____ >____ >____/ \___ >
+// \/ \/ \/
+
+// CreateIssueForm form for creating issue
+type CreateIssueForm struct {
+ Title string `binding:"Required;MaxSize(255)"`
+ LabelIDs string `form:"label_ids"`
+ AssigneeIDs string `form:"assignee_ids"`
+ Ref string `form:"ref"`
+ MilestoneID int64
+ ProjectID int64
+ AssigneeID int64
+ Content string
+ Files []string
+}
+
+// Validate validates the fields
+func (f *CreateIssueForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// CreateCommentForm form for creating comment
+type CreateCommentForm struct {
+ Content string
+ Status string `binding:"OmitEmpty;In(reopen,close)"`
+ Files []string
+}
+
+// Validate validates the fields
+func (f *CreateCommentForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// ReactionForm form for adding and removing reaction
+type ReactionForm struct {
+ Content string `binding:"Required"`
+}
+
+// Validate validates the fields
+func (f *ReactionForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// IssueLockForm form for locking an issue
+type IssueLockForm struct {
+ Reason string `binding:"Required"`
+}
+
+// Validate validates the fields
+func (i *IssueLockForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, i, ctx.Locale)
+}
+
+// HasValidReason checks to make sure that the reason submitted in
+// the form matches any of the values in the config
+func (i IssueLockForm) HasValidReason() bool {
+ if strings.TrimSpace(i.Reason) == "" {
+ return true
+ }
+
+ for _, v := range setting.Repository.Issue.LockReasons {
+ if v == i.Reason {
+ return true
+ }
+ }
+
+ return false
+}
+
+// __________ __ __
+// \______ \_______ ____ |__| ____ _____/ |_ ______
+// | ___/\_ __ \/ _ \ | |/ __ \_/ ___\ __\/ ___/
+// | | | | \( <_> ) | \ ___/\ \___| | \___ \
+// |____| |__| \____/\__| |\___ >\___ >__| /____ >
+// \______| \/ \/ \/
+
+// CreateProjectForm form for creating a project
+type CreateProjectForm struct {
+ Title string `binding:"Required;MaxSize(100)"`
+ Content string
+ BoardType models.ProjectBoardType
+}
+
+// UserCreateProjectForm is a from for creating an individual or organization
+// form.
+type UserCreateProjectForm struct {
+ Title string `binding:"Required;MaxSize(100)"`
+ Content string
+ BoardType models.ProjectBoardType
+ UID int64 `binding:"Required"`
+}
+
+// EditProjectBoardTitleForm is a form for editing the title of a project's
+// board
+type EditProjectBoardTitleForm struct {
+ Title string `binding:"Required;MaxSize(100)"`
+}
+
+// _____ .__.__ __
+// / \ |__| | ____ _______/ |_ ____ ____ ____
+// / \ / \| | | _/ __ \ / ___/\ __\/ _ \ / \_/ __ \
+// / Y \ | |_\ ___/ \___ \ | | ( <_> ) | \ ___/
+// \____|__ /__|____/\___ >____ > |__| \____/|___| /\___ >
+// \/ \/ \/ \/ \/
+
+// CreateMilestoneForm form for creating milestone
+type CreateMilestoneForm struct {
+ Title string `binding:"Required;MaxSize(50)"`
+ Content string
+ Deadline string
+}
+
+// Validate validates the fields
+func (f *CreateMilestoneForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// .____ ___. .__
+// | | _____ \_ |__ ____ | |
+// | | \__ \ | __ \_/ __ \| |
+// | |___ / __ \| \_\ \ ___/| |__
+// |_______ (____ /___ /\___ >____/
+// \/ \/ \/ \/
+
+// CreateLabelForm form for creating label
+type CreateLabelForm struct {
+ ID int64
+ Title string `binding:"Required;MaxSize(50)" locale:"repo.issues.label_title"`
+ Description string `binding:"MaxSize(200)" locale:"repo.issues.label_description"`
+ Color string `binding:"Required;Size(7)" locale:"repo.issues.label_color"`
+}
+
+// Validate validates the fields
+func (f *CreateLabelForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// InitializeLabelsForm form for initializing labels
+type InitializeLabelsForm struct {
+ TemplateName string `binding:"Required"`
+}
+
+// Validate validates the fields
+func (f *InitializeLabelsForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// __________ .__ .__ __________ __
+// \______ \__ __| | | | \______ \ ____ ________ __ ____ _______/ |_
+// | ___/ | \ | | | | _// __ \/ ____/ | \_/ __ \ / ___/\ __\
+// | | | | / |_| |__ | | \ ___< <_| | | /\ ___/ \___ \ | |
+// |____| |____/|____/____/ |____|_ /\___ >__ |____/ \___ >____ > |__|
+// \/ \/ |__| \/ \/
+
+// MergePullRequestForm form for merging Pull Request
+// swagger:model MergePullRequestOption
+type MergePullRequestForm struct {
+ // required: true
+ // enum: merge,rebase,rebase-merge,squash
+ Do string `binding:"Required;In(merge,rebase,rebase-merge,squash)"`
+ MergeTitleField string
+ MergeMessageField string
+ ForceMerge *bool `json:"force_merge,omitempty"`
+}
+
+// Validate validates the fields
+func (f *MergePullRequestForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// CodeCommentForm form for adding code comments for PRs
+type CodeCommentForm struct {
+ Origin string `binding:"Required;In(timeline,diff)"`
+ Content string `binding:"Required"`
+ Side string `binding:"Required;In(previous,proposed)"`
+ Line int64
+ TreePath string `form:"path" binding:"Required"`
+ IsReview bool `form:"is_review"`
+ Reply int64 `form:"reply"`
+ LatestCommitID string
+}
+
+// Validate validates the fields
+func (f *CodeCommentForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// SubmitReviewForm for submitting a finished code review
+type SubmitReviewForm struct {
+ Content string
+ Type string `binding:"Required;In(approve,comment,reject)"`
+ CommitID string
+}
+
+// Validate validates the fields
+func (f *SubmitReviewForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// ReviewType will return the corresponding reviewtype for type
+func (f SubmitReviewForm) ReviewType() models.ReviewType {
+ switch f.Type {
+ case "approve":
+ return models.ReviewTypeApprove
+ case "comment":
+ return models.ReviewTypeComment
+ case "reject":
+ return models.ReviewTypeReject
+ default:
+ return models.ReviewTypeUnknown
+ }
+}
+
+// HasEmptyContent checks if the content of the review form is empty.
+func (f SubmitReviewForm) HasEmptyContent() bool {
+ reviewType := f.ReviewType()
+
+ return (reviewType == models.ReviewTypeComment || reviewType == models.ReviewTypeReject) &&
+ len(strings.TrimSpace(f.Content)) == 0
+}
+
+// __________ .__
+// \______ \ ____ | | ____ _____ ______ ____
+// | _// __ \| | _/ __ \\__ \ / ___// __ \
+// | | \ ___/| |_\ ___/ / __ \_\___ \\ ___/
+// |____|_ /\___ >____/\___ >____ /____ >\___ >
+// \/ \/ \/ \/ \/ \/
+
+// NewReleaseForm form for creating release
+type NewReleaseForm struct {
+ TagName string `binding:"Required;GitRefName;MaxSize(255)"`
+ Target string `form:"tag_target" binding:"Required;MaxSize(255)"`
+ Title string `binding:"Required;MaxSize(255)"`
+ Content string
+ Draft string
+ Prerelease bool
+ Files []string
+}
+
+// Validate validates the fields
+func (f *NewReleaseForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// EditReleaseForm form for changing release
+type EditReleaseForm struct {
+ Title string `form:"title" binding:"Required;MaxSize(255)"`
+ Content string `form:"content"`
+ Draft string `form:"draft"`
+ Prerelease bool `form:"prerelease"`
+ Files []string
+}
+
+// Validate validates the fields
+func (f *EditReleaseForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// __ __.__ __ .__
+// / \ / \__| | _|__|
+// \ \/\/ / | |/ / |
+// \ /| | <| |
+// \__/\ / |__|__|_ \__|
+// \/ \/
+
+// NewWikiForm form for creating wiki
+type NewWikiForm struct {
+ Title string `binding:"Required"`
+ Content string `binding:"Required"`
+ Message string
+}
+
+// Validate validates the fields
+// FIXME: use code generation to generate this method.
+func (f *NewWikiForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// ___________ .___.__ __
+// \_ _____/ __| _/|__|/ |_
+// | __)_ / __ | | \ __\
+// | \/ /_/ | | || |
+// /_______ /\____ | |__||__|
+// \/ \/
+
+// EditRepoFileForm form for changing repository file
+type EditRepoFileForm struct {
+ TreePath string `binding:"Required;MaxSize(500)"`
+ Content string
+ CommitSummary string `binding:"MaxSize(100)"`
+ CommitMessage string
+ CommitChoice string `binding:"Required;MaxSize(50)"`
+ NewBranchName string `binding:"GitRefName;MaxSize(100)"`
+ LastCommit string
+}
+
+// Validate validates the fields
+func (f *EditRepoFileForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// EditPreviewDiffForm form for changing preview diff
+type EditPreviewDiffForm struct {
+ Content string
+}
+
+// Validate validates the fields
+func (f *EditPreviewDiffForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// ____ ___ .__ .___
+// | | \______ | | _________ __| _/
+// | | /\____ \| | / _ \__ \ / __ |
+// | | / | |_> > |_( <_> ) __ \_/ /_/ |
+// |______/ | __/|____/\____(____ /\____ |
+// |__| \/ \/
+//
+
+// UploadRepoFileForm form for uploading repository file
+type UploadRepoFileForm struct {
+ TreePath string `binding:"MaxSize(500)"`
+ CommitSummary string `binding:"MaxSize(100)"`
+ CommitMessage string
+ CommitChoice string `binding:"Required;MaxSize(50)"`
+ NewBranchName string `binding:"GitRefName;MaxSize(100)"`
+ Files []string
+}
+
+// Validate validates the fields
+func (f *UploadRepoFileForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// RemoveUploadFileForm form for removing uploaded file
+type RemoveUploadFileForm struct {
+ File string `binding:"Required;MaxSize(50)"`
+}
+
+// Validate validates the fields
+func (f *RemoveUploadFileForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// ________ .__ __
+// \______ \ ____ | | _____/ |_ ____
+// | | \_/ __ \| | _/ __ \ __\/ __ \
+// | ` \ ___/| |_\ ___/| | \ ___/
+// /_______ /\___ >____/\___ >__| \___ >
+// \/ \/ \/ \/
+
+// DeleteRepoFileForm form for deleting repository file
+type DeleteRepoFileForm struct {
+ CommitSummary string `binding:"MaxSize(100)"`
+ CommitMessage string
+ CommitChoice string `binding:"Required;MaxSize(50)"`
+ NewBranchName string `binding:"GitRefName;MaxSize(100)"`
+ LastCommit string
+}
+
+// Validate validates the fields
+func (f *DeleteRepoFileForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// ___________.__ ___________ __
+// \__ ___/|__| _____ ____ \__ ___/___________ ____ | | __ ___________
+// | | | |/ \_/ __ \ | | \_ __ \__ \ _/ ___\| |/ // __ \_ __ \
+// | | | | Y Y \ ___/ | | | | \// __ \\ \___| <\ ___/| | \/
+// |____| |__|__|_| /\___ > |____| |__| (____ /\___ >__|_ \\___ >__|
+// \/ \/ \/ \/ \/ \/
+
+// AddTimeManuallyForm form that adds spent time manually.
+type AddTimeManuallyForm struct {
+ Hours int `binding:"Range(0,1000)"`
+ Minutes int `binding:"Range(0,1000)"`
+}
+
+// Validate validates the fields
+func (f *AddTimeManuallyForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// SaveTopicForm form for save topics for repository
+type SaveTopicForm struct {
+ Topics []string `binding:"topics;Required;"`
+}
+
+// DeadlineForm hold the validation rules for deadlines
+type DeadlineForm struct {
+ DateString string `form:"date" binding:"Required;Size(10)"`
+}
+
+// Validate validates the fields
+func (f *DeadlineForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
--- /dev/null
+// Copyright 2018 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package forms
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/modules/setting"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestSubmitReviewForm_IsEmpty(t *testing.T) {
+
+ cases := []struct {
+ form SubmitReviewForm
+ expected bool
+ }{
+ // Approved PR with a comment shouldn't count as empty
+ {SubmitReviewForm{Type: "approve", Content: "Awesome"}, false},
+
+ // Approved PR without a comment shouldn't count as empty
+ {SubmitReviewForm{Type: "approve", Content: ""}, false},
+
+ // Rejected PR without a comment should count as empty
+ {SubmitReviewForm{Type: "reject", Content: ""}, true},
+
+ // Rejected PR with a comment shouldn't count as empty
+ {SubmitReviewForm{Type: "reject", Content: "Awesome"}, false},
+
+ // Comment review on a PR with a comment shouldn't count as empty
+ {SubmitReviewForm{Type: "comment", Content: "Awesome"}, false},
+
+ // Comment review on a PR without a comment should count as empty
+ {SubmitReviewForm{Type: "comment", Content: ""}, true},
+ }
+
+ for _, v := range cases {
+ assert.Equal(t, v.expected, v.form.HasEmptyContent())
+ }
+}
+
+func TestIssueLock_HasValidReason(t *testing.T) {
+
+ // Init settings
+ _ = setting.Repository
+
+ cases := []struct {
+ form IssueLockForm
+ expected bool
+ }{
+ {IssueLockForm{""}, true}, // an empty reason is accepted
+ {IssueLockForm{"Off-topic"}, true},
+ {IssueLockForm{"Too heated"}, true},
+ {IssueLockForm{"Spam"}, true},
+ {IssueLockForm{"Resolved"}, true},
+
+ {IssueLockForm{"ZZZZ"}, false},
+ {IssueLockForm{"I want to lock this issue"}, false},
+ }
+
+ for _, v := range cases {
+ assert.Equal(t, v.expected, v.form.HasValidReason())
+ }
+}
--- /dev/null
+// Copyright 2014 The Gogs Authors. All rights reserved.
+// Copyright 2018 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package forms
+
+import (
+ "mime/multipart"
+ "net/http"
+ "strings"
+
+ "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/middlewares"
+ "code.gitea.io/gitea/modules/setting"
+
+ "gitea.com/go-chi/binding"
+)
+
+// InstallForm form for installation page
+type InstallForm struct {
+ DbType string `binding:"Required"`
+ DbHost string
+ DbUser string
+ DbPasswd string
+ DbName string
+ SSLMode string
+ Charset string `binding:"Required;In(utf8,utf8mb4)"`
+ DbPath string
+ DbSchema string
+
+ AppName string `binding:"Required" locale:"install.app_name"`
+ RepoRootPath string `binding:"Required"`
+ LFSRootPath string
+ RunUser string `binding:"Required"`
+ Domain string `binding:"Required"`
+ SSHPort int
+ HTTPPort string `binding:"Required"`
+ AppURL string `binding:"Required"`
+ LogRootPath string `binding:"Required"`
+
+ SMTPHost string
+ SMTPFrom string
+ SMTPUser string `binding:"OmitEmpty;MaxSize(254)" locale:"install.mailer_user"`
+ SMTPPasswd string
+ RegisterConfirm bool
+ MailNotify bool
+
+ OfflineMode bool
+ DisableGravatar bool
+ EnableFederatedAvatar bool
+ EnableOpenIDSignIn bool
+ EnableOpenIDSignUp bool
+ DisableRegistration bool
+ AllowOnlyExternalRegistration bool
+ EnableCaptcha bool
+ RequireSignInView bool
+ DefaultKeepEmailPrivate bool
+ DefaultAllowCreateOrganization bool
+ DefaultEnableTimetracking bool
+ NoReplyAddress string
+
+ AdminName string `binding:"OmitEmpty;AlphaDashDot;MaxSize(30)" locale:"install.admin_name"`
+ AdminPasswd string `binding:"OmitEmpty;MaxSize(255)" locale:"install.admin_password"`
+ AdminConfirmPasswd string
+ AdminEmail string `binding:"OmitEmpty;MinSize(3);MaxSize(254);Include(@)" locale:"install.admin_email"`
+}
+
+// Validate validates the fields
+func (f *InstallForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// _____ ____ _________________ ___
+// / _ \ | | \__ ___/ | \
+// / /_\ \| | / | | / ~ \
+// / | \ | / | | \ Y /
+// \____|__ /______/ |____| \___|_ /
+// \/ \/
+
+// RegisterForm form for registering
+type RegisterForm struct {
+ UserName string `binding:"Required;AlphaDashDot;MaxSize(40)"`
+ Email string `binding:"Required;Email;MaxSize(254)"`
+ Password string `binding:"MaxSize(255)"`
+ Retype string
+ GRecaptchaResponse string `form:"g-recaptcha-response"`
+ HcaptchaResponse string `form:"h-captcha-response"`
+}
+
+// Validate validates the fields
+func (f *RegisterForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// IsEmailDomainWhitelisted validates that the email address
+// provided by the user matches what has been configured .
+// If the domain whitelist from the config is empty, it marks the
+// email as whitelisted
+func (f RegisterForm) IsEmailDomainWhitelisted() bool {
+ if len(setting.Service.EmailDomainWhitelist) == 0 {
+ return true
+ }
+
+ n := strings.LastIndex(f.Email, "@")
+ if n <= 0 {
+ return false
+ }
+
+ domain := strings.ToLower(f.Email[n+1:])
+
+ for _, v := range setting.Service.EmailDomainWhitelist {
+ if strings.ToLower(v) == domain {
+ return true
+ }
+ }
+
+ return false
+}
+
+// MustChangePasswordForm form for updating your password after account creation
+// by an admin
+type MustChangePasswordForm struct {
+ Password string `binding:"Required;MaxSize(255)"`
+ Retype string
+}
+
+// Validate validates the fields
+func (f *MustChangePasswordForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// SignInForm form for signing in with user/password
+type SignInForm struct {
+ UserName string `binding:"Required;MaxSize(254)"`
+ // TODO remove required from password for SecondFactorAuthentication
+ Password string `binding:"Required;MaxSize(255)"`
+ Remember bool
+}
+
+// Validate validates the fields
+func (f *SignInForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// AuthorizationForm form for authorizing oauth2 clients
+type AuthorizationForm struct {
+ ResponseType string `binding:"Required;In(code)"`
+ ClientID string `binding:"Required"`
+ RedirectURI string
+ State string
+ Scope string
+ Nonce string
+
+ // PKCE support
+ CodeChallengeMethod string // S256, plain
+ CodeChallenge string
+}
+
+// Validate validates the fields
+func (f *AuthorizationForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// GrantApplicationForm form for authorizing oauth2 clients
+type GrantApplicationForm struct {
+ ClientID string `binding:"Required"`
+ RedirectURI string
+ State string
+ Scope string
+ Nonce string
+}
+
+// Validate validates the fields
+func (f *GrantApplicationForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// AccessTokenForm for issuing access tokens from authorization codes or refresh tokens
+type AccessTokenForm struct {
+ GrantType string `json:"grant_type"`
+ ClientID string `json:"client_id"`
+ ClientSecret string `json:"client_secret"`
+ RedirectURI string `json:"redirect_uri"`
+ Code string `json:"code"`
+ RefreshToken string `json:"refresh_token"`
+
+ // PKCE support
+ CodeVerifier string `json:"code_verifier"`
+}
+
+// Validate validates the fields
+func (f *AccessTokenForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// __________________________________________.___ _______ ________ _________
+// / _____/\_ _____/\__ ___/\__ ___/| |\ \ / _____/ / _____/
+// \_____ \ | __)_ | | | | | |/ | \/ \ ___ \_____ \
+// / \ | \ | | | | | / | \ \_\ \/ \
+// /_______ //_______ / |____| |____| |___\____|__ /\______ /_______ /
+// \/ \/ \/ \/ \/
+
+// UpdateProfileForm form for updating profile
+type UpdateProfileForm struct {
+ Name string `binding:"AlphaDashDot;MaxSize(40)"`
+ FullName string `binding:"MaxSize(100)"`
+ KeepEmailPrivate bool
+ Website string `binding:"ValidUrl;MaxSize(255)"`
+ Location string `binding:"MaxSize(50)"`
+ Language string
+ Description string `binding:"MaxSize(255)"`
+ KeepActivityPrivate bool
+}
+
+// Validate validates the fields
+func (f *UpdateProfileForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// Avatar types
+const (
+ AvatarLocal string = "local"
+ AvatarByMail string = "bymail"
+)
+
+// AvatarForm form for changing avatar
+type AvatarForm struct {
+ Source string
+ Avatar *multipart.FileHeader
+ Gravatar string `binding:"OmitEmpty;Email;MaxSize(254)"`
+ Federavatar bool
+}
+
+// Validate validates the fields
+func (f *AvatarForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// AddEmailForm form for adding new email
+type AddEmailForm struct {
+ Email string `binding:"Required;Email;MaxSize(254)"`
+}
+
+// Validate validates the fields
+func (f *AddEmailForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// UpdateThemeForm form for updating a users' theme
+type UpdateThemeForm struct {
+ Theme string `binding:"Required;MaxSize(30)"`
+}
+
+// Validate validates the field
+func (f *UpdateThemeForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// IsThemeExists checks if the theme is a theme available in the config.
+func (f UpdateThemeForm) IsThemeExists() bool {
+ var exists bool
+
+ for _, v := range setting.UI.Themes {
+ if strings.EqualFold(v, f.Theme) {
+ exists = true
+ break
+ }
+ }
+
+ return exists
+}
+
+// ChangePasswordForm form for changing password
+type ChangePasswordForm struct {
+ OldPassword string `form:"old_password" binding:"MaxSize(255)"`
+ Password string `form:"password" binding:"Required;MaxSize(255)"`
+ Retype string `form:"retype"`
+}
+
+// Validate validates the fields
+func (f *ChangePasswordForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// AddOpenIDForm is for changing openid uri
+type AddOpenIDForm struct {
+ Openid string `binding:"Required;MaxSize(256)"`
+}
+
+// Validate validates the fields
+func (f *AddOpenIDForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// AddKeyForm form for adding SSH/GPG key
+type AddKeyForm struct {
+ Type string `binding:"OmitEmpty"`
+ Title string `binding:"Required;MaxSize(50)"`
+ Content string `binding:"Required"`
+ IsWritable bool
+}
+
+// Validate validates the fields
+func (f *AddKeyForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// NewAccessTokenForm form for creating access token
+type NewAccessTokenForm struct {
+ Name string `binding:"Required;MaxSize(255)"`
+}
+
+// Validate validates the fields
+func (f *NewAccessTokenForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// EditOAuth2ApplicationForm form for editing oauth2 applications
+type EditOAuth2ApplicationForm struct {
+ Name string `binding:"Required;MaxSize(255)" form:"application_name"`
+ RedirectURI string `binding:"Required" form:"redirect_uri"`
+}
+
+// Validate validates the fields
+func (f *EditOAuth2ApplicationForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// TwoFactorAuthForm for logging in with 2FA token.
+type TwoFactorAuthForm struct {
+ Passcode string `binding:"Required"`
+}
+
+// Validate validates the fields
+func (f *TwoFactorAuthForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// TwoFactorScratchAuthForm for logging in with 2FA scratch token.
+type TwoFactorScratchAuthForm struct {
+ Token string `binding:"Required"`
+}
+
+// Validate validates the fields
+func (f *TwoFactorScratchAuthForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// U2FRegistrationForm for reserving an U2F name
+type U2FRegistrationForm struct {
+ Name string `binding:"Required"`
+}
+
+// Validate validates the fields
+func (f *U2FRegistrationForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// U2FDeleteForm for deleting U2F keys
+type U2FDeleteForm struct {
+ ID int64 `binding:"Required"`
+}
+
+// Validate validates the fields
+func (f *U2FDeleteForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
--- /dev/null
+// Copyright 2017 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package forms
+
+import (
+ "net/http"
+
+ "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/middlewares"
+ "gitea.com/go-chi/binding"
+)
+
+// SignInOpenIDForm form for signing in with OpenID
+type SignInOpenIDForm struct {
+ Openid string `binding:"Required;MaxSize(256)"`
+ Remember bool
+}
+
+// Validate validates the fields
+func (f *SignInOpenIDForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// SignUpOpenIDForm form for signin up with OpenID
+type SignUpOpenIDForm struct {
+ UserName string `binding:"Required;AlphaDashDot;MaxSize(40)"`
+ Email string `binding:"Required;Email;MaxSize(254)"`
+ GRecaptchaResponse string `form:"g-recaptcha-response"`
+ HcaptchaResponse string `form:"h-captcha-response"`
+}
+
+// Validate validates the fields
+func (f *SignUpOpenIDForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// ConnectOpenIDForm form for connecting an existing account to an OpenID URI
+type ConnectOpenIDForm struct {
+ UserName string `binding:"Required;MaxSize(254)"`
+ Password string `binding:"Required;MaxSize(255)"`
+}
+
+// Validate validates the fields
+func (f *ConnectOpenIDForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middlewares.Validate(errs, ctx.Data, f, ctx.Locale)
+}
--- /dev/null
+// Copyright 2018 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package forms
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/modules/setting"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestRegisterForm_IsDomainWhiteList_Empty(t *testing.T) {
+ _ = setting.Service
+
+ setting.Service.EmailDomainWhitelist = []string{}
+
+ form := RegisterForm{}
+
+ assert.True(t, form.IsEmailDomainWhitelisted())
+}
+
+func TestRegisterForm_IsDomainWhiteList_InvalidEmail(t *testing.T) {
+ _ = setting.Service
+
+ setting.Service.EmailDomainWhitelist = []string{"gitea.io"}
+
+ tt := []struct {
+ email string
+ }{
+ {"securitygieqqq"},
+ {"hdudhdd"},
+ }
+
+ for _, v := range tt {
+ form := RegisterForm{Email: v.email}
+
+ assert.False(t, form.IsEmailDomainWhitelisted())
+ }
+}
+
+func TestRegisterForm_IsDomainWhiteList_ValidEmail(t *testing.T) {
+ _ = setting.Service
+
+ setting.Service.EmailDomainWhitelist = []string{"gitea.io"}
+
+ tt := []struct {
+ email string
+ valid bool
+ }{
+ {"security@gitea.io", true},
+ {"security@gITea.io", true},
+ {"hdudhdd", false},
+ {"seee@example.com", false},
+ }
+
+ for _, v := range tt {
+ form := RegisterForm{Email: v.email}
+
+ assert.Equal(t, v.valid, form.IsEmailDomainWhitelisted())
+ }
+}
}
var req api.LFSLockRequest
- bodyReader := ctx.Req.Body().ReadCloser()
+ bodyReader := ctx.Req.Body
defer bodyReader.Close()
dec := json.NewDecoder(bodyReader)
if err := dec.Decode(&req); err != nil {
}
var req api.LFSLockDeleteRequest
- bodyReader := ctx.Req.Body().ReadCloser()
+ bodyReader := ctx.Req.Body
defer bodyReader.Close()
dec := json.NewDecoder(bodyReader)
if err := dec.Decode(&req); err != nil {
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
- "gitea.com/macaron/macaron"
"github.com/dgrijalva/jwt-go"
)
}
contentStore := &ContentStore{ObjectStorage: storage.LFS}
- defer ctx.Req.Request.Body.Close()
- if err := contentStore.Put(meta, ctx.Req.Request.Body); err != nil {
+ defer ctx.Req.Body.Close()
+ if err := contentStore.Put(meta, ctx.Req.Body); err != nil {
// Put will log the error itself
ctx.Resp.WriteHeader(500)
if err == errSizeMismatch || err == errHashMismatch {
// MetaMatcher provides a mux.MatcherFunc that only allows requests that contain
// an Accept header with the metaMediaType
-func MetaMatcher(r macaron.Request) bool {
+func MetaMatcher(r *http.Request) bool {
mediaParts := strings.Split(r.Header.Get("Accept"), ";")
mt := mediaParts[0]
return mt == metaMediaType
if r.Method == "POST" { // Maybe also check if +json
var p RequestVars
- bodyReader := r.Body().ReadCloser()
+ bodyReader := r.Body
defer bodyReader.Close()
dec := json.NewDecoder(bodyReader)
err := dec.Decode(&p)
r := ctx.Req
var bv BatchVars
- bodyReader := r.Body().ReadCloser()
+ bodyReader := r.Body
defer bodyReader.Close()
dec := json.NewDecoder(bodyReader)
err := dec.Decode(&bv)
logRequest(ctx.Req, status)
}
-func logRequest(r macaron.Request, status int) {
+func logRequest(r *http.Request, status int) {
log.Debug("LFS request - Method: %s, URL: %s, Status %d", r.Method, r.URL, status)
}
--- /dev/null
+// Copyright 2014 The Gogs Authors. All rights reserved.
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package middlewares
+
+import (
+ "reflect"
+ "strings"
+
+ "code.gitea.io/gitea/modules/translation"
+ "code.gitea.io/gitea/modules/validation"
+
+ "gitea.com/go-chi/binding"
+ "github.com/unknwon/com"
+)
+
+// Form form binding interface
+type Form interface {
+ binding.Validator
+}
+
+func init() {
+ binding.SetNameMapper(com.ToSnakeCase)
+}
+
+// AssignForm assign form values back to the template data.
+func AssignForm(form interface{}, data map[string]interface{}) {
+ typ := reflect.TypeOf(form)
+ val := reflect.ValueOf(form)
+
+ for typ.Kind() == reflect.Ptr {
+ typ = typ.Elem()
+ val = val.Elem()
+ }
+
+ for i := 0; i < typ.NumField(); i++ {
+ field := typ.Field(i)
+
+ fieldName := field.Tag.Get("form")
+ // Allow ignored fields in the struct
+ if fieldName == "-" {
+ continue
+ } else if len(fieldName) == 0 {
+ fieldName = com.ToSnakeCase(field.Name)
+ }
+
+ data[fieldName] = val.Field(i).Interface()
+ }
+}
+
+func getRuleBody(field reflect.StructField, prefix string) string {
+ for _, rule := range strings.Split(field.Tag.Get("binding"), ";") {
+ if strings.HasPrefix(rule, prefix) {
+ return rule[len(prefix) : len(rule)-1]
+ }
+ }
+ return ""
+}
+
+// GetSize get size int form tag
+func GetSize(field reflect.StructField) string {
+ return getRuleBody(field, "Size(")
+}
+
+// GetMinSize get minimal size in form tag
+func GetMinSize(field reflect.StructField) string {
+ return getRuleBody(field, "MinSize(")
+}
+
+// GetMaxSize get max size in form tag
+func GetMaxSize(field reflect.StructField) string {
+ return getRuleBody(field, "MaxSize(")
+}
+
+// GetInclude get include in form tag
+func GetInclude(field reflect.StructField) string {
+ return getRuleBody(field, "Include(")
+}
+
+// Validate validate TODO:
+func Validate(errs binding.Errors, data map[string]interface{}, f Form, l translation.Locale) binding.Errors {
+ if errs.Len() == 0 {
+ return errs
+ }
+
+ data["HasError"] = true
+ // If the field with name errs[0].FieldNames[0] is not found in form
+ // somehow, some code later on will panic on Data["ErrorMsg"].(string).
+ // So initialize it to some default.
+ data["ErrorMsg"] = l.Tr("form.unknown_error")
+ AssignForm(f, data)
+
+ typ := reflect.TypeOf(f)
+ val := reflect.ValueOf(f)
+
+ if typ.Kind() == reflect.Ptr {
+ typ = typ.Elem()
+ val = val.Elem()
+ }
+
+ if field, ok := typ.FieldByName(errs[0].FieldNames[0]); ok {
+ fieldName := field.Tag.Get("form")
+ if fieldName != "-" {
+ data["Err_"+field.Name] = true
+
+ trName := field.Tag.Get("locale")
+ if len(trName) == 0 {
+ trName = l.Tr("form." + field.Name)
+ } else {
+ trName = l.Tr(trName)
+ }
+
+ switch errs[0].Classification {
+ case binding.ERR_REQUIRED:
+ data["ErrorMsg"] = trName + l.Tr("form.require_error")
+ case binding.ERR_ALPHA_DASH:
+ data["ErrorMsg"] = trName + l.Tr("form.alpha_dash_error")
+ case binding.ERR_ALPHA_DASH_DOT:
+ data["ErrorMsg"] = trName + l.Tr("form.alpha_dash_dot_error")
+ case validation.ErrGitRefName:
+ data["ErrorMsg"] = trName + l.Tr("form.git_ref_name_error")
+ case binding.ERR_SIZE:
+ data["ErrorMsg"] = trName + l.Tr("form.size_error", GetSize(field))
+ case binding.ERR_MIN_SIZE:
+ data["ErrorMsg"] = trName + l.Tr("form.min_size_error", GetMinSize(field))
+ case binding.ERR_MAX_SIZE:
+ data["ErrorMsg"] = trName + l.Tr("form.max_size_error", GetMaxSize(field))
+ case binding.ERR_EMAIL:
+ data["ErrorMsg"] = trName + l.Tr("form.email_error")
+ case binding.ERR_URL:
+ data["ErrorMsg"] = trName + l.Tr("form.url_error")
+ case binding.ERR_INCLUDE:
+ data["ErrorMsg"] = trName + l.Tr("form.include_error", GetInclude(field))
+ case validation.ErrGlobPattern:
+ data["ErrorMsg"] = trName + l.Tr("form.glob_pattern_error", errs[0].Message)
+ default:
+ data["ErrorMsg"] = l.Tr("form.unknown_error") + " " + errs[0].Classification
+ }
+ return errs
+ }
+ }
+ return errs
+}
+// Copyright 2020 The Macaron Authors
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
"code.gitea.io/gitea/modules/setting"
)
+// MaxAge sets the maximum age for a provided cookie
+func MaxAge(maxAge int) func(*http.Cookie) {
+ return func(c *http.Cookie) {
+ c.MaxAge = maxAge
+ }
+}
+
+// Path sets the path for a provided cookie
+func Path(path string) func(*http.Cookie) {
+ return func(c *http.Cookie) {
+ c.Path = path
+ }
+}
+
+// Domain sets the domain for a provided cookie
+func Domain(domain string) func(*http.Cookie) {
+ return func(c *http.Cookie) {
+ c.Domain = domain
+ }
+}
+
+// Secure sets the secure setting for a provided cookie
+func Secure(secure bool) func(*http.Cookie) {
+ return func(c *http.Cookie) {
+ c.Secure = secure
+ }
+}
+
+// HTTPOnly sets the HttpOnly setting for a provided cookie
+func HTTPOnly(httpOnly bool) func(*http.Cookie) {
+ return func(c *http.Cookie) {
+ c.HttpOnly = httpOnly
+ }
+}
+
+// Expires sets the expires and rawexpires for a provided cookie
+func Expires(expires time.Time) func(*http.Cookie) {
+ return func(c *http.Cookie) {
+ c.Expires = expires
+ c.RawExpires = expires.Format(time.UnixDate)
+ }
+}
+
+// SameSite sets the SameSite for a provided cookie
+func SameSite(sameSite http.SameSite) func(*http.Cookie) {
+ return func(c *http.Cookie) {
+ c.SameSite = sameSite
+ }
+}
+
// NewCookie creates a cookie
func NewCookie(name, value string, maxAge int) *http.Cookie {
return &http.Cookie{
resp.Header().Add("Set-Cookie", cookie.String())
}
+
+// GetCookie returns given cookie value from request header.
+func GetCookie(req *http.Request, name string) string {
+ cookie, err := req.Cookie(name)
+ if err != nil {
+ return ""
+ }
+ val, _ := url.QueryUnescape(cookie.Value)
+ return val
+}
--- /dev/null
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package middlewares
+
+// DataStore represents a data store
+type DataStore interface {
+ GetData() map[string]interface{}
+}
--- /dev/null
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package middlewares
+
+import "net/url"
+
+// flashes enumerates all the flash types
+const (
+ SuccessFlash = "SuccessMsg"
+ ErrorFlash = "ErrorMsg"
+ WarnFlash = "WarningMsg"
+ InfoFlash = "InfoMsg"
+)
+
+var (
+ // FlashNow FIXME:
+ FlashNow bool
+)
+
+// Flash represents a one time data transfer between two requests.
+type Flash struct {
+ DataStore
+ url.Values
+ ErrorMsg, WarningMsg, InfoMsg, SuccessMsg string
+}
+
+func (f *Flash) set(name, msg string, current ...bool) {
+ isShow := false
+ if (len(current) == 0 && FlashNow) ||
+ (len(current) > 0 && current[0]) {
+ isShow = true
+ }
+
+ if isShow {
+ f.GetData()["Flash"] = f
+ } else {
+ f.Set(name, msg)
+ }
+}
+
+// Error sets error message
+func (f *Flash) Error(msg string, current ...bool) {
+ f.ErrorMsg = msg
+ f.set("error", msg, current...)
+}
+
+// Warning sets warning message
+func (f *Flash) Warning(msg string, current ...bool) {
+ f.WarningMsg = msg
+ f.set("warning", msg, current...)
+}
+
+// Info sets info message
+func (f *Flash) Info(msg string, current ...bool) {
+ f.InfoMsg = msg
+ f.set("info", msg, current...)
+}
+
+// Success sets success message
+func (f *Flash) Success(msg string, current ...bool) {
+ f.SuccessMsg = msg
+ f.set("success", msg, current...)
+}
// 2. Get language information from cookies.
if len(lang) == 0 {
ck, _ := req.Cookie("lang")
- lang = ck.Value
- hasCookie = true
+ if ck != nil {
+ lang = ck.Value
+ hasCookie = true
+ }
}
// Check again in case someone modify by purpose.
- if !i18n.IsExist(lang) {
+ if lang != "" && !i18n.IsExist(lang) {
lang = ""
hasCookie = false
}
+++ /dev/null
-// Copyright 2013 Beego Authors
-// Copyright 2014 The Macaron Authors
-// Copyright 2020 The Gitea Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package middlewares
-
-import (
- "fmt"
- "sync"
- "time"
-
- "code.gitea.io/gitea/modules/nosql"
-
- "gitea.com/go-chi/session"
- "github.com/go-redis/redis/v7"
-)
-
-// RedisStore represents a redis session store implementation.
-// TODO: copied from modules/session/redis.go and should remove that one until macaron removed.
-type RedisStore struct {
- c redis.UniversalClient
- prefix, sid string
- duration time.Duration
- lock sync.RWMutex
- data map[interface{}]interface{}
-}
-
-// NewRedisStore creates and returns a redis session store.
-func NewRedisStore(c redis.UniversalClient, prefix, sid string, dur time.Duration, kv map[interface{}]interface{}) *RedisStore {
- return &RedisStore{
- c: c,
- prefix: prefix,
- sid: sid,
- duration: dur,
- data: kv,
- }
-}
-
-// Set sets value to given key in session.
-func (s *RedisStore) Set(key, val interface{}) error {
- s.lock.Lock()
- defer s.lock.Unlock()
-
- s.data[key] = val
- return nil
-}
-
-// Get gets value by given key in session.
-func (s *RedisStore) Get(key interface{}) interface{} {
- s.lock.RLock()
- defer s.lock.RUnlock()
-
- return s.data[key]
-}
-
-// Delete delete a key from session.
-func (s *RedisStore) Delete(key interface{}) error {
- s.lock.Lock()
- defer s.lock.Unlock()
-
- delete(s.data, key)
- return nil
-}
-
-// ID returns current session ID.
-func (s *RedisStore) ID() string {
- return s.sid
-}
-
-// Release releases resource and save data to provider.
-func (s *RedisStore) Release() error {
- // Skip encoding if the data is empty
- if len(s.data) == 0 {
- return nil
- }
-
- data, err := session.EncodeGob(s.data)
- if err != nil {
- return err
- }
-
- return s.c.Set(s.prefix+s.sid, string(data), s.duration).Err()
-}
-
-// Flush deletes all session data.
-func (s *RedisStore) Flush() error {
- s.lock.Lock()
- defer s.lock.Unlock()
-
- s.data = make(map[interface{}]interface{})
- return nil
-}
-
-// RedisProvider represents a redis session provider implementation.
-type RedisProvider struct {
- c redis.UniversalClient
- duration time.Duration
- prefix string
-}
-
-// Init initializes redis session provider.
-// configs: network=tcp,addr=:6379,password=macaron,db=0,pool_size=100,idle_timeout=180,prefix=session;
-func (p *RedisProvider) Init(maxlifetime int64, configs string) (err error) {
- p.duration, err = time.ParseDuration(fmt.Sprintf("%ds", maxlifetime))
- if err != nil {
- return err
- }
-
- uri := nosql.ToRedisURI(configs)
-
- for k, v := range uri.Query() {
- switch k {
- case "prefix":
- p.prefix = v[0]
- }
- }
-
- p.c = nosql.GetManager().GetRedisClient(uri.String())
- return p.c.Ping().Err()
-}
-
-// Read returns raw session store by session ID.
-func (p *RedisProvider) Read(sid string) (session.RawStore, error) {
- psid := p.prefix + sid
- if !p.Exist(sid) {
- if err := p.c.Set(psid, "", p.duration).Err(); err != nil {
- return nil, err
- }
- }
-
- var kv map[interface{}]interface{}
- kvs, err := p.c.Get(psid).Result()
- if err != nil {
- return nil, err
- }
- if len(kvs) == 0 {
- kv = make(map[interface{}]interface{})
- } else {
- kv, err = session.DecodeGob([]byte(kvs))
- if err != nil {
- return nil, err
- }
- }
-
- return NewRedisStore(p.c, p.prefix, sid, p.duration, kv), nil
-}
-
-// Exist returns true if session with given ID exists.
-func (p *RedisProvider) Exist(sid string) bool {
- v, err := p.c.Exists(p.prefix + sid).Result()
- return err == nil && v == 1
-}
-
-// Destroy deletes a session by session ID.
-func (p *RedisProvider) Destroy(sid string) error {
- return p.c.Del(p.prefix + sid).Err()
-}
-
-// Regenerate regenerates a session store from old session ID to new one.
-func (p *RedisProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err error) {
- poldsid := p.prefix + oldsid
- psid := p.prefix + sid
-
- if p.Exist(sid) {
- return nil, fmt.Errorf("new sid '%s' already exists", sid)
- } else if !p.Exist(oldsid) {
- // Make a fake old session.
- if err = p.c.Set(poldsid, "", p.duration).Err(); err != nil {
- return nil, err
- }
- }
-
- if err = p.c.Rename(poldsid, psid).Err(); err != nil {
- return nil, err
- }
-
- var kv map[interface{}]interface{}
- kvs, err := p.c.Get(psid).Result()
- if err != nil {
- return nil, err
- }
-
- if len(kvs) == 0 {
- kv = make(map[interface{}]interface{})
- } else {
- kv, err = session.DecodeGob([]byte(kvs))
- if err != nil {
- return nil, err
- }
- }
-
- return NewRedisStore(p.c, p.prefix, sid, p.duration, kv), nil
-}
-
-// Count counts and returns number of sessions.
-func (p *RedisProvider) Count() int {
- return int(p.c.DBSize().Val())
-}
-
-// GC calls GC to clean expired sessions.
-func (*RedisProvider) GC() {}
-
-func init() {
- session.Register("redis", &RedisProvider{})
-}
+++ /dev/null
-// Copyright 2019 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package middlewares
-
-import (
- "encoding/json"
- "fmt"
- "sync"
-
- "gitea.com/go-chi/session"
- couchbase "gitea.com/go-chi/session/couchbase"
- memcache "gitea.com/go-chi/session/memcache"
- mysql "gitea.com/go-chi/session/mysql"
- postgres "gitea.com/go-chi/session/postgres"
-)
-
-// VirtualSessionProvider represents a shadowed session provider implementation.
-// TODO: copied from modules/session/redis.go and should remove that one until macaron removed.
-type VirtualSessionProvider struct {
- lock sync.RWMutex
- provider session.Provider
-}
-
-// Init initializes the cookie session provider with given root path.
-func (o *VirtualSessionProvider) Init(gclifetime int64, config string) error {
- var opts session.Options
- if err := json.Unmarshal([]byte(config), &opts); err != nil {
- return err
- }
- // Note that these options are unprepared so we can't just use NewManager here.
- // Nor can we access the provider map in session.
- // So we will just have to do this by hand.
- // This is only slightly more wrong than modules/setting/session.go:23
- switch opts.Provider {
- case "memory":
- o.provider = &session.MemProvider{}
- case "file":
- o.provider = &session.FileProvider{}
- case "redis":
- o.provider = &RedisProvider{}
- case "mysql":
- o.provider = &mysql.MysqlProvider{}
- case "postgres":
- o.provider = &postgres.PostgresProvider{}
- case "couchbase":
- o.provider = &couchbase.CouchbaseProvider{}
- case "memcache":
- o.provider = &memcache.MemcacheProvider{}
- default:
- return fmt.Errorf("VirtualSessionProvider: Unknown Provider: %s", opts.Provider)
- }
- return o.provider.Init(gclifetime, opts.ProviderConfig)
-}
-
-// Read returns raw session store by session ID.
-func (o *VirtualSessionProvider) Read(sid string) (session.RawStore, error) {
- o.lock.RLock()
- defer o.lock.RUnlock()
- if o.provider.Exist(sid) {
- return o.provider.Read(sid)
- }
- kv := make(map[interface{}]interface{})
- kv["_old_uid"] = "0"
- return NewVirtualStore(o, sid, kv), nil
-}
-
-// Exist returns true if session with given ID exists.
-func (o *VirtualSessionProvider) Exist(sid string) bool {
- return true
-}
-
-// Destroy deletes a session by session ID.
-func (o *VirtualSessionProvider) Destroy(sid string) error {
- o.lock.Lock()
- defer o.lock.Unlock()
- return o.provider.Destroy(sid)
-}
-
-// Regenerate regenerates a session store from old session ID to new one.
-func (o *VirtualSessionProvider) Regenerate(oldsid, sid string) (session.RawStore, error) {
- o.lock.Lock()
- defer o.lock.Unlock()
- return o.provider.Regenerate(oldsid, sid)
-}
-
-// Count counts and returns number of sessions.
-func (o *VirtualSessionProvider) Count() int {
- o.lock.RLock()
- defer o.lock.RUnlock()
- return o.provider.Count()
-}
-
-// GC calls GC to clean expired sessions.
-func (o *VirtualSessionProvider) GC() {
- o.provider.GC()
-}
-
-func init() {
- session.Register("VirtualSession", &VirtualSessionProvider{})
-}
-
-// VirtualStore represents a virtual session store implementation.
-type VirtualStore struct {
- p *VirtualSessionProvider
- sid string
- lock sync.RWMutex
- data map[interface{}]interface{}
- released bool
-}
-
-// NewVirtualStore creates and returns a virtual session store.
-func NewVirtualStore(p *VirtualSessionProvider, sid string, kv map[interface{}]interface{}) *VirtualStore {
- return &VirtualStore{
- p: p,
- sid: sid,
- data: kv,
- }
-}
-
-// Set sets value to given key in session.
-func (s *VirtualStore) Set(key, val interface{}) error {
- s.lock.Lock()
- defer s.lock.Unlock()
-
- s.data[key] = val
- return nil
-}
-
-// Get gets value by given key in session.
-func (s *VirtualStore) Get(key interface{}) interface{} {
- s.lock.RLock()
- defer s.lock.RUnlock()
-
- return s.data[key]
-}
-
-// Delete delete a key from session.
-func (s *VirtualStore) Delete(key interface{}) error {
- s.lock.Lock()
- defer s.lock.Unlock()
-
- delete(s.data, key)
- return nil
-}
-
-// ID returns current session ID.
-func (s *VirtualStore) ID() string {
- return s.sid
-}
-
-// Release releases resource and save data to provider.
-func (s *VirtualStore) Release() error {
- s.lock.Lock()
- defer s.lock.Unlock()
- // Now need to lock the provider
- s.p.lock.Lock()
- defer s.p.lock.Unlock()
- if oldUID, ok := s.data["_old_uid"]; (ok && (oldUID != "0" || len(s.data) > 1)) || (!ok && len(s.data) > 0) {
- // Now ensure that we don't exist!
- realProvider := s.p.provider
-
- if !s.released && realProvider.Exist(s.sid) {
- // This is an error!
- return fmt.Errorf("new sid '%s' already exists", s.sid)
- }
- realStore, err := realProvider.Read(s.sid)
- if err != nil {
- return err
- }
- if err := realStore.Flush(); err != nil {
- return err
- }
- for key, value := range s.data {
- if err := realStore.Set(key, value); err != nil {
- return err
- }
- }
- err = realStore.Release()
- if err == nil {
- s.released = true
- }
- return err
- }
- return nil
-}
-
-// Flush deletes all session data.
-func (s *VirtualStore) Flush() error {
- s.lock.Lock()
- defer s.lock.Unlock()
-
- s.data = make(map[interface{}]interface{})
- return nil
-}
"code.gitea.io/gitea/modules/nosql"
- "gitea.com/macaron/session"
+ "gitea.com/go-chi/session"
"github.com/go-redis/redis/v7"
)
--- /dev/null
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package session
+
+// Store represents a session store
+type Store interface {
+ Get(interface{}) interface{}
+ Set(interface{}, interface{}) error
+ Delete(interface{}) error
+}
"fmt"
"sync"
- "gitea.com/macaron/session"
- couchbase "gitea.com/macaron/session/couchbase"
- memcache "gitea.com/macaron/session/memcache"
- mysql "gitea.com/macaron/session/mysql"
- nodb "gitea.com/macaron/session/nodb"
- postgres "gitea.com/macaron/session/postgres"
+ "gitea.com/go-chi/session"
+ couchbase "gitea.com/go-chi/session/couchbase"
+ memcache "gitea.com/go-chi/session/memcache"
+ mysql "gitea.com/go-chi/session/mysql"
+ postgres "gitea.com/go-chi/session/postgres"
)
// VirtualSessionProvider represents a shadowed session provider implementation.
o.provider = &couchbase.CouchbaseProvider{}
case "memcache":
o.provider = &memcache.MemcacheProvider{}
- case "nodb":
- o.provider = &nodb.NodbProvider{}
default:
return fmt.Errorf("VirtualSessionProvider: Unknown Provider: %s", opts.Provider)
}
return &description
}
-func newMacaronLogService() {
- options := newDefaultLogOptions()
- options.filename = filepath.Join(LogRootPath, "macaron.log")
- options.bufferLength = Cfg.Section("log").Key("BUFFER_LEN").MustInt64(10000)
-
- Cfg.Section("log").Key("MACARON").MustString("file")
- if RedirectMacaronLog {
- generateNamedLogger("macaron", options)
- }
-}
-
func newAccessLogService() {
EnableAccessLog = Cfg.Section("log").Key("ENABLE_ACCESS_LOG").MustBool(false)
AccessLogTemplate = Cfg.Section("log").Key("ACCESS_LOG_TEMPLATE").MustString(
// NewLogServices creates all the log services
func NewLogServices(disableConsole bool) {
newLogService()
- newMacaronLogService()
newRouterLogService()
newAccessLogService()
NewXORMLogService(disableConsole)
func newSessionService() {
sec := Cfg.Section("session")
SessionConfig.Provider = sec.Key("PROVIDER").In("memory",
- []string{"memory", "file", "redis", "mysql", "postgres", "couchbase", "memcache", "nodb"})
+ []string{"memory", "file", "redis", "mysql", "postgres", "couchbase", "memcache"})
SessionConfig.ProviderConfig = strings.Trim(sec.Key("PROVIDER_CONFIG").MustString(path.Join(AppDataPath, "sessions")), "\" ")
if SessionConfig.Provider == "file" && !filepath.IsAbs(SessionConfig.ProviderConfig) {
SessionConfig.ProviderConfig = path.Join(AppWorkPath, SessionConfig.ProviderConfig)
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
+
+ "github.com/unrolled/render"
)
// Vars represents variables to be render in golang templates
}
return tmpls
}
+
+// HTMLRenderer returns a render.
+func HTMLRenderer() *render.Render {
+ return render.New(render.Options{
+ Extensions: []string{".tmpl"},
+ Directory: "templates",
+ Funcs: NewFuncMap(),
+ Asset: GetAsset,
+ AssetNames: GetAssetNames,
+ IsDevelopment: !setting.IsProd(),
+ })
+}
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
-
- "gitea.com/macaron/macaron"
)
var (
return append(tmpls, tmpls2...)
}
-// HTMLRenderer implements the macaron handler for serving HTML templates.
-func HTMLRenderer() macaron.Handler {
- return macaron.Renderer(macaron.RenderOptions{
- Funcs: NewFuncMap(),
- Directory: path.Join(setting.StaticRootPath, "templates"),
- AppendDirectories: []string{
- path.Join(setting.CustomPath, "templates"),
- },
- })
-}
-
-// JSONRenderer implements the macaron handler for serving JSON templates.
-func JSONRenderer() macaron.Handler {
- return macaron.Renderer(macaron.RenderOptions{
- Funcs: NewFuncMap(),
- Directory: path.Join(setting.StaticRootPath, "templates"),
- AppendDirectories: []string{
- path.Join(setting.CustomPath, "templates"),
- },
- HTMLContentType: "application/json",
- })
-}
-
// Mailer provides the templates required for sending notification mails.
func Mailer() (*texttmpl.Template, *template.Template) {
for _, funcs := range NewTextFuncMap() {
package templates
import (
- "bytes"
- "fmt"
"html/template"
- "io"
"io/ioutil"
"os"
"path"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
-
- "gitea.com/macaron/macaron"
)
var (
bodyTemplates = template.New("")
)
-type templateFileSystem struct {
- files []macaron.TemplateFile
-}
-
-func (templates templateFileSystem) ListFiles() []macaron.TemplateFile {
- return templates.files
-}
-
-func (templates templateFileSystem) Get(name string) (io.Reader, error) {
- for i := range templates.files {
- if templates.files[i].Name()+templates.files[i].Ext() == name {
- return bytes.NewReader(templates.files[i].Data()), nil
- }
- }
-
- return nil, fmt.Errorf("file '%s' not found", name)
-}
-
// GetAsset get a special asset, only for chi
func GetAsset(name string) ([]byte, error) {
bs, err := ioutil.ReadFile(filepath.Join(setting.CustomPath, name))
return append(tmpls, customTmpls...)
}
-func NewTemplateFileSystem() templateFileSystem {
- fs := templateFileSystem{}
- fs.files = make([]macaron.TemplateFile, 0, 10)
-
- for _, assetPath := range AssetNames() {
- if strings.HasPrefix(assetPath, "mail/") {
- continue
- }
-
- if !strings.HasSuffix(assetPath, ".tmpl") {
- continue
- }
-
- content, err := Asset(assetPath)
-
- if err != nil {
- log.Warn("Failed to read embedded %s template. %v", assetPath, err)
- continue
- }
-
- fs.files = append(fs.files, macaron.NewTplFile(
- strings.TrimSuffix(
- assetPath,
- ".tmpl",
- ),
- content,
- ".tmpl",
- ))
- }
-
- customDir := path.Join(setting.CustomPath, "templates")
- isDir, err := util.IsDir(customDir)
- if err != nil {
- log.Warn("Unable to check if templates dir %s is a directory. Error: %v", customDir, err)
- }
- if isDir {
- files, err := util.StatDir(customDir)
-
- if err != nil {
- log.Warn("Failed to read %s templates dir. %v", customDir, err)
- } else {
- for _, filePath := range files {
- if strings.HasPrefix(filePath, "mail/") {
- continue
- }
-
- if !strings.HasSuffix(filePath, ".tmpl") {
- continue
- }
-
- content, err := ioutil.ReadFile(path.Join(customDir, filePath))
-
- if err != nil {
- log.Warn("Failed to read custom %s template. %v", filePath, err)
- continue
- }
-
- fs.files = append(fs.files, macaron.NewTplFile(
- strings.TrimSuffix(
- filePath,
- ".tmpl",
- ),
- content,
- ".tmpl",
- ))
- }
- }
- }
-
- return fs
-}
-
-// HTMLRenderer implements the macaron handler for serving HTML templates.
-func HTMLRenderer() macaron.Handler {
- return macaron.Renderer(macaron.RenderOptions{
- Funcs: NewFuncMap(),
- TemplateFileSystem: NewTemplateFileSystem(),
- })
-}
-
-// JSONRenderer implements the macaron handler for serving JSON templates.
-func JSONRenderer() macaron.Handler {
- return macaron.Renderer(macaron.RenderOptions{
- Funcs: NewFuncMap(),
- TemplateFileSystem: NewTemplateFileSystem(),
- HTMLContentType: "application/json",
- })
-}
-
// Mailer provides the templates required for sending notification mails.
func Mailer() (*texttmpl.Template, *template.Template) {
for _, funcs := range NewTextFuncMap() {
package test
import (
+ scontext "context"
+ "html/template"
+ "io"
"net/http"
"net/http/httptest"
"net/url"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/middlewares"
- "gitea.com/macaron/macaron"
- "gitea.com/macaron/session"
+ "github.com/go-chi/chi"
"github.com/stretchr/testify/assert"
+ "github.com/unrolled/render"
)
// MockContext mock context for unit tests
func MockContext(t *testing.T, path string) *context.Context {
- var macaronContext macaron.Context
- macaronContext.ReplaceAllParams(macaron.Params{})
- macaronContext.Locale = &mockLocale{}
+ var resp = &mockResponseWriter{}
+ var ctx = context.Context{
+ Render: &mockRender{},
+ Data: make(map[string]interface{}),
+ Flash: &middlewares.Flash{
+ Values: make(url.Values),
+ },
+ Resp: context.NewResponse(resp),
+ Locale: &mockLocale{},
+ }
+
requestURL, err := url.Parse(path)
assert.NoError(t, err)
- macaronContext.Req = macaron.Request{Request: &http.Request{
+ var req = &http.Request{
URL: requestURL,
Form: url.Values{},
- }}
- macaronContext.Resp = &mockResponseWriter{}
- macaronContext.Render = &mockRender{ResponseWriter: macaronContext.Resp}
- macaronContext.Data = map[string]interface{}{}
- return &context.Context{
- Context: &macaronContext,
- Flash: &session.Flash{
- Values: make(url.Values),
- },
}
+
+ chiCtx := chi.NewRouteContext()
+ req = req.WithContext(scontext.WithValue(req.Context(), chi.RouteCtxKey, chiCtx))
+ ctx.Req = context.WithContext(req, &ctx)
+ return &ctx
}
// LoadRepo load a repo into a test context.
return rw.size
}
-func (rw *mockResponseWriter) Before(b macaron.BeforeFunc) {
- b(rw)
-}
-
func (rw *mockResponseWriter) Push(target string, opts *http.PushOptions) error {
return nil
}
type mockRender struct {
- http.ResponseWriter
-}
-
-func (tr *mockRender) SetResponseWriter(rw http.ResponseWriter) {
- tr.ResponseWriter = rw
-}
-
-func (tr *mockRender) JSON(status int, _ interface{}) {
- tr.Status(status)
-}
-
-func (tr *mockRender) JSONString(interface{}) (string, error) {
- return "", nil
-}
-
-func (tr *mockRender) RawData(status int, _ []byte) {
- tr.Status(status)
-}
-
-func (tr *mockRender) PlainText(status int, _ []byte) {
- tr.Status(status)
-}
-
-func (tr *mockRender) HTML(status int, _ string, _ interface{}, _ ...macaron.HTMLOptions) {
- tr.Status(status)
-}
-
-func (tr *mockRender) HTMLSet(status int, _ string, _ string, _ interface{}, _ ...macaron.HTMLOptions) {
- tr.Status(status)
-}
-
-func (tr *mockRender) HTMLSetString(string, string, interface{}, ...macaron.HTMLOptions) (string, error) {
- return "", nil
-}
-
-func (tr *mockRender) HTMLString(string, interface{}, ...macaron.HTMLOptions) (string, error) {
- return "", nil
-}
-
-func (tr *mockRender) HTMLSetBytes(string, string, interface{}, ...macaron.HTMLOptions) ([]byte, error) {
- return nil, nil
-}
-
-func (tr *mockRender) HTMLBytes(string, interface{}, ...macaron.HTMLOptions) ([]byte, error) {
- return nil, nil
-}
-
-func (tr *mockRender) XML(status int, _ interface{}) {
- tr.Status(status)
-}
-
-func (tr *mockRender) Error(status int, _ ...string) {
- tr.Status(status)
}
-func (tr *mockRender) Status(status int) {
- tr.ResponseWriter.WriteHeader(status)
-}
-
-func (tr *mockRender) SetTemplatePath(string, string) {
+func (tr *mockRender) TemplateLookup(tmpl string) *template.Template {
+ return nil
}
-func (tr *mockRender) HasTemplateSet(string) bool {
- return true
+func (tr *mockRender) HTML(w io.Writer, status int, _ string, _ interface{}, _ ...render.HTMLOptions) error {
+ if resp, ok := w.(http.ResponseWriter); ok {
+ resp.WriteHeader(status)
+ }
+ return nil
}
"time"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/translation"
- macaroni18n "gitea.com/macaron/i18n"
"github.com/stretchr/testify/assert"
"github.com/unknwon/i18n"
)
)
func TestMain(m *testing.M) {
+ setting.StaticRootPath = "../../"
+ setting.Names = []string{"english"}
+ setting.Langs = []string{"en-US"}
// setup
- macaroni18n.I18n(macaroni18n.Options{
- Directory: "../../options/locale/",
- DefaultLang: "en-US",
- Langs: []string{"en-US"},
- Names: []string{"english"},
- })
+ translation.InitLocales()
BaseDate = time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC)
// run the tests
"code.gitea.io/gitea/modules/options"
"code.gitea.io/gitea/modules/setting"
- macaron_i18n "gitea.com/macaron/i18n"
"github.com/unknwon/i18n"
"golang.org/x/text/language"
)
Tr(string, ...interface{}) string
}
+// LangType represents a lang type
+type LangType struct {
+ Lang, Name string
+}
+
var (
- matcher language.Matcher
+ matcher language.Matcher
+ allLangs []LangType
)
+// AllLangs returns all supported langauages
+func AllLangs() []LangType {
+ return allLangs
+}
+
// InitLocales loads the locales
func InitLocales() {
localeNames, err := options.Dir("locale")
-
if err != nil {
log.Fatal("Failed to list locale files: %v", err)
}
- localFiles := make(map[string][]byte)
+ localFiles := make(map[string][]byte)
for _, name := range localeNames {
localFiles[name], err = options.Locale(name)
-
if err != nil {
log.Fatal("Failed to load %s locale file. %v", name, err)
}
}
// These codes will be used once macaron removed
- /*tags := make([]language.Tag, len(setting.Langs))
+ tags := make([]language.Tag, len(setting.Langs))
for i, lang := range setting.Langs {
tags[i] = language.Raw.Make(lang)
}
+
matcher = language.NewMatcher(tags)
- for i, name := range setting.Names {
- i18n.SetMessage(setting.Langs[i], localFiles[name])
+ for i := range setting.Names {
+ key := "locale_" + setting.Langs[i] + ".ini"
+ if err := i18n.SetMessageWithDesc(setting.Langs[i], setting.Names[i], localFiles[key]); err != nil {
+ log.Fatal("Failed to set messages to %s: %v", setting.Langs[i], err)
+ }
+ }
+ i18n.SetDefaultLang("en-US")
+
+ allLangs = make([]LangType, 0, i18n.Count()-1)
+ langs := i18n.ListLangs()
+ names := i18n.ListLangDescs()
+ for i, v := range langs {
+ allLangs = append(allLangs, LangType{v, names[i]})
}
- i18n.SetDefaultLang("en-US")*/
-
- // To be compatible with macaron, we now have to use macaron i18n, once macaron
- // removed, we can use i18n directly
- macaron_i18n.I18n(macaron_i18n.Options{
- SubURL: setting.AppSubURL,
- Files: localFiles,
- Langs: setting.Langs,
- Names: setting.Names,
- DefaultLang: "en-US",
- Redirect: false,
- CookieDomain: setting.SessionConfig.Domain,
- })
}
// Match matches accept languages
"regexp"
"strings"
- "gitea.com/macaron/binding"
+ "gitea.com/go-chi/binding"
"github.com/gobwas/glob"
)
"net/http/httptest"
"testing"
- "gitea.com/macaron/binding"
- "gitea.com/macaron/macaron"
+ "gitea.com/go-chi/binding"
+ "github.com/go-chi/chi"
"github.com/stretchr/testify/assert"
)
func performValidationTest(t *testing.T, testCase validationTestCase) {
httpRecorder := httptest.NewRecorder()
- m := macaron.Classic()
+ m := chi.NewRouter()
- m.Post(testRoute, binding.Validate(testCase.data), func(actual binding.Errors) {
+ m.Post(testRoute, func(resp http.ResponseWriter, req *http.Request) {
+ actual := binding.Validate(req, testCase.data)
// see https://github.com/stretchr/testify/issues/435
if actual == nil {
actual = binding.Errors{}
if err != nil {
panic(err)
}
-
+ req.Header.Add("Content-Type", "x-www-form-urlencoded")
m.ServeHTTP(httpRecorder, req)
switch httpRecorder.Code {
import (
"testing"
- "gitea.com/macaron/binding"
+ "gitea.com/go-chi/binding"
"github.com/gobwas/glob"
)
import (
"testing"
- "gitea.com/macaron/binding"
+ "gitea.com/go-chi/binding"
)
var gitRefNameValidationTestCases = []validationTestCase{
import (
"testing"
- "gitea.com/macaron/binding"
+ "gitea.com/go-chi/binding"
)
var urlValidationTestCases = []validationTestCase{
--- /dev/null
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package web
+
+import (
+ "fmt"
+ "net/http"
+ "reflect"
+ "strings"
+
+ "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/middlewares"
+
+ "gitea.com/go-chi/binding"
+ "github.com/go-chi/chi"
+)
+
+// Wrap converts all kinds of routes to standard library one
+func Wrap(handlers ...interface{}) http.HandlerFunc {
+ if len(handlers) == 0 {
+ panic("No handlers found")
+ }
+
+ for _, handler := range handlers {
+ switch t := handler.(type) {
+ case http.HandlerFunc, func(http.ResponseWriter, *http.Request),
+ func(ctx *context.Context),
+ func(*context.APIContext),
+ func(*context.PrivateContext),
+ func(http.Handler) http.Handler:
+ default:
+ panic(fmt.Sprintf("Unsupported handler type: %#v", t))
+ }
+ }
+ return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
+ for i := 0; i < len(handlers); i++ {
+ handler := handlers[i]
+ switch t := handler.(type) {
+ case http.HandlerFunc:
+ t(resp, req)
+ if r, ok := resp.(context.ResponseWriter); ok && r.Status() > 0 {
+ return
+ }
+ case func(http.ResponseWriter, *http.Request):
+ t(resp, req)
+ if r, ok := resp.(context.ResponseWriter); ok && r.Status() > 0 {
+ return
+ }
+ case func(ctx *context.Context):
+ ctx := context.GetContext(req)
+ t(ctx)
+ if ctx.Written() {
+ return
+ }
+ case func(*context.APIContext):
+ ctx := context.GetAPIContext(req)
+ t(ctx)
+ if ctx.Written() {
+ return
+ }
+ case func(*context.PrivateContext):
+ ctx := context.GetPrivateContext(req)
+ t(ctx)
+ if ctx.Written() {
+ return
+ }
+ case func(http.Handler) http.Handler:
+ var next = http.HandlerFunc(func(http.ResponseWriter, *http.Request) {})
+ t(next).ServeHTTP(resp, req)
+ if r, ok := resp.(context.ResponseWriter); ok && r.Status() > 0 {
+ return
+ }
+ default:
+ panic(fmt.Sprintf("Unsupported handler type: %#v", t))
+ }
+ }
+ })
+}
+
+// Middle wrap a context function as a chi middleware
+func Middle(f func(ctx *context.Context)) func(netx http.Handler) http.Handler {
+ return func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
+ ctx := context.GetContext(req)
+ f(ctx)
+ if ctx.Written() {
+ return
+ }
+ next.ServeHTTP(ctx.Resp, ctx.Req)
+ })
+ }
+}
+
+// MiddleAPI wrap a context function as a chi middleware
+func MiddleAPI(f func(ctx *context.APIContext)) func(netx http.Handler) http.Handler {
+ return func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
+ ctx := context.GetAPIContext(req)
+ f(ctx)
+ if ctx.Written() {
+ return
+ }
+ next.ServeHTTP(ctx.Resp, ctx.Req)
+ })
+ }
+}
+
+// Bind binding an obj to a handler
+func Bind(obj interface{}) http.HandlerFunc {
+ var tp = reflect.TypeOf(obj)
+ if tp.Kind() == reflect.Ptr {
+ tp = tp.Elem()
+ }
+ if tp.Kind() != reflect.Struct {
+ panic("Only structs are allowed to bind")
+ }
+ return Wrap(func(ctx *context.Context) {
+ var theObj = reflect.New(tp).Interface() // create a new form obj for every request but not use obj directly
+ binding.Bind(ctx.Req, theObj)
+ SetForm(ctx, theObj)
+ middlewares.AssignForm(theObj, ctx.Data)
+ })
+}
+
+// SetForm set the form object
+func SetForm(data middlewares.DataStore, obj interface{}) {
+ data.GetData()["__form"] = obj
+}
+
+// GetForm returns the validate form information
+func GetForm(data middlewares.DataStore) interface{} {
+ return data.GetData()["__form"]
+}
+
+// Route defines a route based on chi's router
+type Route struct {
+ R chi.Router
+ curGroupPrefix string
+ curMiddlewares []interface{}
+}
+
+// NewRoute creates a new route
+func NewRoute() *Route {
+ r := chi.NewRouter()
+ return &Route{
+ R: r,
+ curGroupPrefix: "",
+ curMiddlewares: []interface{}{},
+ }
+}
+
+// Use supports two middlewares
+func (r *Route) Use(middlewares ...interface{}) {
+ if r.curGroupPrefix != "" {
+ r.curMiddlewares = append(r.curMiddlewares, middlewares...)
+ } else {
+ for _, middle := range middlewares {
+ switch t := middle.(type) {
+ case func(http.Handler) http.Handler:
+ r.R.Use(t)
+ case func(*context.Context):
+ r.R.Use(Middle(t))
+ case func(*context.APIContext):
+ r.R.Use(MiddleAPI(t))
+ default:
+ panic(fmt.Sprintf("Unsupported middleware type: %#v", t))
+ }
+ }
+ }
+}
+
+// Group mounts a sub-Router along a `pattern` string.
+func (r *Route) Group(pattern string, fn func(), middlewares ...interface{}) {
+ var previousGroupPrefix = r.curGroupPrefix
+ var previousMiddlewares = r.curMiddlewares
+ r.curGroupPrefix += pattern
+ r.curMiddlewares = append(r.curMiddlewares, middlewares...)
+
+ fn()
+
+ r.curGroupPrefix = previousGroupPrefix
+ r.curMiddlewares = previousMiddlewares
+}
+
+func (r *Route) getPattern(pattern string) string {
+ newPattern := r.curGroupPrefix + pattern
+ if !strings.HasPrefix(newPattern, "/") {
+ newPattern = "/" + newPattern
+ }
+ if newPattern == "/" {
+ return newPattern
+ }
+ return strings.TrimSuffix(newPattern, "/")
+}
+
+// Mount attaches another Route along ./pattern/*
+func (r *Route) Mount(pattern string, subR *Route) {
+ var middlewares = make([]interface{}, len(r.curMiddlewares))
+ copy(middlewares, r.curMiddlewares)
+ subR.Use(middlewares...)
+ r.R.Mount(r.getPattern(pattern), subR.R)
+}
+
+// Any delegate requests for all methods
+func (r *Route) Any(pattern string, h ...interface{}) {
+ var middlewares = r.getMiddlewares(h)
+ r.R.HandleFunc(r.getPattern(pattern), Wrap(middlewares...))
+}
+
+// Route delegate special methods
+func (r *Route) Route(pattern string, methods string, h ...interface{}) {
+ p := r.getPattern(pattern)
+ ms := strings.Split(methods, ",")
+ var middlewares = r.getMiddlewares(h)
+ for _, method := range ms {
+ r.R.MethodFunc(strings.TrimSpace(method), p, Wrap(middlewares...))
+ }
+}
+
+// Delete delegate delete method
+func (r *Route) Delete(pattern string, h ...interface{}) {
+ var middlewares = r.getMiddlewares(h)
+ r.R.Delete(r.getPattern(pattern), Wrap(middlewares...))
+}
+
+func (r *Route) getMiddlewares(h []interface{}) []interface{} {
+ var middlewares = make([]interface{}, len(r.curMiddlewares), len(r.curMiddlewares)+len(h))
+ copy(middlewares, r.curMiddlewares)
+ middlewares = append(middlewares, h...)
+ return middlewares
+}
+
+// Get delegate get method
+func (r *Route) Get(pattern string, h ...interface{}) {
+ var middlewares = r.getMiddlewares(h)
+ r.R.Get(r.getPattern(pattern), Wrap(middlewares...))
+}
+
+// Head delegate head method
+func (r *Route) Head(pattern string, h ...interface{}) {
+ var middlewares = r.getMiddlewares(h)
+ r.R.Head(r.getPattern(pattern), Wrap(middlewares...))
+}
+
+// Post delegate post method
+func (r *Route) Post(pattern string, h ...interface{}) {
+ var middlewares = r.getMiddlewares(h)
+ r.R.Post(r.getPattern(pattern), Wrap(middlewares...))
+}
+
+// Put delegate put method
+func (r *Route) Put(pattern string, h ...interface{}) {
+ var middlewares = r.getMiddlewares(h)
+ r.R.Put(r.getPattern(pattern), Wrap(middlewares...))
+}
+
+// Patch delegate patch method
+func (r *Route) Patch(pattern string, h ...interface{}) {
+ var middlewares = r.getMiddlewares(h)
+ r.R.Patch(r.getPattern(pattern), Wrap(middlewares...))
+}
+
+// ServeHTTP implements http.Handler
+func (r *Route) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+ r.R.ServeHTTP(w, req)
+}
+
+// NotFound defines a handler to respond whenever a route could
+// not be found.
+func (r *Route) NotFound(h http.HandlerFunc) {
+ r.R.NotFound(h)
+}
+
+// MethodNotAllowed defines a handler to respond whenever a method is
+// not allowed.
+func (r *Route) MethodNotAllowed(h http.HandlerFunc) {
+ r.R.MethodNotAllowed(h)
+}
+
+// Combo deletegate requests to Combo
+func (r *Route) Combo(pattern string, h ...interface{}) *Combo {
+ return &Combo{r, pattern, h}
+}
+
+// Combo represents a tiny group routes with same pattern
+type Combo struct {
+ r *Route
+ pattern string
+ h []interface{}
+}
+
+// Get deletegate Get method
+func (c *Combo) Get(h ...interface{}) *Combo {
+ c.r.Get(c.pattern, append(c.h, h...)...)
+ return c
+}
+
+// Post deletegate Post method
+func (c *Combo) Post(h ...interface{}) *Combo {
+ c.r.Post(c.pattern, append(c.h, h...)...)
+ return c
+}
+
+// Delete deletegate Delete method
+func (c *Combo) Delete(h ...interface{}) *Combo {
+ c.r.Delete(c.pattern, append(c.h, h...)...)
+ return c
+}
+
+// Put deletegate Put method
+func (c *Combo) Put(h ...interface{}) *Combo {
+ c.r.Put(c.pattern, append(c.h, h...)...)
+ return c
+}
+
+// Patch deletegate Patch method
+func (c *Combo) Patch(h ...interface{}) *Combo {
+ c.r.Patch(c.pattern, append(c.h, h...)...)
+ return c
+}
--- /dev/null
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package web
+
+import (
+ "bytes"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "github.com/go-chi/chi"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestRoute1(t *testing.T) {
+ buff := bytes.NewBufferString("")
+ recorder := httptest.NewRecorder()
+ recorder.Body = buff
+
+ r := NewRoute()
+ r.Get("/{username}/{reponame}/{type:issues|pulls}", func(resp http.ResponseWriter, req *http.Request) {
+ username := chi.URLParam(req, "username")
+ assert.EqualValues(t, "gitea", username)
+ reponame := chi.URLParam(req, "reponame")
+ assert.EqualValues(t, "gitea", reponame)
+ tp := chi.URLParam(req, "type")
+ assert.EqualValues(t, "issues", tp)
+ })
+
+ req, err := http.NewRequest("GET", "http://localhost:8000/gitea/gitea/issues", nil)
+ assert.NoError(t, err)
+ r.ServeHTTP(recorder, req)
+ assert.EqualValues(t, http.StatusOK, recorder.Code)
+}
+
+func TestRoute2(t *testing.T) {
+ buff := bytes.NewBufferString("")
+ recorder := httptest.NewRecorder()
+ recorder.Body = buff
+
+ var route int
+
+ r := NewRoute()
+ r.Group("/{username}/{reponame}", func() {
+ r.Group("", func() {
+ r.Get("/{type:issues|pulls}", func(resp http.ResponseWriter, req *http.Request) {
+ username := chi.URLParam(req, "username")
+ assert.EqualValues(t, "gitea", username)
+ reponame := chi.URLParam(req, "reponame")
+ assert.EqualValues(t, "gitea", reponame)
+ tp := chi.URLParam(req, "type")
+ assert.EqualValues(t, "issues", tp)
+ route = 0
+ })
+
+ r.Get("/{type:issues|pulls}/{index}", func(resp http.ResponseWriter, req *http.Request) {
+ username := chi.URLParam(req, "username")
+ assert.EqualValues(t, "gitea", username)
+ reponame := chi.URLParam(req, "reponame")
+ assert.EqualValues(t, "gitea", reponame)
+ tp := chi.URLParam(req, "type")
+ assert.EqualValues(t, "issues", tp)
+ index := chi.URLParam(req, "index")
+ assert.EqualValues(t, "1", index)
+ route = 1
+ })
+ }, func(resp http.ResponseWriter, req *http.Request) {
+ resp.WriteHeader(200)
+ })
+
+ r.Group("/issues/{index}", func() {
+ r.Get("/view", func(resp http.ResponseWriter, req *http.Request) {
+ username := chi.URLParam(req, "username")
+ assert.EqualValues(t, "gitea", username)
+ reponame := chi.URLParam(req, "reponame")
+ assert.EqualValues(t, "gitea", reponame)
+ index := chi.URLParam(req, "index")
+ assert.EqualValues(t, "1", index)
+ route = 2
+ })
+ })
+ })
+
+ req, err := http.NewRequest("GET", "http://localhost:8000/gitea/gitea/issues", nil)
+ assert.NoError(t, err)
+ r.ServeHTTP(recorder, req)
+ assert.EqualValues(t, http.StatusOK, recorder.Code)
+ assert.EqualValues(t, 0, route)
+
+ req, err = http.NewRequest("GET", "http://localhost:8000/gitea/gitea/issues/1", nil)
+ assert.NoError(t, err)
+ r.ServeHTTP(recorder, req)
+ assert.EqualValues(t, http.StatusOK, recorder.Code)
+ assert.EqualValues(t, 1, route)
+
+ req, err = http.NewRequest("GET", "http://localhost:8000/gitea/gitea/issues/1/view", nil)
+ assert.NoError(t, err)
+ r.ServeHTTP(recorder, req)
+ assert.EqualValues(t, http.StatusOK, recorder.Code)
+ assert.EqualValues(t, 2, route)
+}
+
+func TestRoute3(t *testing.T) {
+ buff := bytes.NewBufferString("")
+ recorder := httptest.NewRecorder()
+ recorder.Body = buff
+
+ var route int
+
+ m := NewRoute()
+ r := NewRoute()
+ r.Mount("/api/v1", m)
+
+ m.Group("/repos", func() {
+ m.Group("/{username}/{reponame}", func() {
+ m.Group("/branch_protections", func() {
+ m.Get("", func(resp http.ResponseWriter, req *http.Request) {
+ route = 0
+ })
+ m.Post("", func(resp http.ResponseWriter, req *http.Request) {
+ route = 1
+ })
+ m.Group("/{name}", func() {
+ m.Get("", func(resp http.ResponseWriter, req *http.Request) {
+ route = 2
+ })
+ m.Patch("", func(resp http.ResponseWriter, req *http.Request) {
+ route = 3
+ })
+ m.Delete("", func(resp http.ResponseWriter, req *http.Request) {
+ route = 4
+ })
+ })
+ })
+ })
+ })
+
+ req, err := http.NewRequest("GET", "http://localhost:8000/api/v1/repos/gitea/gitea/branch_protections", nil)
+ assert.NoError(t, err)
+ r.ServeHTTP(recorder, req)
+ assert.EqualValues(t, http.StatusOK, recorder.Code)
+ assert.EqualValues(t, 0, route)
+
+ req, err = http.NewRequest("POST", "http://localhost:8000/api/v1/repos/gitea/gitea/branch_protections", nil)
+ assert.NoError(t, err)
+ r.ServeHTTP(recorder, req)
+ assert.EqualValues(t, http.StatusOK, recorder.Code, http.StatusOK)
+ assert.EqualValues(t, 1, route)
+
+ req, err = http.NewRequest("GET", "http://localhost:8000/api/v1/repos/gitea/gitea/branch_protections/master", nil)
+ assert.NoError(t, err)
+ r.ServeHTTP(recorder, req)
+ assert.EqualValues(t, http.StatusOK, recorder.Code)
+ assert.EqualValues(t, 2, route)
+
+ req, err = http.NewRequest("PATCH", "http://localhost:8000/api/v1/repos/gitea/gitea/branch_protections/master", nil)
+ assert.NoError(t, err)
+ r.ServeHTTP(recorder, req)
+ assert.EqualValues(t, http.StatusOK, recorder.Code)
+ assert.EqualValues(t, 3, route)
+
+ req, err = http.NewRequest("DELETE", "http://localhost:8000/api/v1/repos/gitea/gitea/branch_protections/master", nil)
+ assert.NoError(t, err)
+ r.ServeHTTP(recorder, req)
+ assert.EqualValues(t, http.StatusOK, recorder.Code)
+ assert.EqualValues(t, 4, route)
+}
settings.trust_model.collaborator.desc=Gültige Signaturen von Mitarbeitern dieses Projekts werden als "vertrauenswürdig" markiert - ( egal ob sie mit dem Committer übereinstimmen oder nicht). Andernfalls werden gültige Signaturen als "nicht vertrauenswürdig" markiert, unabhängig ob die Signatur mit dem Committer übereinstimmt oder nicht.
settings.trust_model.committer=Committer
settings.trust_model.committer.long=Committer: Vertraue Signaturen, die zu Committern passen (Dies stimmt mit GitHub überein und zwingt signierte Commits von Gitea dazu, Gitea als Committer zu haben)
-settings.trust_model.committer.desc=Gültige Signaturen von Mitwirkenden werden als "vertrauenswürdig" gekennzeichnet, wenn sie mit ihrem Committer übereinstimmen. Ansonsten werden sie als "nicht übereinstimmend" markiert. Das führt dazu, dass Gitea auf signierten Commits, bei denen der echte Committer als Co-authored-by: oder Co-committed-by in der Beschreibung eingetragen wurde, als Committer gilt.
-Der Standard Gitea Schlüssel muss auf einen User in der Datenbank zeigen.
+settings.trust_model.committer.desc=Gültige Signaturen von Mitwirkenden werden als "vertrauenswürdig" gekennzeichnet, wenn sie mit ihrem Committer übereinstimmen. Ansonsten werden sie als "nicht übereinstimmend" markiert. Das führt dazu, dass Gitea auf signierten Commits, bei denen der echte Committer als Co-authored-by: oder Co-committed-by in der Beschreibung eingetragen wurde, als Committer gilt. Der Standard Gitea Schlüssel muss auf einen User in der Datenbank zeigen.
settings.trust_model.collaboratorcommitter=Mitarbeiter+Committer
settings.trust_model.collaboratorcommitter.long=Mitarbeiter+Committer: Signaturen der Mitarbeiter vertrauen die mit dem Committer übereinstimmen
settings.trust_model.collaboratorcommitter.desc=Gültige Signaturen von Mitarbeitern dieses Projekts werden als "vertrauenswürdig" markiert, wenn sie mit dem Committer übereinstimmen. Andernfalls werden gültige Signaturen als "nicht vertrauenswürdig" markiert, wenn die Signatur mit dem Committer übereinstimmt als "nicht übereinstimmend". Dies zwingt Gitea als Committer bei signierten Commits mit dem tatsächlichen Committer als Co-Authored-By: und Co-Committed-By: Trailer im Commit. Der Standard-Gitea-Schlüssel muss mit einem Benutzer in der Datenbank übereinstimmen.
"time"
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/cron"
+ auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/queue"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
+ "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/mailer"
- "gitea.com/macaron/macaron"
- "gitea.com/macaron/session"
+ "gitea.com/go-chi/session"
)
const (
}
// DashboardPost run an admin operation
-func DashboardPost(ctx *context.Context, form auth.AdminDashboardForm) {
+func DashboardPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.AdminDashboardForm)
ctx.Data["Title"] = ctx.Tr("admin.dashboard")
ctx.Data["PageIsAdmin"] = true
ctx.Data["PageIsAdminDashboard"] = true
ctx.Data["OfflineMode"] = setting.OfflineMode
ctx.Data["DisableRouterLog"] = setting.DisableRouterLog
ctx.Data["RunUser"] = setting.RunUser
- ctx.Data["RunMode"] = strings.Title(macaron.Env)
+ ctx.Data["RunMode"] = strings.Title(setting.RunMode)
if version, err := git.LocalVersion(); err == nil {
ctx.Data["GitVersion"] = version.Original()
}
"regexp"
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/auth/ldap"
"code.gitea.io/gitea/modules/auth/oauth2"
"code.gitea.io/gitea/modules/auth/pam"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
+ auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/web"
"xorm.io/xorm/convert"
)
}
// NewAuthSourcePost response for adding an auth source
-func NewAuthSourcePost(ctx *context.Context, form auth.AuthenticationForm) {
+func NewAuthSourcePost(ctx *context.Context) {
+ form := *web.GetForm(ctx).(*auth.AuthenticationForm)
ctx.Data["Title"] = ctx.Tr("admin.auths.new")
ctx.Data["PageIsAdmin"] = true
ctx.Data["PageIsAdminAuthentications"] = true
}
// EditAuthSourcePost response for editing auth source
-func EditAuthSourcePost(ctx *context.Context, form auth.AuthenticationForm) {
+func EditAuthSourcePost(ctx *context.Context) {
+ form := *web.GetForm(ctx).(*auth.AuthenticationForm)
ctx.Data["Title"] = ctx.Tr("admin.auths.edit")
ctx.Data["PageIsAdmin"] = true
ctx.Data["PageIsAdminAuthentications"] = true
"strings"
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
+ auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/password"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers"
router_user_setting "code.gitea.io/gitea/routers/user/setting"
"code.gitea.io/gitea/services/mailer"
}
// NewUserPost response for adding a new user
-func NewUserPost(ctx *context.Context, form auth.AdminCreateUserForm) {
+func NewUserPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.AdminCreateUserForm)
ctx.Data["Title"] = ctx.Tr("admin.users.new_account")
ctx.Data["PageIsAdmin"] = true
ctx.Data["PageIsAdminUsers"] = true
}
// EditUserPost response for editting user
-func EditUserPost(ctx *context.Context, form auth.AdminEditUserForm) {
+func EditUserPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.AdminEditUserForm)
ctx.Data["Title"] = ctx.Tr("admin.users.edit_account")
ctx.Data["PageIsAdmin"] = true
ctx.Data["PageIsAdminUsers"] = true
"testing"
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
+ auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/test"
+ "code.gitea.io/gitea/modules/web"
"github.com/stretchr/testify/assert"
)
MustChangePassword: true,
}
- NewUserPost(ctx, form)
+ web.SetForm(ctx, &form)
+ NewUserPost(ctx)
assert.NotEmpty(t, ctx.Flash.SuccessMsg)
MustChangePassword: false,
}
- NewUserPost(ctx, form)
+ web.SetForm(ctx, &form)
+ NewUserPost(ctx)
assert.NotEmpty(t, ctx.Flash.SuccessMsg)
MustChangePassword: false,
}
- NewUserPost(ctx, form)
+ web.SetForm(ctx, &form)
+ NewUserPost(ctx)
assert.NotEmpty(t, ctx.Flash.ErrorMsg)
}
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/user"
"code.gitea.io/gitea/routers/api/v1/utils"
)
// CreateOrg api for create organization
-func CreateOrg(ctx *context.APIContext, form api.CreateOrgOption) {
+func CreateOrg(ctx *context.APIContext) {
// swagger:operation POST /admin/users/{username}/orgs admin adminCreateOrg
// ---
// summary: Create an organization
// "$ref": "#/responses/forbidden"
// "422":
// "$ref": "#/responses/validationError"
-
+ form := web.GetForm(ctx).(*api.CreateOrgOption)
u := user.GetUserByParams(ctx)
if ctx.Written() {
return
import (
"code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/repo"
"code.gitea.io/gitea/routers/api/v1/user"
)
// CreateRepo api for creating a repository
-func CreateRepo(ctx *context.APIContext, form api.CreateRepoOption) {
+func CreateRepo(ctx *context.APIContext) {
// swagger:operation POST /admin/users/{username}/repos admin adminCreateRepo
// ---
// summary: Create a repository on behalf of a user
// "$ref": "#/responses/error"
// "422":
// "$ref": "#/responses/validationError"
-
+ form := web.GetForm(ctx).(*api.CreateRepoOption)
owner := user.GetUserByParams(ctx)
if ctx.Written() {
return
}
- repo.CreateUserRepo(ctx, owner, form)
+ repo.CreateUserRepo(ctx, owner, *form)
}
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/password"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/user"
"code.gitea.io/gitea/routers/api/v1/utils"
"code.gitea.io/gitea/services/mailer"
}
// CreateUser create a user
-func CreateUser(ctx *context.APIContext, form api.CreateUserOption) {
+func CreateUser(ctx *context.APIContext) {
// swagger:operation POST /admin/users admin adminCreateUser
// ---
// summary: Create a user
// "$ref": "#/responses/forbidden"
// "422":
// "$ref": "#/responses/validationError"
-
+ form := web.GetForm(ctx).(*api.CreateUserOption)
u := &models.User{
Name: form.Username,
FullName: form.FullName,
}
// EditUser api for modifying a user's information
-func EditUser(ctx *context.APIContext, form api.EditUserOption) {
+func EditUser(ctx *context.APIContext) {
// swagger:operation PATCH /admin/users/{username} admin adminEditUser
// ---
// summary: Edit an existing user
// "$ref": "#/responses/forbidden"
// "422":
// "$ref": "#/responses/validationError"
-
+ form := web.GetForm(ctx).(*api.EditUserOption)
u := user.GetUserByParams(ctx)
if ctx.Written() {
return
}
// CreatePublicKey api for creating a public key to a user
-func CreatePublicKey(ctx *context.APIContext, form api.CreateKeyOption) {
+func CreatePublicKey(ctx *context.APIContext) {
// swagger:operation POST /admin/users/{username}/keys admin adminCreatePublicKey
// ---
// summary: Add a public key on behalf of a user
// "$ref": "#/responses/forbidden"
// "422":
// "$ref": "#/responses/validationError"
-
+ form := web.GetForm(ctx).(*api.CreateKeyOption)
u := user.GetUserByParams(ctx)
if ctx.Written() {
return
}
- user.CreateUserPublicKey(ctx, form, u.ID)
+ user.CreateUserPublicKey(ctx, *form, u.ID)
}
// DeleteUserPublicKey api for deleting a user's public key
import (
"net/http"
+ "reflect"
"strings"
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/context"
+ auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/admin"
"code.gitea.io/gitea/routers/api/v1/misc"
"code.gitea.io/gitea/routers/api/v1/notify"
_ "code.gitea.io/gitea/routers/api/v1/swagger" // for swagger generation
"code.gitea.io/gitea/routers/api/v1/user"
- "gitea.com/macaron/binding"
- "gitea.com/macaron/macaron"
+ "gitea.com/go-chi/binding"
+ "gitea.com/go-chi/session"
+ "github.com/go-chi/cors"
)
-func sudo() macaron.Handler {
+func sudo() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
sudo := ctx.Query("sudo")
if len(sudo) == 0 {
}
}
-func repoAssignment() macaron.Handler {
+func repoAssignment() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
- userName := ctx.Params(":username")
- repoName := ctx.Params(":reponame")
+ userName := ctx.Params("username")
+ repoName := ctx.Params("reponame")
var (
owner *models.User
}
// Contexter middleware already checks token for user sign in process.
-func reqToken() macaron.Handler {
+func reqToken() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if true == ctx.Data["IsApiToken"] {
return
}
}
-func reqBasicAuth() macaron.Handler {
+func reqBasicAuth() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if !ctx.Context.IsBasicAuth {
ctx.Error(http.StatusUnauthorized, "reqBasicAuth", "basic auth required")
}
// reqSiteAdmin user should be the site admin
-func reqSiteAdmin() macaron.Handler {
+func reqSiteAdmin() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if !ctx.IsUserSiteAdmin() {
ctx.Error(http.StatusForbidden, "reqSiteAdmin", "user should be the site admin")
}
// reqOwner user should be the owner of the repo or site admin.
-func reqOwner() macaron.Handler {
+func reqOwner() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if !ctx.IsUserRepoOwner() && !ctx.IsUserSiteAdmin() {
ctx.Error(http.StatusForbidden, "reqOwner", "user should be the owner of the repo")
}
// reqAdmin user should be an owner or a collaborator with admin write of a repository, or site admin
-func reqAdmin() macaron.Handler {
+func reqAdmin() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() {
ctx.Error(http.StatusForbidden, "reqAdmin", "user should be an owner or a collaborator with admin write of a repository")
}
// reqRepoWriter user should have a permission to write to a repo, or be a site admin
-func reqRepoWriter(unitTypes ...models.UnitType) macaron.Handler {
+func reqRepoWriter(unitTypes ...models.UnitType) func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if !ctx.IsUserRepoWriter(unitTypes) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() {
ctx.Error(http.StatusForbidden, "reqRepoWriter", "user should have a permission to write to a repo")
}
// reqRepoReader user should have specific read permission or be a repo admin or a site admin
-func reqRepoReader(unitType models.UnitType) macaron.Handler {
+func reqRepoReader(unitType models.UnitType) func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if !ctx.IsUserRepoReaderSpecific(unitType) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() {
ctx.Error(http.StatusForbidden, "reqRepoReader", "user should have specific read permission or be a repo admin or a site admin")
}
// reqAnyRepoReader user should have any permission to read repository or permissions of site admin
-func reqAnyRepoReader() macaron.Handler {
+func reqAnyRepoReader() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if !ctx.IsUserRepoReaderAny() && !ctx.IsUserSiteAdmin() {
ctx.Error(http.StatusForbidden, "reqAnyRepoReader", "user should have any permission to read repository or permissions of site admin")
}
// reqOrgOwnership user should be an organization owner, or a site admin
-func reqOrgOwnership() macaron.Handler {
+func reqOrgOwnership() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if ctx.Context.IsUserSiteAdmin() {
return
}
// reqTeamMembership user should be an team member, or a site admin
-func reqTeamMembership() macaron.Handler {
+func reqTeamMembership() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if ctx.Context.IsUserSiteAdmin() {
return
}
// reqOrgMembership user should be an organization member, or a site admin
-func reqOrgMembership() macaron.Handler {
+func reqOrgMembership() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if ctx.Context.IsUserSiteAdmin() {
return
}
}
-func reqGitHook() macaron.Handler {
+func reqGitHook() func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if !ctx.User.CanEditGitHook() {
ctx.Error(http.StatusForbidden, "", "must be allowed to edit Git hooks")
}
}
-func orgAssignment(args ...bool) macaron.Handler {
+func orgAssignment(args ...bool) func(ctx *context.APIContext) {
var (
assignOrg bool
assignTeam bool
}
}
-func mustEnableUserHeatmap(ctx *context.APIContext) {
- if !setting.Service.EnableUserHeatmap {
- ctx.NotFound()
- return
- }
-}
-
func mustNotBeArchived(ctx *context.APIContext) {
if ctx.Repo.Repository.IsArchived {
ctx.NotFound()
}
}
-// RegisterRoutes registers all v1 APIs routes to web application.
-func RegisterRoutes(m *macaron.Macaron) {
- bind := binding.Bind
+// bind binding an obj to a func(ctx *context.APIContext)
+func bind(obj interface{}) http.HandlerFunc {
+ var tp = reflect.TypeOf(obj)
+ for tp.Kind() == reflect.Ptr {
+ tp = tp.Elem()
+ }
+ return web.Wrap(func(ctx *context.APIContext) {
+ var theObj = reflect.New(tp).Interface() // create a new form obj for every request but not use obj directly
+ errs := binding.Bind(ctx.Req, theObj)
+ if len(errs) > 0 {
+ ctx.Error(422, "validationError", errs[0].Error())
+ return
+ }
+ web.SetForm(ctx, theObj)
+ })
+}
- if setting.API.EnableSwagger {
- m.Get("/swagger", misc.Swagger) // Render V1 by default
+// Routes registers all v1 APIs routes to web application.
+func Routes() *web.Route {
+ var m = web.NewRoute()
+
+ m.Use(session.Sessioner(session.Options{
+ Provider: setting.SessionConfig.Provider,
+ ProviderConfig: setting.SessionConfig.ProviderConfig,
+ CookieName: setting.SessionConfig.CookieName,
+ CookiePath: setting.SessionConfig.CookiePath,
+ Gclifetime: setting.SessionConfig.Gclifetime,
+ Maxlifetime: setting.SessionConfig.Maxlifetime,
+ Secure: setting.SessionConfig.Secure,
+ Domain: setting.SessionConfig.Domain,
+ }))
+ m.Use(securityHeaders())
+ if setting.CORSConfig.Enabled {
+ m.Use(cors.Handler(cors.Options{
+ //Scheme: setting.CORSConfig.Scheme, // FIXME: the cors middleware needs scheme option
+ AllowedOrigins: setting.CORSConfig.AllowDomain,
+ //setting.CORSConfig.AllowSubdomain // FIXME: the cors middleware needs allowSubdomain option
+ AllowedMethods: setting.CORSConfig.Methods,
+ AllowCredentials: setting.CORSConfig.AllowCredentials,
+ MaxAge: int(setting.CORSConfig.MaxAge.Seconds()),
+ }))
}
+ m.Use(context.APIContexter())
+ m.Use(context.ToggleAPI(&context.ToggleOptions{
+ SignInRequired: setting.Service.RequireSignInView,
+ }))
- m.Group("/v1", func() {
+ m.Group("", func() {
// Miscellaneous
if setting.API.EnableSwagger {
- m.Get("/swagger", misc.Swagger)
+ m.Get("/swagger", func(ctx *context.APIContext) {
+ ctx.Redirect("/api/swagger")
+ })
}
m.Get("/version", misc.Version)
m.Get("/signing-key.gpg", misc.SigningKey)
Get(notify.ListNotifications).
Put(notify.ReadNotifications)
m.Get("/new", notify.NewAvailable)
- m.Combo("/threads/:id").
+ m.Combo("/threads/{id}").
Get(notify.GetThread).
Patch(notify.ReadThread)
}, reqToken())
m.Group("/users", func() {
m.Get("/search", user.Search)
- m.Group("/:username", func() {
+ m.Group("/{username}", func() {
m.Get("", user.GetInfo)
- m.Get("/heatmap", mustEnableUserHeatmap, user.GetUserHeatmapData)
+
+ if setting.Service.EnableUserHeatmap {
+ m.Get("/heatmap", user.GetUserHeatmapData)
+ }
m.Get("/repos", user.ListUserRepos)
m.Group("/tokens", func() {
m.Combo("").Get(user.ListAccessTokens).
Post(bind(api.CreateAccessTokenOption{}), user.CreateAccessToken)
- m.Combo("/:id").Delete(user.DeleteAccessToken)
+ m.Combo("/{id}").Delete(user.DeleteAccessToken)
}, reqBasicAuth())
})
})
m.Group("/users", func() {
- m.Group("/:username", func() {
+ m.Group("/{username}", func() {
m.Get("/keys", user.ListPublicKeys)
m.Get("/gpg_keys", user.ListGPGKeys)
m.Get("/followers", user.ListFollowers)
m.Group("/following", func() {
m.Get("", user.ListFollowing)
- m.Get("/:target", user.CheckFollowing)
+ m.Get("/{target}", user.CheckFollowing)
})
m.Get("/starred", user.GetStarredRepos)
m.Get("/followers", user.ListMyFollowers)
m.Group("/following", func() {
m.Get("", user.ListMyFollowing)
- m.Combo("/:username").Get(user.CheckMyFollowing).Put(user.Follow).Delete(user.Unfollow)
+ m.Combo("/{username}").Get(user.CheckMyFollowing).Put(user.Follow).Delete(user.Unfollow)
})
m.Group("/keys", func() {
m.Combo("").Get(user.ListMyPublicKeys).
Post(bind(api.CreateKeyOption{}), user.CreatePublicKey)
- m.Combo("/:id").Get(user.GetPublicKey).
+ m.Combo("/{id}").Get(user.GetPublicKey).
Delete(user.DeletePublicKey)
})
m.Group("/applications", func() {
m.Combo("/oauth2").
Get(user.ListOauth2Applications).
Post(bind(api.CreateOAuth2ApplicationOptions{}), user.CreateOauth2Application)
- m.Combo("/oauth2/:id").
+ m.Combo("/oauth2/{id}").
Delete(user.DeleteOauth2Application).
Patch(bind(api.CreateOAuth2ApplicationOptions{}), user.UpdateOauth2Application).
Get(user.GetOauth2Application)
m.Group("/gpg_keys", func() {
m.Combo("").Get(user.ListMyGPGKeys).
Post(bind(api.CreateGPGKeyOption{}), user.CreateGPGKey)
- m.Combo("/:id").Get(user.GetGPGKey).
+ m.Combo("/{id}").Get(user.GetGPGKey).
Delete(user.DeleteGPGKey)
})
m.Group("/starred", func() {
m.Get("", user.GetMyStarredRepos)
- m.Group("/:username/:reponame", func() {
+ m.Group("/{username}/{reponame}", func() {
m.Get("", user.IsStarring)
m.Put("", user.Star)
m.Delete("", user.Unstar)
}, reqToken())
// Repositories
- m.Post("/org/:org/repos", reqToken(), bind(api.CreateRepoOption{}), repo.CreateOrgRepoDeprecated)
+ m.Post("/org/{org}/repos", reqToken(), bind(api.CreateRepoOption{}), repo.CreateOrgRepoDeprecated)
- m.Combo("/repositories/:id", reqToken()).Get(repo.GetByID)
+ m.Combo("/repositories/{id}", reqToken()).Get(repo.GetByID)
m.Group("/repos", func() {
m.Get("/search", repo.Search)
m.Post("/migrate", reqToken(), bind(api.MigrateRepoOptions{}), repo.Migrate)
- m.Group("/:username/:reponame", func() {
+ m.Group("/{username}/{reponame}", func() {
m.Combo("").Get(reqAnyRepoReader(), repo.Get).
Delete(reqToken(), reqOwner(), repo.Delete).
- Patch(reqToken(), reqAdmin(), context.RepoRefForAPI(), bind(api.EditRepoOption{}), repo.Edit)
+ Patch(reqToken(), reqAdmin(), context.RepoRefForAPI, bind(api.EditRepoOption{}), repo.Edit)
m.Post("/transfer", reqOwner(), bind(api.TransferRepoOption{}), repo.Transfer)
m.Combo("/notifications").
Get(reqToken(), notify.ListRepoNotifications).
m.Group("/hooks", func() {
m.Combo("").Get(repo.ListHooks).
Post(bind(api.CreateHookOption{}), repo.CreateHook)
- m.Group("/:id", func() {
+ m.Group("/{id}", func() {
m.Combo("").Get(repo.GetHook).
Patch(bind(api.EditHookOption{}), repo.EditHook).
Delete(repo.DeleteHook)
- m.Post("/tests", context.RepoRefForAPI(), repo.TestHook)
+ m.Post("/tests", context.RepoRefForAPI, repo.TestHook)
})
m.Group("/git", func() {
m.Combo("").Get(repo.ListGitHooks)
- m.Group("/:id", func() {
+ m.Group("/{id}", func() {
m.Combo("").Get(repo.GetGitHook).
Patch(bind(api.EditGitHookOption{}), repo.EditGitHook).
Delete(repo.DeleteGitHook)
}, reqToken(), reqAdmin())
m.Group("/collaborators", func() {
m.Get("", reqAnyRepoReader(), repo.ListCollaborators)
- m.Combo("/:collaborator").Get(reqAnyRepoReader(), repo.IsCollaborator).
+ m.Combo("/{collaborator}").Get(reqAnyRepoReader(), repo.IsCollaborator).
Put(reqAdmin(), bind(api.AddCollaboratorOption{}), repo.AddCollaborator).
Delete(reqAdmin(), repo.DeleteCollaborator)
}, reqToken())
- m.Get("/raw/*", context.RepoRefForAPI(), reqRepoReader(models.UnitTypeCode), repo.GetRawFile)
+ m.Get("/raw/*", context.RepoRefForAPI, reqRepoReader(models.UnitTypeCode), repo.GetRawFile)
m.Get("/archive/*", reqRepoReader(models.UnitTypeCode), repo.GetArchive)
m.Combo("/forks").Get(repo.ListForks).
Post(reqToken(), reqRepoReader(models.UnitTypeCode), bind(api.CreateForkOption{}), repo.CreateFork)
m.Group("/branch_protections", func() {
m.Get("", repo.ListBranchProtections)
m.Post("", bind(api.CreateBranchProtectionOption{}), repo.CreateBranchProtection)
- m.Group("/:name", func() {
+ m.Group("/{name}", func() {
m.Get("", repo.GetBranchProtection)
m.Patch("", bind(api.EditBranchProtectionOption{}), repo.EditBranchProtection)
m.Delete("", repo.DeleteBranchProtection)
m.Group("/keys", func() {
m.Combo("").Get(repo.ListDeployKeys).
Post(bind(api.CreateKeyOption{}), repo.CreateDeployKey)
- m.Combo("/:id").Get(repo.GetDeployKey).
+ m.Combo("/{id}").Get(repo.GetDeployKey).
Delete(repo.DeleteDeploykey)
}, reqToken(), reqAdmin())
m.Group("/times", func() {
m.Combo("").Get(repo.ListTrackedTimesByRepository)
- m.Combo("/:timetrackingusername").Get(repo.ListTrackedTimesByUser)
+ m.Combo("/{timetrackingusername}").Get(repo.ListTrackedTimesByUser)
}, mustEnableIssues, reqToken())
m.Group("/issues", func() {
m.Combo("").Get(repo.ListIssues).
Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueOption{}), repo.CreateIssue)
m.Group("/comments", func() {
m.Get("", repo.ListRepoIssueComments)
- m.Group("/:id", func() {
+ m.Group("/{id}", func() {
m.Combo("").
Get(repo.GetIssueComment).
Patch(mustNotBeArchived, reqToken(), bind(api.EditIssueCommentOption{}), repo.EditIssueComment).
Delete(reqToken(), bind(api.EditReactionOption{}), repo.DeleteIssueCommentReaction)
})
})
- m.Group("/:index", func() {
+ m.Group("/{index}", func() {
m.Combo("").Get(repo.GetIssue).
Patch(reqToken(), bind(api.EditIssueOption{}), repo.EditIssue)
m.Group("/comments", func() {
m.Combo("").Get(repo.ListIssueComments).
Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueCommentOption{}), repo.CreateIssueComment)
- m.Combo("/:id", reqToken()).Patch(bind(api.EditIssueCommentOption{}), repo.EditIssueCommentDeprecated).
+ m.Combo("/{id}", reqToken()).Patch(bind(api.EditIssueCommentOption{}), repo.EditIssueCommentDeprecated).
Delete(repo.DeleteIssueCommentDeprecated)
})
m.Group("/labels", func() {
Post(reqToken(), bind(api.IssueLabelsOption{}), repo.AddIssueLabels).
Put(reqToken(), bind(api.IssueLabelsOption{}), repo.ReplaceIssueLabels).
Delete(reqToken(), repo.ClearIssueLabels)
- m.Delete("/:id", reqToken(), repo.DeleteIssueLabel)
+ m.Delete("/{id}", reqToken(), repo.DeleteIssueLabel)
})
m.Group("/times", func() {
m.Combo("").
Get(repo.ListTrackedTimes).
Post(bind(api.AddTimeOption{}), repo.AddTime).
Delete(repo.ResetIssueTime)
- m.Delete("/:id", repo.DeleteTime)
+ m.Delete("/{id}", repo.DeleteTime)
}, reqToken())
m.Combo("/deadline").Post(reqToken(), bind(api.EditDeadlineOption{}), repo.UpdateIssueDeadline)
m.Group("/stopwatch", func() {
m.Group("/subscriptions", func() {
m.Get("", repo.GetIssueSubscribers)
m.Get("/check", reqToken(), repo.CheckIssueSubscription)
- m.Put("/:user", reqToken(), repo.AddIssueSubscription)
- m.Delete("/:user", reqToken(), repo.DelIssueSubscription)
+ m.Put("/{user}", reqToken(), repo.AddIssueSubscription)
+ m.Delete("/{user}", reqToken(), repo.DelIssueSubscription)
})
m.Combo("/reactions").
Get(repo.GetIssueReactions).
m.Group("/labels", func() {
m.Combo("").Get(repo.ListLabels).
Post(reqToken(), reqRepoWriter(models.UnitTypeIssues, models.UnitTypePullRequests), bind(api.CreateLabelOption{}), repo.CreateLabel)
- m.Combo("/:id").Get(repo.GetLabel).
+ m.Combo("/{id}").Get(repo.GetLabel).
Patch(reqToken(), reqRepoWriter(models.UnitTypeIssues, models.UnitTypePullRequests), bind(api.EditLabelOption{}), repo.EditLabel).
Delete(reqToken(), reqRepoWriter(models.UnitTypeIssues, models.UnitTypePullRequests), repo.DeleteLabel)
})
m.Group("/milestones", func() {
m.Combo("").Get(repo.ListMilestones).
Post(reqToken(), reqRepoWriter(models.UnitTypeIssues, models.UnitTypePullRequests), bind(api.CreateMilestoneOption{}), repo.CreateMilestone)
- m.Combo("/:id").Get(repo.GetMilestone).
+ m.Combo("/{id}").Get(repo.GetMilestone).
Patch(reqToken(), reqRepoWriter(models.UnitTypeIssues, models.UnitTypePullRequests), bind(api.EditMilestoneOption{}), repo.EditMilestone).
Delete(reqToken(), reqRepoWriter(models.UnitTypeIssues, models.UnitTypePullRequests), repo.DeleteMilestone)
})
m.Group("/releases", func() {
m.Combo("").Get(repo.ListReleases).
Post(reqToken(), reqRepoWriter(models.UnitTypeReleases), context.ReferencesGitRepo(false), bind(api.CreateReleaseOption{}), repo.CreateRelease)
- m.Group("/:id", func() {
+ m.Group("/{id}", func() {
m.Combo("").Get(repo.GetRelease).
Patch(reqToken(), reqRepoWriter(models.UnitTypeReleases), context.ReferencesGitRepo(false), bind(api.EditReleaseOption{}), repo.EditRelease).
Delete(reqToken(), reqRepoWriter(models.UnitTypeReleases), repo.DeleteRelease)
m.Group("/assets", func() {
m.Combo("").Get(repo.ListReleaseAttachments).
Post(reqToken(), reqRepoWriter(models.UnitTypeReleases), repo.CreateReleaseAttachment)
- m.Combo("/:asset").Get(repo.GetReleaseAttachment).
+ m.Combo("/{asset}").Get(repo.GetReleaseAttachment).
Patch(reqToken(), reqRepoWriter(models.UnitTypeReleases), bind(api.EditAttachmentOptions{}), repo.EditReleaseAttachment).
Delete(reqToken(), reqRepoWriter(models.UnitTypeReleases), repo.DeleteReleaseAttachment)
})
})
m.Group("/tags", func() {
- m.Combo("/:tag").
+ m.Combo("/{tag}").
Get(repo.GetReleaseTag).
Delete(reqToken(), reqRepoWriter(models.UnitTypeReleases), repo.DeleteReleaseTag)
})
}, reqRepoReader(models.UnitTypeReleases))
m.Post("/mirror-sync", reqToken(), reqRepoWriter(models.UnitTypeCode), repo.MirrorSync)
- m.Get("/editorconfig/:filename", context.RepoRefForAPI(), reqRepoReader(models.UnitTypeCode), repo.GetEditorconfig)
+ m.Get("/editorconfig/{filename}", context.RepoRefForAPI, reqRepoReader(models.UnitTypeCode), repo.GetEditorconfig)
m.Group("/pulls", func() {
- m.Combo("").Get(bind(api.ListPullRequestsOptions{}), repo.ListPullRequests).
+ m.Combo("").Get(repo.ListPullRequests).
Post(reqToken(), mustNotBeArchived, bind(api.CreatePullRequestOption{}), repo.CreatePullRequest)
- m.Group("/:index", func() {
+ m.Group("/{index}", func() {
m.Combo("").Get(repo.GetPullRequest).
Patch(reqToken(), reqRepoWriter(models.UnitTypePullRequests), bind(api.EditPullRequestOption{}), repo.EditPullRequest)
m.Get(".diff", repo.DownloadPullDiff)
m.Combo("").
Get(repo.ListPullReviews).
Post(reqToken(), bind(api.CreatePullReviewOptions{}), repo.CreatePullReview)
- m.Group("/:id", func() {
+ m.Group("/{id}", func() {
m.Combo("").
Get(repo.GetPullReview).
Delete(reqToken(), repo.DeletePullReview).
})
}, mustAllowPulls, reqRepoReader(models.UnitTypeCode), context.ReferencesGitRepo(false))
m.Group("/statuses", func() {
- m.Combo("/:sha").Get(repo.GetCommitStatuses).
+ m.Combo("/{sha}").Get(repo.GetCommitStatuses).
Post(reqToken(), bind(api.CreateStatusOption{}), repo.NewCommitStatus)
}, reqRepoReader(models.UnitTypeCode))
m.Group("/commits", func() {
m.Get("", repo.GetAllCommits)
- m.Group("/:ref", func() {
+ m.Group("/{ref}", func() {
m.Get("/status", repo.GetCombinedCommitStatusByRef)
m.Get("/statuses", repo.GetCommitStatusesByRef)
})
}, reqRepoReader(models.UnitTypeCode))
m.Group("/git", func() {
m.Group("/commits", func() {
- m.Get("/:sha", repo.GetSingleCommit)
+ m.Get("/{sha}", repo.GetSingleCommit)
})
m.Get("/refs", repo.GetGitAllRefs)
m.Get("/refs/*", repo.GetGitRefs)
- m.Get("/trees/:sha", context.RepoRefForAPI(), repo.GetTree)
- m.Get("/blobs/:sha", context.RepoRefForAPI(), repo.GetBlob)
- m.Get("/tags/:sha", context.RepoRefForAPI(), repo.GetTag)
+ m.Get("/trees/{sha}", context.RepoRefForAPI, repo.GetTree)
+ m.Get("/blobs/{sha}", context.RepoRefForAPI, repo.GetBlob)
+ m.Get("/tags/{sha}", context.RepoRefForAPI, repo.GetTag)
}, reqRepoReader(models.UnitTypeCode))
m.Group("/contents", func() {
m.Get("", repo.GetContentsList)
m.Group("/topics", func() {
m.Combo("").Get(repo.ListTopics).
Put(reqToken(), reqAdmin(), bind(api.RepoTopicOptions{}), repo.UpdateTopics)
- m.Group("/:topic", func() {
+ m.Group("/{topic}", func() {
m.Combo("").Put(reqToken(), repo.AddTopic).
Delete(reqToken(), repo.DeleteTopic)
}, reqAdmin())
// Organizations
m.Get("/user/orgs", reqToken(), org.ListMyOrgs)
- m.Get("/users/:username/orgs", org.ListUserOrgs)
+ m.Get("/users/{username}/orgs", org.ListUserOrgs)
m.Post("/orgs", reqToken(), bind(api.CreateOrgOption{}), org.Create)
m.Get("/orgs", org.GetAll)
- m.Group("/orgs/:org", func() {
+ m.Group("/orgs/{org}", func() {
m.Combo("").Get(org.Get).
Patch(reqToken(), reqOrgOwnership(), bind(api.EditOrgOption{}), org.Edit).
Delete(reqToken(), reqOrgOwnership(), org.Delete)
Post(reqToken(), bind(api.CreateRepoOption{}), repo.CreateOrgRepo)
m.Group("/members", func() {
m.Get("", org.ListMembers)
- m.Combo("/:username").Get(org.IsMember).
+ m.Combo("/{username}").Get(org.IsMember).
Delete(reqToken(), reqOrgOwnership(), org.DeleteMember)
})
m.Group("/public_members", func() {
m.Get("", org.ListPublicMembers)
- m.Combo("/:username").Get(org.IsPublicMember).
+ m.Combo("/{username}").Get(org.IsPublicMember).
Put(reqToken(), reqOrgMembership(), org.PublicizeMember).
Delete(reqToken(), reqOrgMembership(), org.ConcealMember)
})
m.Group("/labels", func() {
m.Get("", org.ListLabels)
m.Post("", reqToken(), reqOrgOwnership(), bind(api.CreateLabelOption{}), org.CreateLabel)
- m.Combo("/:id").Get(org.GetLabel).
+ m.Combo("/{id}").Get(org.GetLabel).
Patch(reqToken(), reqOrgOwnership(), bind(api.EditLabelOption{}), org.EditLabel).
Delete(reqToken(), reqOrgOwnership(), org.DeleteLabel)
})
m.Group("/hooks", func() {
m.Combo("").Get(org.ListHooks).
Post(bind(api.CreateHookOption{}), org.CreateHook)
- m.Combo("/:id").Get(org.GetHook).
+ m.Combo("/{id}").Get(org.GetHook).
Patch(bind(api.EditHookOption{}), org.EditHook).
Delete(org.DeleteHook)
}, reqToken(), reqOrgOwnership())
}, orgAssignment(true))
- m.Group("/teams/:teamid", func() {
+ m.Group("/teams/{teamid}", func() {
m.Combo("").Get(org.GetTeam).
Patch(reqOrgOwnership(), bind(api.EditTeamOption{}), org.EditTeam).
Delete(reqOrgOwnership(), org.DeleteTeam)
m.Group("/members", func() {
m.Get("", org.GetTeamMembers)
- m.Combo("/:username").
+ m.Combo("/{username}").
Get(org.GetTeamMember).
Put(reqOrgOwnership(), org.AddTeamMember).
Delete(reqOrgOwnership(), org.RemoveTeamMember)
})
m.Group("/repos", func() {
m.Get("", org.GetTeamRepos)
- m.Combo("/:org/:reponame").
+ m.Combo("/{org}/{reponame}").
Put(org.AddTeamRepository).
Delete(org.RemoveTeamRepository)
})
}, orgAssignment(false, true), reqToken(), reqTeamMembership())
- m.Any("/*", func(ctx *context.APIContext) {
- ctx.NotFound()
- })
-
m.Group("/admin", func() {
m.Group("/cron", func() {
m.Get("", admin.ListCronTasks)
- m.Post("/:task", admin.PostCronTask)
+ m.Post("/{task}", admin.PostCronTask)
})
m.Get("/orgs", admin.GetAllOrgs)
m.Group("/users", func() {
m.Get("", admin.GetAllUsers)
m.Post("", bind(api.CreateUserOption{}), admin.CreateUser)
- m.Group("/:username", func() {
+ m.Group("/{username}", func() {
m.Combo("").Patch(bind(api.EditUserOption{}), admin.EditUser).
Delete(admin.DeleteUser)
m.Group("/keys", func() {
m.Post("", bind(api.CreateKeyOption{}), admin.CreatePublicKey)
- m.Delete("/:id", admin.DeleteUserPublicKey)
+ m.Delete("/{id}", admin.DeleteUserPublicKey)
})
m.Get("/orgs", org.ListUserOrgs)
m.Post("/orgs", bind(api.CreateOrgOption{}), admin.CreateOrg)
})
m.Group("/unadopted", func() {
m.Get("", admin.ListUnadoptedRepositories)
- m.Post("/:username/:reponame", admin.AdoptRepository)
- m.Delete("/:username/:reponame", admin.DeleteUnadoptedRepository)
+ m.Post("/{username}/{reponame}", admin.AdoptRepository)
+ m.Delete("/{username}/{reponame}", admin.DeleteUnadoptedRepository)
})
}, reqToken(), reqSiteAdmin())
m.Group("/topics", func() {
m.Get("/search", repo.TopicSearch)
})
- }, securityHeaders(), context.APIContexter(), sudo())
+ }, sudo())
+
+ return m
}
-func securityHeaders() macaron.Handler {
- return func(ctx *macaron.Context) {
- ctx.Resp.Before(func(w macaron.ResponseWriter) {
+func securityHeaders() func(http.Handler) http.Handler {
+ return func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
// CORB: https://www.chromium.org/Home/chromium-security/corb-for-developers
// http://stackoverflow.com/a/3146618/244009
- w.Header().Set("x-content-type-options", "nosniff")
+ resp.Header().Set("x-content-type-options", "nosniff")
+ next.ServeHTTP(resp, req)
})
}
}
package misc
import (
+ "io/ioutil"
"net/http"
"strings"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/web"
"mvdan.cc/xurls/v2"
)
// Markdown render markdown document to HTML
-func Markdown(ctx *context.APIContext, form api.MarkdownOption) {
+func Markdown(ctx *context.APIContext) {
// swagger:operation POST /markdown miscellaneous renderMarkdown
// ---
// summary: Render a markdown document as HTML
// "422":
// "$ref": "#/responses/validationError"
+ form := web.GetForm(ctx).(*api.MarkdownOption)
+
if ctx.HasAPIError() {
ctx.Error(http.StatusUnprocessableEntity, "", ctx.GetErrMsg())
return
// "422":
// "$ref": "#/responses/validationError"
- body, err := ctx.Req.Body().Bytes()
+ body, err := ioutil.ReadAll(ctx.Req.Body)
if err != nil {
ctx.Error(http.StatusUnprocessableEntity, "", err)
return
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/web"
- "gitea.com/macaron/inject"
- "gitea.com/macaron/macaron"
"github.com/stretchr/testify/assert"
)
const Repo = "gogits/gogs"
const AppSubURL = AppURL + Repo + "/"
-func createContext(req *http.Request) (*macaron.Context, *httptest.ResponseRecorder) {
+func createContext(req *http.Request) (*context.Context, *httptest.ResponseRecorder) {
+ var rnd = templates.HTMLRenderer()
resp := httptest.NewRecorder()
- c := &macaron.Context{
- Injector: inject.New(),
- Req: macaron.Request{Request: req},
- Resp: macaron.NewResponseWriter(req.Method, resp),
- Render: &macaron.DummyRender{ResponseWriter: resp},
- Data: make(map[string]interface{}),
+ c := &context.Context{
+ Req: req,
+ Resp: context.NewResponse(resp),
+ Render: rnd,
+ Data: make(map[string]interface{}),
}
- c.Map(c)
- c.Map(req)
return c, resp
}
-func wrap(ctx *macaron.Context) *context.APIContext {
+func wrap(ctx *context.Context) *context.APIContext {
return &context.APIContext{
- Context: &context.Context{
- Context: ctx,
- },
+ Context: ctx,
}
}
for i := 0; i < len(testCases); i += 2 {
options.Text = testCases[i]
- Markdown(ctx, options)
+ web.SetForm(ctx, &options)
+ Markdown(ctx)
assert.Equal(t, testCases[i+1], resp.Body.String())
resp.Body.Reset()
}
for i := 0; i < len(simpleCases); i += 2 {
options.Text = simpleCases[i]
- Markdown(ctx, options)
+ web.SetForm(ctx, &options)
+ Markdown(ctx)
assert.Equal(t, simpleCases[i+1], resp.Body.String())
resp.Body.Reset()
}
ctx := wrap(m)
for i := 0; i < len(simpleCases); i += 2 {
- ctx.Req.Request.Body = ioutil.NopCloser(strings.NewReader(simpleCases[i]))
+ ctx.Req.Body = ioutil.NopCloser(strings.NewReader(simpleCases[i]))
MarkdownRaw(ctx)
assert.Equal(t, simpleCases[i+1], resp.Body.String())
resp.Body.Reset()
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
)
}
// CreateHook create a hook for an organization
-func CreateHook(ctx *context.APIContext, form api.CreateHookOption) {
+func CreateHook(ctx *context.APIContext) {
// swagger:operation POST /orgs/{org}/hooks/ organization orgCreateHook
// ---
// summary: Create a hook
// "201":
// "$ref": "#/responses/Hook"
+ form := web.GetForm(ctx).(*api.CreateHookOption)
//TODO in body params
- if !utils.CheckCreateHookOption(ctx, &form) {
+ if !utils.CheckCreateHookOption(ctx, form) {
return
}
- utils.AddOrgHook(ctx, &form)
+ utils.AddOrgHook(ctx, form)
}
// EditHook modify a hook of a repository
-func EditHook(ctx *context.APIContext, form api.EditHookOption) {
+func EditHook(ctx *context.APIContext) {
// swagger:operation PATCH /orgs/{org}/hooks/{id} organization orgEditHook
// ---
// summary: Update a hook
// "200":
// "$ref": "#/responses/Hook"
+ form := web.GetForm(ctx).(*api.EditHookOption)
+
//TODO in body params
hookID := ctx.ParamsInt64(":id")
- utils.EditOrgHook(ctx, &form, hookID)
+ utils.EditOrgHook(ctx, form, hookID)
}
// DeleteHook delete a hook of an organization
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
)
}
// CreateLabel create a label for a repository
-func CreateLabel(ctx *context.APIContext, form api.CreateLabelOption) {
+func CreateLabel(ctx *context.APIContext) {
// swagger:operation POST /orgs/{org}/labels organization orgCreateLabel
// ---
// summary: Create a label for an organization
// "$ref": "#/responses/Label"
// "422":
// "$ref": "#/responses/validationError"
-
+ form := web.GetForm(ctx).(*api.CreateLabelOption)
form.Color = strings.Trim(form.Color, " ")
if len(form.Color) == 6 {
form.Color = "#" + form.Color
}
// EditLabel modify a label for an Organization
-func EditLabel(ctx *context.APIContext, form api.EditLabelOption) {
+func EditLabel(ctx *context.APIContext) {
// swagger:operation PATCH /orgs/{org}/labels/{id} organization orgEditLabel
// ---
// summary: Update a label
// "$ref": "#/responses/Label"
// "422":
// "$ref": "#/responses/validationError"
-
+ form := web.GetForm(ctx).(*api.EditLabelOption)
label, err := models.GetLabelInOrgByID(ctx.Org.Organization.ID, ctx.ParamsInt64(":id"))
if err != nil {
if models.IsErrOrgLabelNotExist(err) {
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/user"
"code.gitea.io/gitea/routers/api/v1/utils"
)
}
// Create api for create organization
-func Create(ctx *context.APIContext, form api.CreateOrgOption) {
+func Create(ctx *context.APIContext) {
// swagger:operation POST /orgs organization orgCreate
// ---
// summary: Create an organization
// "$ref": "#/responses/forbidden"
// "422":
// "$ref": "#/responses/validationError"
-
+ form := web.GetForm(ctx).(*api.CreateOrgOption)
if !ctx.User.CanCreateOrganization() {
ctx.Error(http.StatusForbidden, "Create organization not allowed", nil)
return
}
// Edit change an organization's information
-func Edit(ctx *context.APIContext, form api.EditOrgOption) {
+func Edit(ctx *context.APIContext) {
// swagger:operation PATCH /orgs/{org} organization orgEdit
// ---
// summary: Edit an organization
// responses:
// "200":
// "$ref": "#/responses/Organization"
-
+ form := web.GetForm(ctx).(*api.EditOrgOption)
org := ctx.Org.Organization
org.FullName = form.FullName
org.Description = form.Description
"code.gitea.io/gitea/modules/convert"
"code.gitea.io/gitea/modules/log"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/user"
"code.gitea.io/gitea/routers/api/v1/utils"
)
}
// CreateTeam api for create a team
-func CreateTeam(ctx *context.APIContext, form api.CreateTeamOption) {
+func CreateTeam(ctx *context.APIContext) {
// swagger:operation POST /orgs/{org}/teams organization orgCreateTeam
// ---
// summary: Create a team
// "$ref": "#/responses/Team"
// "422":
// "$ref": "#/responses/validationError"
-
+ form := web.GetForm(ctx).(*api.CreateTeamOption)
team := &models.Team{
OrgID: ctx.Org.Organization.ID,
Name: form.Name,
}
// EditTeam api for edit a team
-func EditTeam(ctx *context.APIContext, form api.EditTeamOption) {
+func EditTeam(ctx *context.APIContext) {
// swagger:operation PATCH /teams/{id} organization orgEditTeam
// ---
// summary: Edit a team
// "200":
// "$ref": "#/responses/Team"
+ form := web.GetForm(ctx).(*api.EditTeamOption)
+
team := ctx.Org.Team
if err := team.GetUnits(); err != nil {
ctx.InternalServerError(err)
"code.gitea.io/gitea/modules/log"
repo_module "code.gitea.io/gitea/modules/repository"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/web"
pull_service "code.gitea.io/gitea/services/pull"
repo_service "code.gitea.io/gitea/services/repository"
)
}
// CreateBranch creates a branch for a user's repository
-func CreateBranch(ctx *context.APIContext, opt api.CreateBranchRepoOption) {
+func CreateBranch(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/branches repository repoCreateBranch
// ---
// summary: Create a branch
// "409":
// description: The branch with the same name already exists.
+ opt := web.GetForm(ctx).(*api.CreateBranchRepoOption)
if ctx.Repo.Repository.IsEmpty {
ctx.Error(http.StatusNotFound, "", "Git Repository is empty.")
return
}
// CreateBranchProtection creates a branch protection for a repo
-func CreateBranchProtection(ctx *context.APIContext, form api.CreateBranchProtectionOption) {
+func CreateBranchProtection(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/branch_protections repository repoCreateBranchProtection
// ---
// summary: Create a branch protections for a repository
// "422":
// "$ref": "#/responses/validationError"
+ form := web.GetForm(ctx).(*api.CreateBranchProtectionOption)
repo := ctx.Repo.Repository
// Currently protection must match an actual branch
}
// EditBranchProtection edits a branch protection for a repo
-func EditBranchProtection(ctx *context.APIContext, form api.EditBranchProtectionOption) {
+func EditBranchProtection(ctx *context.APIContext) {
// swagger:operation PATCH /repos/{owner}/{repo}/branch_protections/{name} repository repoEditBranchProtection
// ---
// summary: Edit a branch protections for a repository. Only fields that are set will be changed
// "$ref": "#/responses/notFound"
// "422":
// "$ref": "#/responses/validationError"
-
+ form := web.GetForm(ctx).(*api.EditBranchProtectionOption)
repo := ctx.Repo.Repository
bpName := ctx.Params(":name")
protectBranch, err := models.GetProtectedBranchBy(repo.ID, bpName)
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
)
}
// AddCollaborator add a collaborator to a repository
-func AddCollaborator(ctx *context.APIContext, form api.AddCollaboratorOption) {
+func AddCollaborator(ctx *context.APIContext) {
// swagger:operation PUT /repos/{owner}/{repo}/collaborators/{collaborator} repository repoAddCollaborator
// ---
// summary: Add a collaborator to a repository
// "422":
// "$ref": "#/responses/validationError"
+ form := web.GetForm(ctx).(*api.AddCollaboratorOption)
+
collaborator, err := models.GetUserByName(ctx.Params(":collaborator"))
if err != nil {
if models.IsErrUserNotExist(err) {
defer gitRepo.Close()
commit, err := gitRepo.GetCommit(identifier)
if err != nil {
- ctx.NotFoundOrServerError("GetCommit", git.IsErrNotExist, err)
+ if git.IsErrNotExist(err) {
+ ctx.NotFound(identifier)
+ return
+ }
+ ctx.Error(http.StatusInternalServerError, "gitRepo.GetCommit", err)
return
}
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/repofiles"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/repo"
)
}
// CreateFile handles API call for creating a file
-func CreateFile(ctx *context.APIContext, apiOpts api.CreateFileOptions) {
+func CreateFile(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/contents/{filepath} repository repoCreateFile
// ---
// summary: Create a file in a repository
// "422":
// "$ref": "#/responses/error"
+ apiOpts := web.GetForm(ctx).(*api.CreateFileOptions)
if ctx.Repo.Repository.IsEmpty {
ctx.Error(http.StatusUnprocessableEntity, "RepoIsEmpty", fmt.Errorf("repo is empty"))
}
}
// UpdateFile handles API call for updating a file
-func UpdateFile(ctx *context.APIContext, apiOpts api.UpdateFileOptions) {
+func UpdateFile(ctx *context.APIContext) {
// swagger:operation PUT /repos/{owner}/{repo}/contents/{filepath} repository repoUpdateFile
// ---
// summary: Update a file in a repository
// "$ref": "#/responses/notFound"
// "422":
// "$ref": "#/responses/error"
-
+ apiOpts := web.GetForm(ctx).(*api.UpdateFileOptions)
if ctx.Repo.Repository.IsEmpty {
ctx.Error(http.StatusUnprocessableEntity, "RepoIsEmpty", fmt.Errorf("repo is empty"))
}
}
// DeleteFile Delete a fle in a repository
-func DeleteFile(ctx *context.APIContext, apiOpts api.DeleteFileOptions) {
+func DeleteFile(ctx *context.APIContext) {
// swagger:operation DELETE /repos/{owner}/{repo}/contents/{filepath} repository repoDeleteFile
// ---
// summary: Delete a file in a repository
// "404":
// "$ref": "#/responses/error"
+ apiOpts := web.GetForm(ctx).(*api.DeleteFileOptions)
if !canWriteFiles(ctx.Repo) {
ctx.Error(http.StatusForbidden, "DeleteFile", models.ErrUserDoesNotHaveAccessToRepo{
UserID: ctx.User.ID,
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
repo_service "code.gitea.io/gitea/services/repository"
)
}
// CreateFork create a fork of a repo
-func CreateFork(ctx *context.APIContext, form api.CreateForkOption) {
+func CreateFork(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/forks repository createFork
// ---
// summary: Fork a repository
// "422":
// "$ref": "#/responses/validationError"
+ form := web.GetForm(ctx).(*api.CreateForkOption)
repo := ctx.Repo.Repository
var forker *models.User // user/org that will own the fork
if form.Organization == nil {
"code.gitea.io/gitea/modules/convert"
"code.gitea.io/gitea/modules/git"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/web"
)
// ListGitHooks list all Git hooks of a repository
}
// EditGitHook modify a Git hook of a repository
-func EditGitHook(ctx *context.APIContext, form api.EditGitHookOption) {
+func EditGitHook(ctx *context.APIContext) {
// swagger:operation PATCH /repos/{owner}/{repo}/hooks/git/{id} repository repoEditGitHook
// ---
// summary: Edit a Git hook in a repository
// "404":
// "$ref": "#/responses/notFound"
+ form := web.GetForm(ctx).(*api.EditGitHookOption)
hookID := ctx.Params(":id")
hook, err := ctx.Repo.GitRepo.GetHook(hookID)
if err != nil {
"code.gitea.io/gitea/modules/convert"
"code.gitea.io/gitea/modules/git"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
"code.gitea.io/gitea/services/webhook"
)
}
// CreateHook create a hook for a repository
-func CreateHook(ctx *context.APIContext, form api.CreateHookOption) {
+func CreateHook(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/hooks repository repoCreateHook
// ---
// summary: Create a hook
// responses:
// "201":
// "$ref": "#/responses/Hook"
- if !utils.CheckCreateHookOption(ctx, &form) {
+ form := web.GetForm(ctx).(*api.CreateHookOption)
+
+ if !utils.CheckCreateHookOption(ctx, form) {
return
}
- utils.AddRepoHook(ctx, &form)
+ utils.AddRepoHook(ctx, form)
}
// EditHook modify a hook of a repository
-func EditHook(ctx *context.APIContext, form api.EditHookOption) {
+func EditHook(ctx *context.APIContext) {
// swagger:operation PATCH /repos/{owner}/{repo}/hooks/{id} repository repoEditHook
// ---
// summary: Edit a hook in a repository
// responses:
// "200":
// "$ref": "#/responses/Hook"
+ form := web.GetForm(ctx).(*api.EditHookOption)
hookID := ctx.ParamsInt64(":id")
- utils.EditRepoHook(ctx, &form, hookID)
+ utils.EditRepoHook(ctx, form, hookID)
}
// DeleteHook delete a hook of a repository
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
issue_service "code.gitea.io/gitea/services/issue"
)
}
// CreateIssue create an issue of a repository
-func CreateIssue(ctx *context.APIContext, form api.CreateIssueOption) {
+func CreateIssue(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/issues issue issueCreateIssue
// ---
// summary: Create an issue. If using deadline only the date will be taken into account, and time of day ignored.
// "$ref": "#/responses/error"
// "422":
// "$ref": "#/responses/validationError"
-
+ form := web.GetForm(ctx).(*api.CreateIssueOption)
var deadlineUnix timeutil.TimeStamp
if form.Deadline != nil && ctx.Repo.CanWrite(models.UnitTypeIssues) {
deadlineUnix = timeutil.TimeStamp(form.Deadline.Unix())
}
// EditIssue modify an issue of a repository
-func EditIssue(ctx *context.APIContext, form api.EditIssueOption) {
+func EditIssue(ctx *context.APIContext) {
// swagger:operation PATCH /repos/{owner}/{repo}/issues/{index} issue issueEditIssue
// ---
// summary: Edit an issue. If using deadline only the date will be taken into account, and time of day ignored.
// "412":
// "$ref": "#/responses/error"
+ form := web.GetForm(ctx).(*api.EditIssueOption)
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
if models.IsErrIssueNotExist(err) {
}
// UpdateIssueDeadline updates an issue deadline
-func UpdateIssueDeadline(ctx *context.APIContext, form api.EditDeadlineOption) {
+func UpdateIssueDeadline(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/issues/{index}/deadline issue issueEditIssueDeadline
// ---
// summary: Set an issue deadline. If set to null, the deadline is deleted. If using deadline only the date will be taken into account, and time of day ignored.
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
-
+ form := web.GetForm(ctx).(*api.EditDeadlineOption)
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
if models.IsErrIssueNotExist(err) {
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
comment_service "code.gitea.io/gitea/services/comments"
)
}
// CreateIssueComment create a comment for an issue
-func CreateIssueComment(ctx *context.APIContext, form api.CreateIssueCommentOption) {
+func CreateIssueComment(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/issues/{index}/comments issue issueCreateComment
// ---
// summary: Add a comment to an issue
// "$ref": "#/responses/Comment"
// "403":
// "$ref": "#/responses/forbidden"
-
+ form := web.GetForm(ctx).(*api.CreateIssueCommentOption)
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
}
// EditIssueComment modify a comment of an issue
-func EditIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption) {
+func EditIssueComment(ctx *context.APIContext) {
// swagger:operation PATCH /repos/{owner}/{repo}/issues/comments/{id} issue issueEditComment
// ---
// summary: Edit a comment
// "404":
// "$ref": "#/responses/notFound"
- editIssueComment(ctx, form)
+ form := web.GetForm(ctx).(*api.EditIssueCommentOption)
+ editIssueComment(ctx, *form)
}
// EditIssueCommentDeprecated modify a comment of an issue
-func EditIssueCommentDeprecated(ctx *context.APIContext, form api.EditIssueCommentOption) {
+func EditIssueCommentDeprecated(ctx *context.APIContext) {
// swagger:operation PATCH /repos/{owner}/{repo}/issues/{index}/comments/{id} issue issueEditCommentDeprecated
// ---
// summary: Edit a comment
// "404":
// "$ref": "#/responses/notFound"
- editIssueComment(ctx, form)
+ form := web.GetForm(ctx).(*api.EditIssueCommentOption)
+ editIssueComment(ctx, *form)
}
func editIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption) {
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/web"
issue_service "code.gitea.io/gitea/services/issue"
)
}
// AddIssueLabels add labels for an issue
-func AddIssueLabels(ctx *context.APIContext, form api.IssueLabelsOption) {
+func AddIssueLabels(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/issues/{index}/labels issue issueAddLabel
// ---
// summary: Add a label to an issue
// "403":
// "$ref": "#/responses/forbidden"
- issue, labels, err := prepareForReplaceOrAdd(ctx, form)
+ form := web.GetForm(ctx).(*api.IssueLabelsOption)
+ issue, labels, err := prepareForReplaceOrAdd(ctx, *form)
if err != nil {
return
}
}
// ReplaceIssueLabels replace labels for an issue
-func ReplaceIssueLabels(ctx *context.APIContext, form api.IssueLabelsOption) {
+func ReplaceIssueLabels(ctx *context.APIContext) {
// swagger:operation PUT /repos/{owner}/{repo}/issues/{index}/labels issue issueReplaceLabels
// ---
// summary: Replace an issue's labels
// "$ref": "#/responses/LabelList"
// "403":
// "$ref": "#/responses/forbidden"
-
- issue, labels, err := prepareForReplaceOrAdd(ctx, form)
+ form := web.GetForm(ctx).(*api.IssueLabelsOption)
+ issue, labels, err := prepareForReplaceOrAdd(ctx, *form)
if err != nil {
return
}
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
)
}
// PostIssueCommentReaction add a reaction to a comment of an issue
-func PostIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOption) {
+func PostIssueCommentReaction(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/issues/comments/{id}/reactions issue issuePostCommentReaction
// ---
// summary: Add a reaction to a comment of an issue
// "403":
// "$ref": "#/responses/forbidden"
- changeIssueCommentReaction(ctx, form, true)
+ form := web.GetForm(ctx).(*api.EditReactionOption)
+
+ changeIssueCommentReaction(ctx, *form, true)
}
// DeleteIssueCommentReaction remove a reaction from a comment of an issue
-func DeleteIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOption) {
+func DeleteIssueCommentReaction(ctx *context.APIContext) {
// swagger:operation DELETE /repos/{owner}/{repo}/issues/comments/{id}/reactions issue issueDeleteCommentReaction
// ---
// summary: Remove a reaction from a comment of an issue
// "403":
// "$ref": "#/responses/forbidden"
- changeIssueCommentReaction(ctx, form, false)
+ form := web.GetForm(ctx).(*api.EditReactionOption)
+
+ changeIssueCommentReaction(ctx, *form, false)
}
func changeIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOption, isCreateType bool) {
}
// PostIssueReaction add a reaction to an issue
-func PostIssueReaction(ctx *context.APIContext, form api.EditReactionOption) {
+func PostIssueReaction(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/issues/{index}/reactions issue issuePostIssueReaction
// ---
// summary: Add a reaction to an issue
// "$ref": "#/responses/Reaction"
// "403":
// "$ref": "#/responses/forbidden"
-
- changeIssueReaction(ctx, form, true)
+ form := web.GetForm(ctx).(*api.EditReactionOption)
+ changeIssueReaction(ctx, *form, true)
}
// DeleteIssueReaction remove a reaction from an issue
-func DeleteIssueReaction(ctx *context.APIContext, form api.EditReactionOption) {
+func DeleteIssueReaction(ctx *context.APIContext) {
// swagger:operation DELETE /repos/{owner}/{repo}/issues/{index}/reactions issue issueDeleteIssueReaction
// ---
// summary: Remove a reaction from an issue
// "$ref": "#/responses/empty"
// "403":
// "$ref": "#/responses/forbidden"
-
- changeIssueReaction(ctx, form, false)
+ form := web.GetForm(ctx).(*api.EditReactionOption)
+ changeIssueReaction(ctx, *form, false)
}
func changeIssueReaction(ctx *context.APIContext, form api.EditReactionOption, isCreateType bool) {
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
)
}
// AddTime add time manual to the given issue
-func AddTime(ctx *context.APIContext, form api.AddTimeOption) {
+func AddTime(ctx *context.APIContext) {
// swagger:operation Post /repos/{owner}/{repo}/issues/{index}/times issue issueAddTime
// ---
// summary: Add tracked time to a issue
// "$ref": "#/responses/error"
// "403":
// "$ref": "#/responses/forbidden"
-
+ form := web.GetForm(ctx).(*api.AddTimeOption)
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
if models.IsErrIssueNotExist(err) {
"code.gitea.io/gitea/modules/convert"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
)
}
// CreateDeployKey create deploy key for a repository
-func CreateDeployKey(ctx *context.APIContext, form api.CreateKeyOption) {
+func CreateDeployKey(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/keys repository repoCreateKey
// ---
// summary: Add a key to a repository
// "422":
// "$ref": "#/responses/validationError"
+ form := web.GetForm(ctx).(*api.CreateKeyOption)
content, err := models.CheckPublicKeyString(form.Key)
if err != nil {
HandleCheckKeyStringError(ctx, err)
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
)
}
// CreateLabel create a label for a repository
-func CreateLabel(ctx *context.APIContext, form api.CreateLabelOption) {
+func CreateLabel(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/labels issue issueCreateLabel
// ---
// summary: Create a label
// "422":
// "$ref": "#/responses/validationError"
+ form := web.GetForm(ctx).(*api.CreateLabelOption)
form.Color = strings.Trim(form.Color, " ")
if len(form.Color) == 6 {
form.Color = "#" + form.Color
}
// EditLabel modify a label for a repository
-func EditLabel(ctx *context.APIContext, form api.EditLabelOption) {
+func EditLabel(ctx *context.APIContext) {
// swagger:operation PATCH /repos/{owner}/{repo}/labels/{id} issue issueEditLabel
// ---
// summary: Update a label
// "422":
// "$ref": "#/responses/validationError"
+ form := web.GetForm(ctx).(*api.EditLabelOption)
label, err := models.GetLabelInRepoByID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id"))
if err != nil {
if models.IsErrRepoLabelNotExist(err) {
"strings"
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert"
+ auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/migrations"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/web"
)
// Migrate migrate remote git repository to gitea
-func Migrate(ctx *context.APIContext, form api.MigrateRepoOptions) {
+func Migrate(ctx *context.APIContext) {
// swagger:operation POST /repos/migrate repository repoMigrate
// ---
// summary: Migrate a remote git repository
// "422":
// "$ref": "#/responses/validationError"
+ form := web.GetForm(ctx).(*api.MigrateRepoOptions)
+
//get repoOwner
var (
repoOwner *models.User
"code.gitea.io/gitea/modules/convert"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
+ "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
)
}
// CreateMilestone create a milestone for a repository
-func CreateMilestone(ctx *context.APIContext, form api.CreateMilestoneOption) {
+func CreateMilestone(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/milestones issue issueCreateMilestone
// ---
// summary: Create a milestone
// responses:
// "201":
// "$ref": "#/responses/Milestone"
+ form := web.GetForm(ctx).(*api.CreateMilestoneOption)
if form.Deadline == nil {
defaultDeadline, _ := time.ParseInLocation("2006-01-02", "9999-12-31", time.Local)
}
// EditMilestone modify a milestone for a repository by ID and if not available by name
-func EditMilestone(ctx *context.APIContext, form api.EditMilestoneOption) {
+func EditMilestone(ctx *context.APIContext) {
// swagger:operation PATCH /repos/{owner}/{repo}/milestones/{id} issue issueEditMilestone
// ---
// summary: Update a milestone
// responses:
// "200":
// "$ref": "#/responses/Milestone"
-
+ form := web.GetForm(ctx).(*api.EditMilestoneOption)
milestone := getMilestoneByIDOrName(ctx)
if ctx.Written() {
return
"time"
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert"
+ auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/notification"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
+ "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
issue_service "code.gitea.io/gitea/services/issue"
pull_service "code.gitea.io/gitea/services/pull"
)
// ListPullRequests returns a list of all PRs
-func ListPullRequests(ctx *context.APIContext, form api.ListPullRequestsOptions) {
+func ListPullRequests(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/pulls repository repoListPullRequests
// ---
// summary: List a repo's pull requests
}
// CreatePullRequest does what it says
-func CreatePullRequest(ctx *context.APIContext, form api.CreatePullRequestOption) {
+func CreatePullRequest(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/pulls repository repoCreatePullRequest
// ---
// summary: Create a pull request
// "422":
// "$ref": "#/responses/validationError"
+ form := *web.GetForm(ctx).(*api.CreatePullRequestOption)
if form.Head == form.Base {
ctx.Error(http.StatusUnprocessableEntity, "BaseHeadSame",
"Invalid PullRequest: There are no changes between the head and the base")
}
// EditPullRequest does what it says
-func EditPullRequest(ctx *context.APIContext, form api.EditPullRequestOption) {
+func EditPullRequest(ctx *context.APIContext) {
// swagger:operation PATCH /repos/{owner}/{repo}/pulls/{index} repository repoEditPullRequest
// ---
// summary: Update a pull request. If using deadline only the date will be taken into account, and time of day ignored.
// "422":
// "$ref": "#/responses/validationError"
+ form := web.GetForm(ctx).(*api.EditPullRequestOption)
pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
if models.IsErrPullRequestNotExist(err) {
}
// MergePullRequest merges a PR given an index
-func MergePullRequest(ctx *context.APIContext, form auth.MergePullRequestForm) {
+func MergePullRequest(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/pulls/{index}/merge repository repoMergePullRequest
// ---
// summary: Merge a pull request
// "409":
// "$ref": "#/responses/error"
+ form := web.GetForm(ctx).(*auth.MergePullRequestForm)
pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
if models.IsErrPullRequestNotExist(err) {
"code.gitea.io/gitea/modules/convert"
"code.gitea.io/gitea/modules/git"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
issue_service "code.gitea.io/gitea/services/issue"
pull_service "code.gitea.io/gitea/services/pull"
}
// CreatePullReview create a review to an pull request
-func CreatePullReview(ctx *context.APIContext, opts api.CreatePullReviewOptions) {
+func CreatePullReview(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/pulls/{index}/reviews repository repoCreatePullReview
// ---
// summary: Create a review to an pull request
// "422":
// "$ref": "#/responses/validationError"
+ opts := web.GetForm(ctx).(*api.CreatePullReviewOptions)
pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
if models.IsErrPullRequestNotExist(err) {
}
// SubmitPullReview submit a pending review to an pull request
-func SubmitPullReview(ctx *context.APIContext, opts api.SubmitPullReviewOptions) {
+func SubmitPullReview(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/pulls/{index}/reviews/{id} repository repoSubmitPullReview
// ---
// summary: Submit a pending review to an pull request
// "422":
// "$ref": "#/responses/validationError"
+ opts := web.GetForm(ctx).(*api.SubmitPullReviewOptions)
review, pr, isWrong := prepareSingleReview(ctx)
if isWrong {
return
}
// CreateReviewRequests create review requests to an pull request
-func CreateReviewRequests(ctx *context.APIContext, opts api.PullReviewRequestOptions) {
+func CreateReviewRequests(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/pulls/{index}/requested_reviewers repository repoCreatePullReviewRequests
// ---
// summary: create review requests for a pull request
// "$ref": "#/responses/validationError"
// "404":
// "$ref": "#/responses/notFound"
- apiReviewRequest(ctx, opts, true)
+
+ opts := web.GetForm(ctx).(*api.PullReviewRequestOptions)
+ apiReviewRequest(ctx, *opts, true)
}
// DeleteReviewRequests delete review requests to an pull request
-func DeleteReviewRequests(ctx *context.APIContext, opts api.PullReviewRequestOptions) {
+func DeleteReviewRequests(ctx *context.APIContext) {
// swagger:operation DELETE /repos/{owner}/{repo}/pulls/{index}/requested_reviewers repository repoDeletePullReviewRequests
// ---
// summary: cancel review requests for a pull request
// "$ref": "#/responses/validationError"
// "404":
// "$ref": "#/responses/notFound"
- apiReviewRequest(ctx, opts, false)
+ opts := web.GetForm(ctx).(*api.PullReviewRequestOptions)
+ apiReviewRequest(ctx, *opts, false)
}
func apiReviewRequest(ctx *context.APIContext, opts api.PullReviewRequestOptions, isAdd bool) {
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
releaseservice "code.gitea.io/gitea/services/release"
)
}
// CreateRelease create a release
-func CreateRelease(ctx *context.APIContext, form api.CreateReleaseOption) {
+func CreateRelease(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/releases repository repoCreateRelease
// ---
// summary: Create a release
// "$ref": "#/responses/notFound"
// "409":
// "$ref": "#/responses/error"
-
+ form := web.GetForm(ctx).(*api.CreateReleaseOption)
rel, err := models.GetRelease(ctx.Repo.Repository.ID, form.TagName)
if err != nil {
if !models.IsErrReleaseNotExist(err) {
}
// EditRelease edit a release
-func EditRelease(ctx *context.APIContext, form api.EditReleaseOption) {
+func EditRelease(ctx *context.APIContext) {
// swagger:operation PATCH /repos/{owner}/{repo}/releases/{id} repository repoEditRelease
// ---
// summary: Update a release
// "404":
// "$ref": "#/responses/notFound"
+ form := web.GetForm(ctx).(*api.EditReleaseOption)
id := ctx.ParamsInt64(":id")
rel, err := models.GetReleaseByID(id)
if err != nil && !models.IsErrReleaseNotExist(err) {
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/upload"
+ "code.gitea.io/gitea/modules/web"
)
// GetReleaseAttachment gets a single attachment of the release
}
// Get uploaded file from request
- file, header, err := ctx.GetFile("attachment")
+ file, header, err := ctx.Req.FormFile("attachment")
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetFile", err)
return
}
// EditReleaseAttachment updates the given attachment
-func EditReleaseAttachment(ctx *context.APIContext, form api.EditAttachmentOptions) {
+func EditReleaseAttachment(ctx *context.APIContext) {
// swagger:operation PATCH /repos/{owner}/{repo}/releases/{id}/assets/{attachment_id} repository repoEditReleaseAttachment
// ---
// summary: Edit a release attachment
// "201":
// "$ref": "#/responses/Attachment"
+ form := web.GetForm(ctx).(*api.EditAttachmentOptions)
+
// Check if release exists an load release
releaseID := ctx.ParamsInt64(":id")
attachID := ctx.ParamsInt64(":asset")
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/validation"
+ "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
repo_service "code.gitea.io/gitea/services/repository"
)
}
// Create one repository of mine
-func Create(ctx *context.APIContext, opt api.CreateRepoOption) {
+func Create(ctx *context.APIContext) {
// swagger:operation POST /user/repos repository user createCurrentUserRepo
// ---
// summary: Create a repository
// description: The repository with the same name already exists.
// "422":
// "$ref": "#/responses/validationError"
-
+ opt := web.GetForm(ctx).(*api.CreateRepoOption)
if ctx.User.IsOrganization() {
// Shouldn't reach this condition, but just in case.
ctx.Error(http.StatusUnprocessableEntity, "", "not allowed creating repository for organization")
return
}
- CreateUserRepo(ctx, ctx.User, opt)
+ CreateUserRepo(ctx, ctx.User, *opt)
}
// CreateOrgRepoDeprecated create one repository of the organization
-func CreateOrgRepoDeprecated(ctx *context.APIContext, opt api.CreateRepoOption) {
+func CreateOrgRepoDeprecated(ctx *context.APIContext) {
// swagger:operation POST /org/{org}/repos organization createOrgRepoDeprecated
// ---
// summary: Create a repository in an organization
// "403":
// "$ref": "#/responses/forbidden"
- CreateOrgRepo(ctx, opt)
+ CreateOrgRepo(ctx)
}
// CreateOrgRepo create one repository of the organization
-func CreateOrgRepo(ctx *context.APIContext, opt api.CreateRepoOption) {
+func CreateOrgRepo(ctx *context.APIContext) {
// swagger:operation POST /orgs/{org}/repos organization createOrgRepo
// ---
// summary: Create a repository in an organization
// "$ref": "#/responses/notFound"
// "403":
// "$ref": "#/responses/forbidden"
-
+ opt := web.GetForm(ctx).(*api.CreateRepoOption)
org, err := models.GetOrgByName(ctx.Params(":org"))
if err != nil {
if models.IsErrOrgNotExist(err) {
return
}
}
- CreateUserRepo(ctx, org, opt)
+ CreateUserRepo(ctx, org, *opt)
}
// Get one repository
}
// Edit edit repository properties
-func Edit(ctx *context.APIContext, opts api.EditRepoOption) {
+func Edit(ctx *context.APIContext) {
// swagger:operation PATCH /repos/{owner}/{repo} repository repoEdit
// ---
// summary: Edit a repository's properties. Only fields that are set will be changed.
// "422":
// "$ref": "#/responses/validationError"
+ opts := *web.GetForm(ctx).(*api.EditRepoOption)
+
if err := updateBasicProperties(ctx, opts); err != nil {
return
}
"code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/test"
+ "code.gitea.io/gitea/modules/web"
"github.com/stretchr/testify/assert"
)
Archived: &archived,
}
- Edit(&context.APIContext{Context: ctx, Org: nil}, opts)
+ var apiCtx = &context.APIContext{Context: ctx, Org: nil}
+ web.SetForm(apiCtx, &opts)
+ Edit(apiCtx)
assert.EqualValues(t, http.StatusOK, ctx.Resp.Status())
models.AssertExistsAndLoadBean(t, &models.Repository{
Name: &name,
}
- Edit(&context.APIContext{Context: ctx, Org: nil}, opts)
+ var apiCtx = &context.APIContext{Context: ctx, Org: nil}
+ web.SetForm(apiCtx, &opts)
+ Edit(apiCtx)
assert.EqualValues(t, http.StatusOK, ctx.Resp.Status())
models.AssertExistsAndLoadBean(t, &models.Repository{
"code.gitea.io/gitea/modules/convert"
"code.gitea.io/gitea/modules/repofiles"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
)
// NewCommitStatus creates a new CommitStatus
-func NewCommitStatus(ctx *context.APIContext, form api.CreateStatusOption) {
+func NewCommitStatus(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/statuses/{sha} repository repoCreateStatus
// ---
// summary: Create a commit status
// "400":
// "$ref": "#/responses/error"
+ form := web.GetForm(ctx).(*api.CreateStatusOption)
sha := ctx.Params("sha")
if len(sha) == 0 {
ctx.Error(http.StatusBadRequest, "sha not given", nil)
"code.gitea.io/gitea/modules/convert"
"code.gitea.io/gitea/modules/log"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
)
}
// UpdateTopics updates repo with a new set of topics
-func UpdateTopics(ctx *context.APIContext, form api.RepoTopicOptions) {
+func UpdateTopics(ctx *context.APIContext) {
// swagger:operation PUT /repos/{owner}/{repo}/topics repository repoUpdateTopics
// ---
// summary: Replace list of topics for a repository
// "422":
// "$ref": "#/responses/invalidTopicsError"
+ form := web.GetForm(ctx).(*api.RepoTopicOptions)
topicNames := form.Topics
validTopics, invalidTopics := models.SanitizeAndValidateTopics(topicNames)
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/structs"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/web"
repo_service "code.gitea.io/gitea/services/repository"
)
// Transfer transfers the ownership of a repository
-func Transfer(ctx *context.APIContext, opts api.TransferRepoOption) {
+func Transfer(ctx *context.APIContext) {
// swagger:operation POST /repos/{owner}/{repo}/transfer repository repoTransfer
// ---
// summary: Transfer a repo ownership
// "422":
// "$ref": "#/responses/validationError"
+ opts := web.GetForm(ctx).(*api.TransferRepoOption)
+
newOwner, err := models.GetUserByName(opts.NewOwner)
if err != nil {
if models.IsErrUserNotExist(err) {
package swagger
import (
- "code.gitea.io/gitea/modules/auth"
+ auth "code.gitea.io/gitea/modules/forms"
api "code.gitea.io/gitea/modules/structs"
)
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
)
}
// CreateAccessToken create access tokens
-func CreateAccessToken(ctx *context.APIContext, form api.CreateAccessTokenOption) {
+func CreateAccessToken(ctx *context.APIContext) {
// swagger:operation POST /users/{username}/tokens user userCreateToken
// ---
// summary: Create an access token
// "201":
// "$ref": "#/responses/AccessToken"
+ form := web.GetForm(ctx).(*api.CreateAccessTokenOption)
+
t := &models.AccessToken{
UID: ctx.User.ID,
Name: form.Name,
}
// CreateOauth2Application is the handler to create a new OAuth2 Application for the authenticated user
-func CreateOauth2Application(ctx *context.APIContext, data api.CreateOAuth2ApplicationOptions) {
+func CreateOauth2Application(ctx *context.APIContext) {
// swagger:operation POST /user/applications/oauth2 user userCreateOAuth2Application
// ---
// summary: creates a new OAuth2 application
// responses:
// "201":
// "$ref": "#/responses/OAuth2Application"
+
+ data := web.GetForm(ctx).(*api.CreateOAuth2ApplicationOptions)
+
app, err := models.CreateOAuth2Application(models.CreateOAuth2ApplicationOptions{
Name: data.Name,
UserID: ctx.User.ID,
}
// UpdateOauth2Application update OAuth2 Application
-func UpdateOauth2Application(ctx *context.APIContext, data api.CreateOAuth2ApplicationOptions) {
+func UpdateOauth2Application(ctx *context.APIContext) {
// swagger:operation PATCH /user/applications/oauth2/{id} user userUpdateOAuth2Application
// ---
// summary: update an OAuth2 Application, this includes regenerating the client secret
// "$ref": "#/responses/OAuth2Application"
appID := ctx.ParamsInt64(":id")
+ data := web.GetForm(ctx).(*api.CreateOAuth2ApplicationOptions)
+
app, err := models.UpdateOAuth2Application(models.UpdateOAuth2ApplicationOptions{
Name: data.Name,
UserID: ctx.User.ID,
"code.gitea.io/gitea/modules/convert"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/web"
)
// ListEmails list all of the authenticated user's email addresses
}
// AddEmail add an email address
-func AddEmail(ctx *context.APIContext, form api.CreateEmailOption) {
+func AddEmail(ctx *context.APIContext) {
// swagger:operation POST /user/emails user userAddEmail
// ---
// summary: Add email addresses
// "$ref": "#/responses/EmailList"
// "422":
// "$ref": "#/responses/validationError"
-
+ form := web.GetForm(ctx).(*api.CreateEmailOption)
if len(form.Emails) == 0 {
ctx.Error(http.StatusUnprocessableEntity, "", "Email list empty")
return
}
// DeleteEmail delete email
-func DeleteEmail(ctx *context.APIContext, form api.DeleteEmailOption) {
+func DeleteEmail(ctx *context.APIContext) {
// swagger:operation DELETE /user/emails user userDeleteEmail
// ---
// summary: Delete email addresses
// responses:
// "204":
// "$ref": "#/responses/empty"
-
+ form := web.GetForm(ctx).(*api.DeleteEmailOption)
if len(form.Emails) == 0 {
ctx.Status(http.StatusNoContent)
return
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
)
}
//CreateGPGKey create a GPG key belonging to the authenticated user
-func CreateGPGKey(ctx *context.APIContext, form api.CreateGPGKeyOption) {
+func CreateGPGKey(ctx *context.APIContext) {
// swagger:operation POST /user/gpg_keys user userCurrentPostGPGKey
// ---
// summary: Create a GPG key
// "422":
// "$ref": "#/responses/validationError"
- CreateUserGPGKey(ctx, form, ctx.User.ID)
+ form := web.GetForm(ctx).(*api.CreateGPGKeyOption)
+ CreateUserGPGKey(ctx, *form, ctx.User.ID)
}
//DeleteGPGKey remove a GPG key belonging to the authenticated user
"code.gitea.io/gitea/modules/convert"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/repo"
"code.gitea.io/gitea/routers/api/v1/utils"
)
}
// CreatePublicKey create one public key for me
-func CreatePublicKey(ctx *context.APIContext, form api.CreateKeyOption) {
+func CreatePublicKey(ctx *context.APIContext) {
// swagger:operation POST /user/keys user userCurrentPostKey
// ---
// summary: Create a public key
// "422":
// "$ref": "#/responses/validationError"
- CreateUserPublicKey(ctx, form, ctx.User.ID)
+ form := web.GetForm(ctx).(*api.CreateKeyOption)
+ CreateUserPublicKey(ctx, *form, ctx.User.ID)
}
// DeletePublicKey delete one public key
pull_service "code.gitea.io/gitea/services/pull"
"code.gitea.io/gitea/services/repository"
"code.gitea.io/gitea/services/webhook"
-
- "gitea.com/macaron/macaron"
)
func checkRunMode() {
switch setting.RunMode {
- case "dev":
- git.Debug = true
- case "test":
+ case "dev", "test":
git.Debug = true
default:
- macaron.Env = macaron.PROD
- macaron.ColorLog = false
git.Debug = false
}
- log.Info("Run Mode: %s", strings.Title(macaron.Env))
+ log.Info("Run Mode: %s", strings.Title(setting.RunMode))
}
// NewServices init new services
"os/exec"
"path/filepath"
"strings"
+ "time"
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
+ auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/generate"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/middlewares"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/user"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/web"
+ "gitea.com/go-chi/session"
"gopkg.in/ini.v1"
)
)
// InstallInit prepare for rendering installation page
-func InstallInit(ctx *context.Context) {
- if setting.InstallLock {
- ctx.Header().Add("Refresh", "1; url="+setting.AppURL+"user/login")
- ctx.HTML(200, tplPostInstall)
- return
- }
-
- ctx.Data["Title"] = ctx.Tr("install.install")
- ctx.Data["PageIsInstall"] = true
+func InstallInit(next http.Handler) http.Handler {
+ var rnd = templates.HTMLRenderer()
- ctx.Data["DbOptions"] = setting.SupportedDatabases
+ return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
+ if setting.InstallLock {
+ resp.Header().Add("Refresh", "1; url="+setting.AppURL+"user/login")
+ _ = rnd.HTML(resp, 200, string(tplPostInstall), nil)
+ return
+ }
+ var locale = middlewares.Locale(resp, req)
+ var startTime = time.Now()
+ var ctx = context.Context{
+ Resp: context.NewResponse(resp),
+ Flash: &middlewares.Flash{},
+ Locale: locale,
+ Render: rnd,
+ Session: session.GetSession(req),
+ Data: map[string]interface{}{
+ "Title": locale.Tr("install.install"),
+ "PageIsInstall": true,
+ "DbOptions": setting.SupportedDatabases,
+ "i18n": locale,
+ "Language": locale.Language(),
+ "CurrentURL": setting.AppSubURL + req.URL.RequestURI(),
+ "PageStartTime": startTime,
+ "TmplLoadTimes": func() string {
+ return time.Since(startTime).String()
+ },
+ },
+ }
+ ctx.Req = context.WithContext(req, &ctx)
+ next.ServeHTTP(resp, ctx.Req)
+ })
}
// Install render installation page
form.DefaultEnableTimetracking = setting.Service.DefaultEnableTimetracking
form.NoReplyAddress = setting.Service.NoReplyAddress
- auth.AssignForm(form, ctx.Data)
+ middlewares.AssignForm(form, ctx.Data)
ctx.HTML(200, tplInstall)
}
// InstallPost response for submit install items
-func InstallPost(ctx *context.Context, form auth.InstallForm) {
+func InstallPost(ctx *context.Context) {
+ form := *web.GetForm(ctx).(*auth.InstallForm)
var err error
ctx.Data["CurDbOption"] = form.DbType
"errors"
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
+ auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/web"
)
const (
}
// CreatePost response for create organization
-func CreatePost(ctx *context.Context, form auth.CreateOrgForm) {
+func CreatePost(ctx *context.Context) {
+ form := *web.GetForm(ctx).(*auth.CreateOrgForm)
ctx.Data["Title"] = ctx.Tr("new_org")
if !ctx.User.CanCreateOrganization() {
import (
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/context"
+ auth "code.gitea.io/gitea/modules/forms"
+ "code.gitea.io/gitea/modules/web"
)
// RetrieveLabels find all the labels of an organization
}
// NewLabel create new label for organization
-func NewLabel(ctx *context.Context, form auth.CreateLabelForm) {
+func NewLabel(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.CreateLabelForm)
ctx.Data["Title"] = ctx.Tr("repo.labels")
ctx.Data["PageIsLabels"] = true
}
// UpdateLabel update a label's name and color
-func UpdateLabel(ctx *context.Context, form auth.CreateLabelForm) {
+func UpdateLabel(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.CreateLabelForm)
l, err := models.GetLabelInOrgByID(ctx.Org.Organization.ID, form.ID)
if err != nil {
switch {
}
// InitializeLabels init labels for an organization
-func InitializeLabels(ctx *context.Context, form auth.InitializeLabelsForm) {
+func InitializeLabels(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.InitializeLabelsForm)
if ctx.HasError() {
ctx.Redirect(ctx.Repo.RepoLink + "/labels")
return
"strings"
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
+ auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/web"
userSetting "code.gitea.io/gitea/routers/user/setting"
)
}
// SettingsPost response for settings change submited
-func SettingsPost(ctx *context.Context, form auth.UpdateOrgSettingForm) {
+func SettingsPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.UpdateOrgSettingForm)
ctx.Data["Title"] = ctx.Tr("org.settings")
ctx.Data["PageIsSettingsOptions"] = true
ctx.Data["CurrentVisibility"] = ctx.Org.Organization.Visibility
}
// SettingsAvatar response for change avatar on settings page
-func SettingsAvatar(ctx *context.Context, form auth.AvatarForm) {
+func SettingsAvatar(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.AvatarForm)
form.Source = auth.AvatarLocal
if err := userSetting.UpdateAvatarSetting(ctx, form, ctx.Org.Organization); err != nil {
ctx.Flash.Error(err.Error())
"strings"
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
+ auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/utils"
)
}
// NewTeamPost response for create new team
-func NewTeamPost(ctx *context.Context, form auth.CreateTeamForm) {
+func NewTeamPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.CreateTeamForm)
ctx.Data["Title"] = ctx.Org.Organization.FullName
ctx.Data["PageIsOrgTeams"] = true
ctx.Data["PageIsOrgTeamsNew"] = true
}
// EditTeamPost response for modify team information
-func EditTeamPost(ctx *context.Context, form auth.CreateTeamForm) {
+func EditTeamPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.CreateTeamForm)
t := ctx.Org.Team
ctx.Data["Title"] = ctx.Org.Organization.FullName
ctx.Data["PageIsOrgTeams"] = true
"strings"
"code.gitea.io/gitea/models"
+ gitea_context "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/web"
pull_service "code.gitea.io/gitea/services/pull"
repo_service "code.gitea.io/gitea/services/repository"
-
- "gitea.com/macaron/macaron"
)
func verifyCommits(oldCommitID, newCommitID string, repo *git.Repository, env []string) error {
}
// HookPreReceive checks whether a individual commit is acceptable
-func HookPreReceive(ctx *macaron.Context, opts private.HookOptions) {
+func HookPreReceive(ctx *gitea_context.PrivateContext) {
+ opts := web.GetForm(ctx).(*private.HookOptions)
ownerName := ctx.Params(":owner")
repoName := ctx.Params(":repo")
repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName)
}
// HookPostReceive updates services and users
-func HookPostReceive(ctx *macaron.Context, opts private.HookOptions) {
+func HookPostReceive(ctx *gitea_context.PrivateContext) {
+ opts := web.GetForm(ctx).(*private.HookOptions)
ownerName := ctx.Params(":owner")
repoName := ctx.Params(":repo")
}
// SetDefaultBranch updates the default branch
-func SetDefaultBranch(ctx *macaron.Context) {
+func SetDefaultBranch(ctx *gitea_context.PrivateContext) {
ownerName := ctx.Params(":owner")
repoName := ctx.Params(":repo")
branch := ctx.Params(":branch")
package private
import (
+ "net/http"
+ "reflect"
"strings"
+ "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/web"
- "gitea.com/macaron/binding"
- "gitea.com/macaron/macaron"
+ "gitea.com/go-chi/binding"
)
// CheckInternalToken check internal token is set
-func CheckInternalToken(ctx *macaron.Context) {
- tokens := ctx.Req.Header.Get("Authorization")
- fields := strings.Fields(tokens)
- if len(fields) != 2 || fields[0] != "Bearer" || fields[1] != setting.InternalToken {
- log.Debug("Forbidden attempt to access internal url: Authorization header: %s", tokens)
- ctx.Error(403)
+func CheckInternalToken(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ tokens := req.Header.Get("Authorization")
+ fields := strings.Fields(tokens)
+ if len(fields) != 2 || fields[0] != "Bearer" || fields[1] != setting.InternalToken {
+ log.Debug("Forbidden attempt to access internal url: Authorization header: %s", tokens)
+ http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
+ } else {
+ next.ServeHTTP(w, req)
+ }
+ })
+}
+
+// bind binding an obj to a handler
+func bind(obj interface{}) http.HandlerFunc {
+ var tp = reflect.TypeOf(obj)
+ for tp.Kind() == reflect.Ptr {
+ tp = tp.Elem()
}
+ return web.Wrap(func(ctx *context.PrivateContext) {
+ var theObj = reflect.New(tp).Interface() // create a new form obj for every request but not use obj directly
+ binding.Bind(ctx.Req, theObj)
+ web.SetForm(ctx, theObj)
+ })
}
-// RegisterRoutes registers all internal APIs routes to web application.
+// Routes registers all internal APIs routes to web application.
// These APIs will be invoked by internal commands for example `gitea serv` and etc.
-func RegisterRoutes(m *macaron.Macaron) {
- bind := binding.Bind
-
- m.Group("/", func() {
- m.Post("/ssh/authorized_keys", AuthorizedPublicKeyByContent)
- m.Post("/ssh/:id/update/:repoid", UpdatePublicKeyInRepo)
- m.Post("/hook/pre-receive/:owner/:repo", bind(private.HookOptions{}), HookPreReceive)
- m.Post("/hook/post-receive/:owner/:repo", bind(private.HookOptions{}), HookPostReceive)
- m.Post("/hook/set-default-branch/:owner/:repo/:branch", SetDefaultBranch)
- m.Get("/serv/none/:keyid", ServNoCommand)
- m.Get("/serv/command/:keyid/:owner/:repo", ServCommand)
- m.Post("/manager/shutdown", Shutdown)
- m.Post("/manager/restart", Restart)
- m.Post("/manager/flush-queues", bind(private.FlushOptions{}), FlushQueues)
- m.Post("/manager/pause-logging", PauseLogging)
- m.Post("/manager/resume-logging", ResumeLogging)
- m.Post("/manager/release-and-reopen-logging", ReleaseReopenLogging)
- m.Post("/manager/add-logger", bind(private.LoggerOptions{}), AddLogger)
- m.Post("/manager/remove-logger/:group/:name", RemoveLogger)
- m.Post("/mail/send", SendEmail)
- }, CheckInternalToken)
+func Routes() *web.Route {
+ var r = web.NewRoute()
+ r.Use(context.PrivateContexter())
+ r.Use(CheckInternalToken)
+
+ r.Post("/ssh/authorized_keys", AuthorizedPublicKeyByContent)
+ r.Post("/ssh/{id}/update/{repoid}", UpdatePublicKeyInRepo)
+ r.Post("/hook/pre-receive/{owner}/{repo}", bind(private.HookOptions{}), HookPreReceive)
+ r.Post("/hook/post-receive/{owner}/{repo}", bind(private.HookOptions{}), HookPostReceive)
+ r.Post("/hook/set-default-branch/{owner}/{repo}/{branch}", SetDefaultBranch)
+ r.Get("/serv/none/{keyid}", ServNoCommand)
+ r.Get("/serv/command/{keyid}/{owner}/{repo}", ServCommand)
+ r.Post("/manager/shutdown", Shutdown)
+ r.Post("/manager/restart", Restart)
+ r.Post("/manager/flush-queues", bind(private.FlushOptions{}), FlushQueues)
+ r.Post("/manager/pause-logging", PauseLogging)
+ r.Post("/manager/resume-logging", ResumeLogging)
+ r.Post("/manager/release-and-reopen-logging", ReleaseReopenLogging)
+ r.Post("/manager/add-logger", bind(private.LoggerOptions{}), AddLogger)
+ r.Post("/manager/remove-logger/{group}/{name}", RemoveLogger)
+ r.Post("/mail/send", SendEmail)
+
+ return r
}
"net/http"
"code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/timeutil"
-
- "gitea.com/macaron/macaron"
)
// UpdatePublicKeyInRepo update public key and deploy key updates
-func UpdatePublicKeyInRepo(ctx *macaron.Context) {
+func UpdatePublicKeyInRepo(ctx *context.PrivateContext) {
keyID := ctx.ParamsInt64(":id")
repoID := ctx.ParamsInt64(":repoid")
if err := models.UpdatePublicKeyUpdated(keyID); err != nil {
// AuthorizedPublicKeyByContent searches content as prefix (leak e-mail part)
// and returns public key found.
-func AuthorizedPublicKeyByContent(ctx *macaron.Context) {
+func AuthorizedPublicKeyByContent(ctx *context.PrivateContext) {
content := ctx.Query("content")
publicKey, err := models.SearchPublicKeyByContent(content)
"strconv"
"code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/mailer"
- "gitea.com/macaron/macaron"
)
// SendEmail pushes messages to mail queue
//
// It doesn't wait before each message will be processed
-func SendEmail(ctx *macaron.Context) {
+func SendEmail(ctx *context.PrivateContext) {
if setting.MailService == nil {
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"err": "Mail service is not enabled.",
}
var mail private.Email
- rd := ctx.Req.Body().ReadCloser()
+ rd := ctx.Req.Body
defer rd.Close()
if err := json.NewDecoder(rd).Decode(&mail); err != nil {
log.Error("%v", err)
sendEmail(ctx, mail.Subject, mail.Message, emails)
}
-func sendEmail(ctx *macaron.Context, subject, message string, to []string) {
+func sendEmail(ctx *context.PrivateContext, subject, message string, to []string) {
for _, email := range to {
msg := mailer.NewMessage([]string{email}, subject, message)
mailer.SendAsync(msg)
"fmt"
"net/http"
+ "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/queue"
"code.gitea.io/gitea/modules/setting"
-
- "gitea.com/macaron/macaron"
+ "code.gitea.io/gitea/modules/web"
)
// FlushQueues flushes all the Queues
-func FlushQueues(ctx *macaron.Context, opts private.FlushOptions) {
+func FlushQueues(ctx *context.PrivateContext) {
+ opts := web.GetForm(ctx).(*private.FlushOptions)
if opts.NonBlocking {
// Save the hammer ctx here - as a new one is created each time you call this.
baseCtx := graceful.GetManager().HammerContext()
})
return
}
- err := queue.GetManager().FlushAll(ctx.Req.Request.Context(), opts.Timeout)
+ err := queue.GetManager().FlushAll(ctx.Req.Context(), opts.Timeout)
if err != nil {
ctx.JSON(http.StatusRequestTimeout, map[string]interface{}{
"err": fmt.Sprintf("%v", err),
}
// PauseLogging pauses logging
-func PauseLogging(ctx *macaron.Context) {
+func PauseLogging(ctx *context.PrivateContext) {
log.Pause()
ctx.PlainText(http.StatusOK, []byte("success"))
}
// ResumeLogging resumes logging
-func ResumeLogging(ctx *macaron.Context) {
+func ResumeLogging(ctx *context.PrivateContext) {
log.Resume()
ctx.PlainText(http.StatusOK, []byte("success"))
}
// ReleaseReopenLogging releases and reopens logging files
-func ReleaseReopenLogging(ctx *macaron.Context) {
+func ReleaseReopenLogging(ctx *context.PrivateContext) {
if err := log.ReleaseReopen(); err != nil {
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"err": fmt.Sprintf("Error during release and reopen: %v", err),
}
// RemoveLogger removes a logger
-func RemoveLogger(ctx *macaron.Context) {
+func RemoveLogger(ctx *context.PrivateContext) {
group := ctx.Params("group")
name := ctx.Params("name")
ok, err := log.GetLogger(group).DelLogger(name)
}
// AddLogger adds a logger
-func AddLogger(ctx *macaron.Context, opts private.LoggerOptions) {
+func AddLogger(ctx *context.PrivateContext) {
+ opts := web.GetForm(ctx).(*private.LoggerOptions)
if len(opts.Group) == 0 {
opts.Group = log.DEFAULT
}
import (
"net/http"
+ "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/graceful"
-
- "gitea.com/macaron/macaron"
)
// Restart causes the server to perform a graceful restart
-func Restart(ctx *macaron.Context) {
+func Restart(ctx *context.PrivateContext) {
graceful.GetManager().DoGracefulRestart()
ctx.PlainText(http.StatusOK, []byte("success"))
}
// Shutdown causes the server to perform a graceful shutdown
-func Shutdown(ctx *macaron.Context) {
+func Shutdown(ctx *context.PrivateContext) {
graceful.GetManager().DoGracefulShutdown()
ctx.PlainText(http.StatusOK, []byte("success"))
}
import (
"net/http"
+ "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/graceful"
-
- "gitea.com/macaron/macaron"
)
// Restart is not implemented for Windows based servers as they can't fork
-func Restart(ctx *macaron.Context) {
+func Restart(ctx *context.PrivateContext) {
ctx.JSON(http.StatusNotImplemented, map[string]interface{}{
"err": "windows servers cannot be gracefully restarted - shutdown and restart manually",
})
}
// Shutdown causes the server to perform a graceful shutdown
-func Shutdown(ctx *macaron.Context) {
+func Shutdown(ctx *context.PrivateContext) {
graceful.GetManager().DoGracefulShutdown()
ctx.PlainText(http.StatusOK, []byte("success"))
}
"strings"
"code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/setting"
repo_service "code.gitea.io/gitea/services/repository"
wiki_service "code.gitea.io/gitea/services/wiki"
-
- "gitea.com/macaron/macaron"
)
// ServNoCommand returns information about the provided keyid
-func ServNoCommand(ctx *macaron.Context) {
+func ServNoCommand(ctx *context.PrivateContext) {
keyID := ctx.ParamsInt64(":keyid")
if keyID <= 0 {
ctx.JSON(http.StatusBadRequest, map[string]interface{}{
}
// ServCommand returns information about the provided keyid
-func ServCommand(ctx *macaron.Context) {
+func ServCommand(ctx *context.PrivateContext) {
keyID := ctx.ParamsInt64(":keyid")
ownerName := ctx.Params(":owner")
repoName := ctx.Params(":repo")
"strings"
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
+ auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/repofiles"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/utils"
repo_service "code.gitea.io/gitea/services/repository"
)
}
// CreateBranch creates new branch in repository
-func CreateBranch(ctx *context.Context, form auth.NewBranchForm) {
+func CreateBranch(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.NewBranchForm)
if !ctx.Repo.CanCreateBranch() {
ctx.NotFound("CreateBranch", nil)
return
"strings"
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/charset"
"code.gitea.io/gitea/modules/context"
+ auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/repofiles"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/upload"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/utils"
)
}
// EditFilePost response for editing file
-func EditFilePost(ctx *context.Context, form auth.EditRepoFileForm) {
- editFilePost(ctx, form, false)
+func EditFilePost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.EditRepoFileForm)
+ editFilePost(ctx, *form, false)
}
// NewFilePost response for creating file
-func NewFilePost(ctx *context.Context, form auth.EditRepoFileForm) {
- editFilePost(ctx, form, true)
+func NewFilePost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.EditRepoFileForm)
+ editFilePost(ctx, *form, true)
}
// DiffPreviewPost render preview diff page
-func DiffPreviewPost(ctx *context.Context, form auth.EditPreviewDiffForm) {
+func DiffPreviewPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.EditPreviewDiffForm)
treePath := cleanUploadFileName(ctx.Repo.TreePath)
if len(treePath) == 0 {
ctx.Error(500, "file name to diff is invalid")
}
// DeleteFilePost response for deleting file
-func DeleteFilePost(ctx *context.Context, form auth.DeleteRepoFileForm) {
+func DeleteFilePost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.DeleteRepoFileForm)
canCommit := renderCommitRights(ctx)
branchName := ctx.Repo.BranchName
if form.CommitChoice == frmCommitChoiceNewBranch {
}
// UploadFilePost response for uploading file
-func UploadFilePost(ctx *context.Context, form auth.UploadRepoFileForm) {
+func UploadFilePost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.UploadRepoFileForm)
ctx.Data["PageIsUpload"] = true
ctx.Data["RequireTribute"] = true
ctx.Data["RequireSimpleMDE"] = true
}
// RemoveUploadFileFromServer remove file from server file dir
-func RemoveUploadFileFromServer(ctx *context.Context, form auth.RemoveUploadFileForm) {
+func RemoveUploadFileFromServer(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.RemoveUploadFileForm)
if len(form.File) == 0 {
ctx.Status(204)
return
repo_service "code.gitea.io/gitea/services/repository"
)
-// HTTP implmentation git smart HTTP protocol
-func HTTP(ctx *context.Context) {
+// httpBase implmentation git smart HTTP protocol
+func httpBase(ctx *context.Context) (h *serviceHandler) {
+ if setting.Repository.DisableHTTPGit {
+ ctx.Resp.WriteHeader(http.StatusForbidden)
+ _, err := ctx.Resp.Write([]byte("Interacting with repositories by HTTP protocol is not allowed"))
+ if err != nil {
+ log.Error(err.Error())
+ }
+ return
+ }
+
if len(setting.Repository.AccessControlAllowOrigin) > 0 {
allowedOrigin := setting.Repository.AccessControlAllowOrigin
// Set CORS headers for browser-based git clients
environ = append(environ, models.EnvRepoID+fmt.Sprintf("=%d", repo.ID))
w := ctx.Resp
- r := ctx.Req.Request
+ r := ctx.Req
cfg := &serviceConfig{
UploadPack: true,
ReceivePack: true,
r.URL.Path = strings.ToLower(r.URL.Path) // blue: In case some repo name has upper case name
- for _, route := range routes {
- if m := route.reg.FindStringSubmatch(r.URL.Path); m != nil {
- if setting.Repository.DisableHTTPGit {
- w.WriteHeader(http.StatusForbidden)
- _, err := w.Write([]byte("Interacting with repositories by HTTP protocol is not allowed"))
- if err != nil {
- log.Error(err.Error())
- }
- return
- }
- if route.method != r.Method {
- if r.Proto == "HTTP/1.1" {
- w.WriteHeader(http.StatusMethodNotAllowed)
- _, err := w.Write([]byte("Method Not Allowed"))
- if err != nil {
- log.Error(err.Error())
- }
- } else {
- w.WriteHeader(http.StatusBadRequest)
- _, err := w.Write([]byte("Bad Request"))
- if err != nil {
- log.Error(err.Error())
- }
- }
- return
- }
-
- file := strings.Replace(r.URL.Path, m[1]+"/", "", 1)
- dir, err := getGitRepoPath(m[1])
- if err != nil {
- log.Error(err.Error())
- ctx.NotFound("Smart Git HTTP", err)
- return
- }
-
- route.handler(serviceHandler{cfg, w, r, dir, file, cfg.Env})
- return
- }
- }
+ dir := models.RepoPath(username, reponame)
- ctx.NotFound("Smart Git HTTP", nil)
+ return &serviceHandler{cfg, w, r, dir, cfg.Env}
}
var (
w http.ResponseWriter
r *http.Request
dir string
- file string
environ []string
}
h.w.Header().Set("Cache-Control", "public, max-age=31536000")
}
-func (h *serviceHandler) sendFile(contentType string) {
- reqFile := path.Join(h.dir, h.file)
+func (h *serviceHandler) sendFile(contentType, file string) {
+ reqFile := path.Join(h.dir, file)
fi, err := os.Stat(reqFile)
if os.IsNotExist(err) {
http.ServeFile(h.w, h.r, reqFile)
}
-type route struct {
- reg *regexp.Regexp
- method string
- handler func(serviceHandler)
-}
-
-var routes = []route{
- {regexp.MustCompile(`(.*?)/git-upload-pack$`), "POST", serviceUploadPack},
- {regexp.MustCompile(`(.*?)/git-receive-pack$`), "POST", serviceReceivePack},
- {regexp.MustCompile(`(.*?)/info/refs$`), "GET", getInfoRefs},
- {regexp.MustCompile(`(.*?)/HEAD$`), "GET", getTextFile},
- {regexp.MustCompile(`(.*?)/objects/info/alternates$`), "GET", getTextFile},
- {regexp.MustCompile(`(.*?)/objects/info/http-alternates$`), "GET", getTextFile},
- {regexp.MustCompile(`(.*?)/objects/info/packs$`), "GET", getInfoPacks},
- {regexp.MustCompile(`(.*?)/objects/info/[^/]*$`), "GET", getTextFile},
- {regexp.MustCompile(`(.*?)/objects/[0-9a-f]{2}/[0-9a-f]{38}$`), "GET", getLooseObject},
- {regexp.MustCompile(`(.*?)/objects/pack/pack-[0-9a-f]{40}\.pack$`), "GET", getPackFile},
- {regexp.MustCompile(`(.*?)/objects/pack/pack-[0-9a-f]{40}\.idx$`), "GET", getIdxFile},
-}
-
// one or more key=value pairs separated by colons
var safeGitProtocolHeader = regexp.MustCompile(`^[0-9a-zA-Z]+=[0-9a-zA-Z]+(:[0-9a-zA-Z]+=[0-9a-zA-Z]+)*$`)
}
}
-func serviceUploadPack(h serviceHandler) {
- serviceRPC(h, "upload-pack")
+// ServiceUploadPack implements Git Smart HTTP protocol
+func ServiceUploadPack(ctx *context.Context) {
+ h := httpBase(ctx)
+ if h != nil {
+ serviceRPC(*h, "upload-pack")
+ }
}
-func serviceReceivePack(h serviceHandler) {
- serviceRPC(h, "receive-pack")
+// ServiceReceivePack implements Git Smart HTTP protocol
+func ServiceReceivePack(ctx *context.Context) {
+ h := httpBase(ctx)
+ if h != nil {
+ serviceRPC(*h, "receive-pack")
+ }
}
func getServiceType(r *http.Request) string {
return []byte(s + str)
}
-func getInfoRefs(h serviceHandler) {
+// GetInfoRefs implements Git dumb HTTP
+func GetInfoRefs(ctx *context.Context) {
+ h := httpBase(ctx)
+ if h == nil {
+ return
+ }
h.setHeaderNoCache()
- if hasAccess(getServiceType(h.r), h, false) {
+ if hasAccess(getServiceType(h.r), *h, false) {
service := getServiceType(h.r)
if protocol := h.r.Header.Get("Git-Protocol"); protocol != "" && safeGitProtocolHeader.MatchString(protocol) {
_, _ = h.w.Write(refs)
} else {
updateServerInfo(h.dir)
- h.sendFile("text/plain; charset=utf-8")
+ h.sendFile("text/plain; charset=utf-8", "info/refs")
}
}
-func getTextFile(h serviceHandler) {
- h.setHeaderNoCache()
- h.sendFile("text/plain")
-}
-
-func getInfoPacks(h serviceHandler) {
- h.setHeaderCacheForever()
- h.sendFile("text/plain; charset=utf-8")
-}
-
-func getLooseObject(h serviceHandler) {
- h.setHeaderCacheForever()
- h.sendFile("application/x-git-loose-object")
+// GetTextFile implements Git dumb HTTP
+func GetTextFile(p string) func(*context.Context) {
+ return func(ctx *context.Context) {
+ h := httpBase(ctx)
+ if h != nil {
+ h.setHeaderNoCache()
+ file := ctx.Params("file")
+ if file != "" {
+ h.sendFile("text/plain", "objects/info/"+file)
+ } else {
+ h.sendFile("text/plain", p)
+ }
+ }
+ }
}
-func getPackFile(h serviceHandler) {
- h.setHeaderCacheForever()
- h.sendFile("application/x-git-packed-objects")
+// GetInfoPacks implements Git dumb HTTP
+func GetInfoPacks(ctx *context.Context) {
+ h := httpBase(ctx)
+ if h != nil {
+ h.setHeaderCacheForever()
+ h.sendFile("text/plain; charset=utf-8", "objects/info/packs")
+ }
}
-func getIdxFile(h serviceHandler) {
- h.setHeaderCacheForever()
- h.sendFile("application/x-git-packed-objects-toc")
+// GetLooseObject implements Git dumb HTTP
+func GetLooseObject(ctx *context.Context) {
+ h := httpBase(ctx)
+ if h != nil {
+ h.setHeaderCacheForever()
+ h.sendFile("application/x-git-loose-object", fmt.Sprintf("objects/%s/%s",
+ ctx.Params("head"), ctx.Params("hash")))
+ }
}
-func getGitRepoPath(subdir string) (string, error) {
- if !strings.HasSuffix(subdir, ".git") {
- subdir += ".git"
+// GetPackFile implements Git dumb HTTP
+func GetPackFile(ctx *context.Context) {
+ h := httpBase(ctx)
+ if h != nil {
+ h.setHeaderCacheForever()
+ h.sendFile("application/x-git-packed-objects", "objects/pack/pack-"+ctx.Params("file")+".pack")
}
+}
- fpath := path.Join(setting.RepoRootPath, subdir)
- if _, err := os.Stat(fpath); os.IsNotExist(err) {
- return "", err
+// GetIdxFile implements Git dumb HTTP
+func GetIdxFile(ctx *context.Context) {
+ h := httpBase(ctx)
+ if h != nil {
+ h.setHeaderCacheForever()
+ h.sendFile("application/x-git-packed-objects-toc", "objects/pack/pack-"+ctx.Params("file")+".idx")
}
-
- return fpath, nil
}
"strings"
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert"
+ auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/git"
issue_indexer "code.gitea.io/gitea/modules/indexer/issues"
"code.gitea.io/gitea/modules/log"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/upload"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/web"
comment_service "code.gitea.io/gitea/services/comments"
issue_service "code.gitea.io/gitea/services/issue"
pull_service "code.gitea.io/gitea/services/pull"
}
// NewIssuePost response for creating new issue
-func NewIssuePost(ctx *context.Context, form auth.CreateIssueForm) {
+func NewIssuePost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.CreateIssueForm)
ctx.Data["Title"] = ctx.Tr("repo.issues.new")
ctx.Data["PageIsIssueList"] = true
ctx.Data["NewIssueChooseTemplate"] = len(ctx.IssueTemplatesFromDefaultBranch()) > 0
attachments []string
)
- labelIDs, assigneeIDs, milestoneID, projectID := ValidateRepoMetas(ctx, form, false)
+ labelIDs, assigneeIDs, milestoneID, projectID := ValidateRepoMetas(ctx, *form, false)
if ctx.Written() {
return
}
}
// NewComment create a comment for issue
-func NewComment(ctx *context.Context, form auth.CreateCommentForm) {
+func NewComment(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.CreateCommentForm)
issue := GetActionIssue(ctx)
if ctx.Written() {
return
}
// ChangeIssueReaction create a reaction for issue
-func ChangeIssueReaction(ctx *context.Context, form auth.ReactionForm) {
+func ChangeIssueReaction(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.ReactionForm)
issue := GetActionIssue(ctx)
if ctx.Written() {
return
}
// ChangeCommentReaction create a reaction for comment
-func ChangeCommentReaction(ctx *context.Context, form auth.ReactionForm) {
+func ChangeCommentReaction(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.ReactionForm)
comment, err := models.GetCommentByID(ctx.ParamsInt64(":id"))
if err != nil {
ctx.NotFoundOrServerError("GetCommentByID", models.IsErrCommentNotExist, err)
import (
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
+ auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/web"
issue_service "code.gitea.io/gitea/services/issue"
)
}
// InitializeLabels init labels for a repository
-func InitializeLabels(ctx *context.Context, form auth.InitializeLabelsForm) {
+func InitializeLabels(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.InitializeLabelsForm)
if ctx.HasError() {
ctx.Redirect(ctx.Repo.RepoLink + "/labels")
return
}
// NewLabel create new label for repository
-func NewLabel(ctx *context.Context, form auth.CreateLabelForm) {
+func NewLabel(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.CreateLabelForm)
ctx.Data["Title"] = ctx.Tr("repo.labels")
ctx.Data["PageIsLabels"] = true
}
// UpdateLabel update a label's name and color
-func UpdateLabel(ctx *context.Context, form auth.CreateLabelForm) {
+func UpdateLabel(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.CreateLabelForm)
l, err := models.GetLabelInRepoByID(ctx.Repo.Repository.ID, form.ID)
if err != nil {
switch {
"testing"
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
+ auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/test"
+ "code.gitea.io/gitea/modules/web"
"github.com/stretchr/testify/assert"
)
ctx := test.MockContext(t, "user2/repo1/labels/initialize")
test.LoadUser(t, ctx, 2)
test.LoadRepo(t, ctx, 2)
- InitializeLabels(ctx, auth.InitializeLabelsForm{TemplateName: "Default"})
+ web.SetForm(ctx, &auth.InitializeLabelsForm{TemplateName: "Default"})
+ InitializeLabels(ctx)
assert.EqualValues(t, http.StatusFound, ctx.Resp.Status())
models.AssertExistsAndLoadBean(t, &models.Label{
RepoID: 2,
ctx := test.MockContext(t, "user2/repo1/labels/edit")
test.LoadUser(t, ctx, 2)
test.LoadRepo(t, ctx, 1)
- NewLabel(ctx, auth.CreateLabelForm{
+ web.SetForm(ctx, &auth.CreateLabelForm{
Title: "newlabel",
Color: "#abcdef",
})
+ NewLabel(ctx)
assert.EqualValues(t, http.StatusFound, ctx.Resp.Status())
models.AssertExistsAndLoadBean(t, &models.Label{
Name: "newlabel",
ctx := test.MockContext(t, "user2/repo1/labels/edit")
test.LoadUser(t, ctx, 2)
test.LoadRepo(t, ctx, 1)
- UpdateLabel(ctx, auth.CreateLabelForm{
+ web.SetForm(ctx, &auth.CreateLabelForm{
ID: 2,
Title: "newnameforlabel",
Color: "#abcdef",
})
+ UpdateLabel(ctx)
assert.EqualValues(t, http.StatusFound, ctx.Resp.Status())
models.AssertExistsAndLoadBean(t, &models.Label{
ID: 2,
"net/http"
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/context"
+ auth "code.gitea.io/gitea/modules/forms"
+ "code.gitea.io/gitea/modules/web"
)
// LockIssue locks an issue. This would limit commenting abilities to
// users with write access to the repo.
-func LockIssue(ctx *context.Context, form auth.IssueLockForm) {
-
+func LockIssue(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.IssueLockForm)
issue := GetActionIssue(ctx)
if ctx.Written() {
return
"time"
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/context"
+ auth "code.gitea.io/gitea/modules/forms"
+ "code.gitea.io/gitea/modules/web"
)
// AddTimeManually tracks time manually
-func AddTimeManually(c *context.Context, form auth.AddTimeManuallyForm) {
+func AddTimeManually(c *context.Context) {
+ form := web.GetForm(c).(*auth.AddTimeManuallyForm)
issue := GetActionIssue(c)
if c.Written() {
return
"strings"
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
+ auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/migrations"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/task"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/web"
)
const (
}
// MigratePost response for migrating from external git repository
-func MigratePost(ctx *context.Context, form auth.MigrateRepoForm) {
+func MigratePost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.MigrateRepoForm)
if setting.Repository.DisableMigrations {
ctx.Error(http.StatusForbidden, "MigratePost: the site administrator has disabled migrations")
return
err = models.CheckCreateRepository(ctx.User, ctxUser, opts.RepoName, false)
if err != nil {
- handleMigrateError(ctx, ctxUser, err, "MigratePost", tpl, &form)
+ handleMigrateError(ctx, ctxUser, err, "MigratePost", tpl, form)
return
}
return
}
- handleMigrateError(ctx, ctxUser, err, "MigratePost", tpl, &form)
+ handleMigrateError(ctx, ctxUser, err, "MigratePost", tpl, form)
}
"time"
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
+ auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/web"
"xorm.io/builder"
)
}
// NewMilestonePost response for creating milestone
-func NewMilestonePost(ctx *context.Context, form auth.CreateMilestoneForm) {
+func NewMilestonePost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.CreateMilestoneForm)
ctx.Data["Title"] = ctx.Tr("repo.milestones.new")
ctx.Data["PageIsIssueList"] = true
ctx.Data["PageIsMilestones"] = true
}
// EditMilestonePost response for edting milestone
-func EditMilestonePost(ctx *context.Context, form auth.CreateMilestoneForm) {
+func EditMilestonePost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.CreateMilestoneForm)
ctx.Data["Title"] = ctx.Tr("repo.milestones.edit")
ctx.Data["PageIsMilestones"] = true
ctx.Data["PageIsEditMilestone"] = true
"strings"
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
+ auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/web"
)
const (
}
// NewProjectPost creates a new project
-func NewProjectPost(ctx *context.Context, form auth.CreateProjectForm) {
+func NewProjectPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.CreateProjectForm)
ctx.Data["Title"] = ctx.Tr("repo.projects.new")
if ctx.HasError() {
}
// EditProjectPost response for editing a project
-func EditProjectPost(ctx *context.Context, form auth.CreateProjectForm) {
+func EditProjectPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.CreateProjectForm)
ctx.Data["Title"] = ctx.Tr("repo.projects.edit")
ctx.Data["PageIsProjects"] = true
ctx.Data["PageIsEditProjects"] = true
}
// AddBoardToProjectPost allows a new board to be added to a project.
-func AddBoardToProjectPost(ctx *context.Context, form auth.EditProjectBoardTitleForm) {
-
+func AddBoardToProjectPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.EditProjectBoardTitleForm)
if !ctx.Repo.IsOwner() && !ctx.Repo.IsAdmin() && !ctx.Repo.CanAccess(models.AccessModeWrite, models.UnitTypeProjects) {
ctx.JSON(403, map[string]string{
"message": "Only authorized users are allowed to perform this action.",
}
// EditProjectBoardTitle allows a project board's title to be updated
-func EditProjectBoardTitle(ctx *context.Context, form auth.EditProjectBoardTitleForm) {
-
+func EditProjectBoardTitle(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.EditProjectBoardTitleForm)
_, board := checkProjectBoardChangePermissions(ctx)
if ctx.Written() {
return
"time"
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
+ auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/middlewares"
"code.gitea.io/gitea/modules/notification"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/upload"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/utils"
"code.gitea.io/gitea/services/gitdiff"
pull_service "code.gitea.io/gitea/services/pull"
}
// ForkPost response for forking a repository
-func ForkPost(ctx *context.Context, form auth.CreateRepoForm) {
+func ForkPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.CreateRepoForm)
ctx.Data["Title"] = ctx.Tr("new_fork")
ctxUser := checkContextUser(ctx, form.UID)
}
// MergePullRequest response for merging pull request
-func MergePullRequest(ctx *context.Context, form auth.MergePullRequestForm) {
+func MergePullRequest(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.MergePullRequestForm)
issue := checkPullInfo(ctx)
if ctx.Written() {
return
}
// CompareAndPullRequestPost response for creating pull request
-func CompareAndPullRequestPost(ctx *context.Context, form auth.CreateIssueForm) {
+func CompareAndPullRequestPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.CreateIssueForm)
ctx.Data["Title"] = ctx.Tr("repo.pulls.compare_changes")
ctx.Data["PageIsComparePull"] = true
ctx.Data["IsDiffCompare"] = true
}
defer headGitRepo.Close()
- labelIDs, assigneeIDs, milestoneID, _ := ValidateRepoMetas(ctx, form, true)
+ labelIDs, assigneeIDs, milestoneID, _ := ValidateRepoMetas(ctx, *form, true)
if ctx.Written() {
return
}
}
if ctx.HasError() {
- auth.AssignForm(form, ctx.Data)
+ middlewares.AssignForm(form, ctx.Data)
// This stage is already stop creating new pull request, so it does not matter if it has
// something to compare or not.
"fmt"
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
+ auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/web"
pull_service "code.gitea.io/gitea/services/pull"
)
}
// CreateCodeComment will create a code comment including an pending review if required
-func CreateCodeComment(ctx *context.Context, form auth.CodeCommentForm) {
+func CreateCodeComment(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.CodeCommentForm)
issue := GetActionIssue(ctx)
if !issue.IsPull {
return
}
// SubmitReview creates a review out of the existing pending review or creates a new one if no pending review exist
-func SubmitReview(ctx *context.Context, form auth.SubmitReviewForm) {
+func SubmitReview(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.SubmitReviewForm)
issue := GetActionIssue(ctx)
if !issue.IsPull {
return
"fmt"
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert"
+ auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/upload"
+ "code.gitea.io/gitea/modules/web"
releaseservice "code.gitea.io/gitea/services/release"
)
}
// NewReleasePost response for creating a release
-func NewReleasePost(ctx *context.Context, form auth.NewReleaseForm) {
+func NewReleasePost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.NewReleaseForm)
ctx.Data["Title"] = ctx.Tr("repo.release.new_release")
ctx.Data["PageIsReleaseList"] = true
}
// EditReleasePost response for edit release
-func EditReleasePost(ctx *context.Context, form auth.EditReleaseForm) {
+func EditReleasePost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.EditReleaseForm)
ctx.Data["Title"] = ctx.Tr("repo.release.edit_release")
ctx.Data["PageIsReleaseList"] = true
ctx.Data["PageIsEditRelease"] = true
"testing"
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
+ auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/test"
+ "code.gitea.io/gitea/modules/web"
)
func TestNewReleasePost(t *testing.T) {
test.LoadUser(t, ctx, 2)
test.LoadRepo(t, ctx, 1)
test.LoadGitRepo(t, ctx)
- NewReleasePost(ctx, testCase.Form)
+ web.SetForm(ctx, &testCase.Form)
+ NewReleasePost(ctx)
models.AssertExistsAndLoadBean(t, &models.Release{
RepoID: 1,
PublisherID: 2,
"time"
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
+ auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/web"
archiver_service "code.gitea.io/gitea/services/archiver"
repo_service "code.gitea.io/gitea/services/repository"
)
}
// CreatePost response for creating repository
-func CreatePost(ctx *context.Context, form auth.CreateRepoForm) {
+func CreatePost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.CreateRepoForm)
ctx.Data["Title"] = ctx.Tr("new_repo")
ctx.Data["Gitignores"] = models.Gitignores
"time"
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
+ auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/validation"
+ "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/utils"
"code.gitea.io/gitea/services/mailer"
mirror_service "code.gitea.io/gitea/services/mirror"
}
// SettingsPost response for changes of a repository
-func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
+func SettingsPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.RepoSettingForm)
ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["PageIsSettingsOptions"] = true
}
// DeployKeysPost response for adding a deploy key of a repository
-func DeployKeysPost(ctx *context.Context, form auth.AddKeyForm) {
+func DeployKeysPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.AddKeyForm)
ctx.Data["Title"] = ctx.Tr("repo.settings.deploy_keys")
ctx.Data["PageIsSettingsKeys"] = true
}
// SettingsAvatar save new POSTed repository avatar
-func SettingsAvatar(ctx *context.Context, form auth.AvatarForm) {
+func SettingsAvatar(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.AvatarForm)
form.Source = auth.AvatarLocal
- if err := UpdateAvatarSetting(ctx, form); err != nil {
+ if err := UpdateAvatarSetting(ctx, *form); err != nil {
ctx.Flash.Error(err.Error())
} else {
ctx.Flash.Success(ctx.Tr("repo.settings.update_avatar_success"))
"time"
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
+ auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/web"
pull_service "code.gitea.io/gitea/services/pull"
)
}
// SettingsProtectedBranchPost updates the protected branch settings
-func SettingsProtectedBranchPost(ctx *context.Context, f auth.ProtectBranchForm) {
+func SettingsProtectedBranchPost(ctx *context.Context) {
+ f := web.GetForm(ctx).(*auth.ProtectBranchForm)
branch := ctx.Params("*")
if !ctx.Repo.GitRepo.IsBranchExist(branch) {
ctx.NotFound("IsBranchExist", nil)
"testing"
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/context"
+ auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/web"
"github.com/stretchr/testify/assert"
)
Title: "read-only",
Content: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment\n",
}
- DeployKeysPost(ctx, addKeyForm)
+ web.SetForm(ctx, &addKeyForm)
+ DeployKeysPost(ctx)
assert.EqualValues(t, http.StatusFound, ctx.Resp.Status())
models.AssertExistsAndLoadBean(t, &models.DeployKey{
Content: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment\n",
IsWritable: true,
}
- DeployKeysPost(ctx, addKeyForm)
+ web.SetForm(ctx, &addKeyForm)
+ DeployKeysPost(ctx)
assert.EqualValues(t, http.StatusFound, ctx.Resp.Status())
models.AssertExistsAndLoadBean(t, &models.DeployKey{
"strings"
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert"
+ auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/webhook"
)
}
// GiteaHooksNewPost response for creating Gitea webhook
-func GiteaHooksNewPost(ctx *context.Context, form auth.NewWebhookForm) {
+func GiteaHooksNewPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.NewWebhookForm)
ctx.Data["Title"] = ctx.Tr("repo.settings.add_webhook")
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksNew"] = true
}
// GogsHooksNewPost response for creating webhook
-func GogsHooksNewPost(ctx *context.Context, form auth.NewGogshookForm) {
- newGogsWebhookPost(ctx, form, models.GOGS)
+func GogsHooksNewPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.NewGogshookForm)
+ newGogsWebhookPost(ctx, *form, models.GOGS)
}
// newGogsWebhookPost response for creating gogs hook
}
// DiscordHooksNewPost response for creating discord hook
-func DiscordHooksNewPost(ctx *context.Context, form auth.NewDiscordHookForm) {
+func DiscordHooksNewPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.NewDiscordHookForm)
ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksNew"] = true
}
// DingtalkHooksNewPost response for creating dingtalk hook
-func DingtalkHooksNewPost(ctx *context.Context, form auth.NewDingtalkHookForm) {
+func DingtalkHooksNewPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.NewDingtalkHookForm)
ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksNew"] = true
}
// TelegramHooksNewPost response for creating telegram hook
-func TelegramHooksNewPost(ctx *context.Context, form auth.NewTelegramHookForm) {
+func TelegramHooksNewPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.NewTelegramHookForm)
ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksNew"] = true
}
// MatrixHooksNewPost response for creating a Matrix hook
-func MatrixHooksNewPost(ctx *context.Context, form auth.NewMatrixHookForm) {
+func MatrixHooksNewPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.NewMatrixHookForm)
ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksNew"] = true
}
// MSTeamsHooksNewPost response for creating MS Teams hook
-func MSTeamsHooksNewPost(ctx *context.Context, form auth.NewMSTeamsHookForm) {
+func MSTeamsHooksNewPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.NewMSTeamsHookForm)
ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksNew"] = true
}
// SlackHooksNewPost response for creating slack hook
-func SlackHooksNewPost(ctx *context.Context, form auth.NewSlackHookForm) {
+func SlackHooksNewPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.NewSlackHookForm)
ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksNew"] = true
}
// FeishuHooksNewPost response for creating feishu hook
-func FeishuHooksNewPost(ctx *context.Context, form auth.NewFeishuHookForm) {
+func FeishuHooksNewPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.NewFeishuHookForm)
ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksNew"] = true
}
// WebHooksEditPost response for editing web hook
-func WebHooksEditPost(ctx *context.Context, form auth.NewWebhookForm) {
+func WebHooksEditPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.NewWebhookForm)
ctx.Data["Title"] = ctx.Tr("repo.settings.update_webhook")
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksEdit"] = true
}
// GogsHooksEditPost response for editing gogs hook
-func GogsHooksEditPost(ctx *context.Context, form auth.NewGogshookForm) {
+func GogsHooksEditPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.NewGogshookForm)
ctx.Data["Title"] = ctx.Tr("repo.settings.update_webhook")
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksEdit"] = true
}
// SlackHooksEditPost response for editing slack hook
-func SlackHooksEditPost(ctx *context.Context, form auth.NewSlackHookForm) {
+func SlackHooksEditPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.NewSlackHookForm)
ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksEdit"] = true
}
// DiscordHooksEditPost response for editing discord hook
-func DiscordHooksEditPost(ctx *context.Context, form auth.NewDiscordHookForm) {
+func DiscordHooksEditPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.NewDiscordHookForm)
ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksEdit"] = true
}
// DingtalkHooksEditPost response for editing discord hook
-func DingtalkHooksEditPost(ctx *context.Context, form auth.NewDingtalkHookForm) {
+func DingtalkHooksEditPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.NewDingtalkHookForm)
ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksEdit"] = true
}
// TelegramHooksEditPost response for editing discord hook
-func TelegramHooksEditPost(ctx *context.Context, form auth.NewTelegramHookForm) {
+func TelegramHooksEditPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.NewTelegramHookForm)
ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksEdit"] = true
}
// MatrixHooksEditPost response for editing a Matrix hook
-func MatrixHooksEditPost(ctx *context.Context, form auth.NewMatrixHookForm) {
+func MatrixHooksEditPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.NewMatrixHookForm)
ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksEdit"] = true
}
// MSTeamsHooksEditPost response for editing MS Teams hook
-func MSTeamsHooksEditPost(ctx *context.Context, form auth.NewMSTeamsHookForm) {
+func MSTeamsHooksEditPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.NewMSTeamsHookForm)
ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksEdit"] = true
}
// FeishuHooksEditPost response for editing feishu hook
-func FeishuHooksEditPost(ctx *context.Context, form auth.NewFeishuHookForm) {
+func FeishuHooksEditPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.NewFeishuHookForm)
ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksEdit"] = true
"strings"
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
+ auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/web"
wiki_service "code.gitea.io/gitea/services/wiki"
)
}
// NewWikiPost response for wiki create request
-func NewWikiPost(ctx *context.Context, form auth.NewWikiForm) {
+func NewWikiPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.NewWikiForm)
ctx.Data["Title"] = ctx.Tr("repo.wiki.new_page")
ctx.Data["PageIsWiki"] = true
ctx.Data["RequireSimpleMDE"] = true
}
// EditWikiPost response for wiki modify request
-func EditWikiPost(ctx *context.Context, form auth.NewWikiForm) {
+func EditWikiPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.NewWikiForm)
ctx.Data["Title"] = ctx.Tr("repo.wiki.new_page")
ctx.Data["PageIsWiki"] = true
ctx.Data["RequireSimpleMDE"] = true
"testing"
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
+ auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/test"
+ "code.gitea.io/gitea/modules/web"
wiki_service "code.gitea.io/gitea/services/wiki"
"github.com/stretchr/testify/assert"
ctx := test.MockContext(t, "user2/repo1/wiki/_new")
test.LoadUser(t, ctx, 2)
test.LoadRepo(t, ctx, 1)
- NewWikiPost(ctx, auth.NewWikiForm{
+ web.SetForm(ctx, &auth.NewWikiForm{
Title: title,
Content: content,
Message: message,
})
+ NewWikiPost(ctx)
assert.EqualValues(t, http.StatusFound, ctx.Resp.Status())
assertWikiExists(t, ctx.Repo.Repository, title)
assert.Equal(t, wikiContent(t, ctx.Repo.Repository, title), content)
ctx := test.MockContext(t, "user2/repo1/wiki/_new")
test.LoadUser(t, ctx, 2)
test.LoadRepo(t, ctx, 1)
- NewWikiPost(ctx, auth.NewWikiForm{
+ web.SetForm(ctx, &auth.NewWikiForm{
Title: "_edit",
Content: content,
Message: message,
})
+ NewWikiPost(ctx)
assert.EqualValues(t, http.StatusOK, ctx.Resp.Status())
assert.EqualValues(t, ctx.Tr("repo.wiki.reserved_page"), ctx.Flash.ErrorMsg)
assertWikiNotExists(t, ctx.Repo.Repository, "_edit")
ctx.SetParams(":page", "Home")
test.LoadUser(t, ctx, 2)
test.LoadRepo(t, ctx, 1)
- EditWikiPost(ctx, auth.NewWikiForm{
+ web.SetForm(ctx, &auth.NewWikiForm{
Title: title,
Content: content,
Message: message,
})
+ EditWikiPost(ctx)
assert.EqualValues(t, http.StatusFound, ctx.Resp.Status())
assertWikiExists(t, ctx.Repo.Repository, title)
assert.Equal(t, wikiContent(t, ctx.Repo.Repository, title), content)
--- /dev/null
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package routes
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "io"
+ "net/http"
+ "os"
+ "path"
+ "strings"
+ "text/template"
+ "time"
+
+ "code.gitea.io/gitea/modules/auth/sso"
+ "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/httpcache"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/middlewares"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/storage"
+ "code.gitea.io/gitea/modules/templates"
+
+ "gitea.com/go-chi/session"
+)
+
+type routerLoggerOptions struct {
+ req *http.Request
+ Identity *string
+ Start *time.Time
+ ResponseWriter http.ResponseWriter
+}
+
+// SignedUserName returns signed user's name via context
+func SignedUserName(req *http.Request) string {
+ ctx := context.GetContext(req)
+ if ctx != nil {
+ v := ctx.Data["SignedUserName"]
+ if res, ok := v.(string); ok {
+ return res
+ }
+ }
+ return ""
+}
+
+func accessLogger() func(http.Handler) http.Handler {
+ logger := log.GetLogger("access")
+ logTemplate, _ := template.New("log").Parse(setting.AccessLogTemplate)
+ return func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ start := time.Now()
+ next.ServeHTTP(w, req)
+ identity := "-"
+ if val := SignedUserName(req); val != "" {
+ identity = val
+ }
+ rw := w
+
+ buf := bytes.NewBuffer([]byte{})
+ err := logTemplate.Execute(buf, routerLoggerOptions{
+ req: req,
+ Identity: &identity,
+ Start: &start,
+ ResponseWriter: rw,
+ })
+ if err != nil {
+ log.Error("Could not set up chi access logger: %v", err.Error())
+ }
+
+ err = logger.SendLog(log.INFO, "", "", 0, buf.String(), "")
+ if err != nil {
+ log.Error("Could not set up chi access logger: %v", err.Error())
+ }
+ })
+ }
+}
+
+// LoggerHandler is a handler that will log the routing to the default gitea log
+func LoggerHandler(level log.Level) func(next http.Handler) http.Handler {
+ return func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ start := time.Now()
+
+ _ = log.GetLogger("router").Log(0, level, "Started %s %s for %s", log.ColoredMethod(req.Method), req.URL.RequestURI(), req.RemoteAddr)
+
+ next.ServeHTTP(w, req)
+
+ var status int
+ if v, ok := w.(context.ResponseWriter); ok {
+ status = v.Status()
+ }
+
+ _ = log.GetLogger("router").Log(0, level, "Completed %s %s %v %s in %v", log.ColoredMethod(req.Method), req.URL.RequestURI(), log.ColoredStatus(status), log.ColoredStatus(status, http.StatusText(status)), log.ColoredTime(time.Since(start)))
+ })
+ }
+}
+
+func storageHandler(storageSetting setting.Storage, prefix string, objStore storage.ObjectStorage) func(next http.Handler) http.Handler {
+ return func(next http.Handler) http.Handler {
+ if storageSetting.ServeDirect {
+ return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ if req.Method != "GET" && req.Method != "HEAD" {
+ next.ServeHTTP(w, req)
+ return
+ }
+
+ if !strings.HasPrefix(req.URL.RequestURI(), "/"+prefix) {
+ next.ServeHTTP(w, req)
+ return
+ }
+
+ rPath := strings.TrimPrefix(req.URL.RequestURI(), "/"+prefix)
+ u, err := objStore.URL(rPath, path.Base(rPath))
+ if err != nil {
+ if os.IsNotExist(err) || errors.Is(err, os.ErrNotExist) {
+ log.Warn("Unable to find %s %s", prefix, rPath)
+ http.Error(w, "file not found", 404)
+ return
+ }
+ log.Error("Error whilst getting URL for %s %s. Error: %v", prefix, rPath, err)
+ http.Error(w, fmt.Sprintf("Error whilst getting URL for %s %s", prefix, rPath), 500)
+ return
+ }
+ http.Redirect(
+ w,
+ req,
+ u.String(),
+ 301,
+ )
+ })
+ }
+
+ return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ if req.Method != "GET" && req.Method != "HEAD" {
+ next.ServeHTTP(w, req)
+ return
+ }
+
+ if !strings.HasPrefix(req.URL.RequestURI(), "/"+prefix) {
+ next.ServeHTTP(w, req)
+ return
+ }
+
+ rPath := strings.TrimPrefix(req.URL.RequestURI(), "/"+prefix)
+ rPath = strings.TrimPrefix(rPath, "/")
+
+ fi, err := objStore.Stat(rPath)
+ if err == nil && httpcache.HandleTimeCache(req, w, fi) {
+ return
+ }
+
+ //If we have matched and access to release or issue
+ fr, err := objStore.Open(rPath)
+ if err != nil {
+ if os.IsNotExist(err) || errors.Is(err, os.ErrNotExist) {
+ log.Warn("Unable to find %s %s", prefix, rPath)
+ http.Error(w, "file not found", 404)
+ return
+ }
+ log.Error("Error whilst opening %s %s. Error: %v", prefix, rPath, err)
+ http.Error(w, fmt.Sprintf("Error whilst opening %s %s", prefix, rPath), 500)
+ return
+ }
+ defer fr.Close()
+
+ _, err = io.Copy(w, fr)
+ if err != nil {
+ log.Error("Error whilst rendering %s %s. Error: %v", prefix, rPath, err)
+ http.Error(w, fmt.Sprintf("Error whilst rendering %s %s", prefix, rPath), 500)
+ return
+ }
+ })
+ }
+}
+
+type dataStore struct {
+ Data map[string]interface{}
+}
+
+func (d *dataStore) GetData() map[string]interface{} {
+ return d.Data
+}
+
+// Recovery returns a middleware that recovers from any panics and writes a 500 and a log if so.
+// This error will be created with the gitea 500 page.
+func Recovery() func(next http.Handler) http.Handler {
+ var rnd = templates.HTMLRenderer()
+ return func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ defer func() {
+ if err := recover(); err != nil {
+ combinedErr := fmt.Sprintf("PANIC: %v\n%s", err, string(log.Stack(2)))
+ log.Error("%v", combinedErr)
+
+ sessionStore := session.GetSession(req)
+ if sessionStore == nil {
+ if setting.IsProd() {
+ http.Error(w, http.StatusText(500), 500)
+ } else {
+ http.Error(w, combinedErr, 500)
+ }
+ return
+ }
+
+ var lc = middlewares.Locale(w, req)
+ var store = dataStore{
+ Data: templates.Vars{
+ "Language": lc.Language(),
+ "CurrentURL": setting.AppSubURL + req.URL.RequestURI(),
+ "i18n": lc,
+ },
+ }
+
+ // Get user from session if logged in.
+ user, _ := sso.SignedInUser(req, w, &store, sessionStore)
+ if user != nil {
+ store.Data["IsSigned"] = true
+ store.Data["SignedUser"] = user
+ store.Data["SignedUserID"] = user.ID
+ store.Data["SignedUserName"] = user.Name
+ store.Data["IsAdmin"] = user.IsAdmin
+ } else {
+ store.Data["SignedUserID"] = int64(0)
+ store.Data["SignedUserName"] = ""
+ }
+
+ w.Header().Set(`X-Frame-Options`, `SAMEORIGIN`)
+
+ if !setting.IsProd() {
+ store.Data["ErrorMsg"] = combinedErr
+ }
+ err = rnd.HTML(w, 500, "status/500", templates.BaseVars().Merge(store.Data))
+ if err != nil {
+ log.Error("%v", err)
+ }
+ }
+ }()
+
+ next.ServeHTTP(w, req)
+ })
+ }
+}
+++ /dev/null
-// Copyright 2020 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package routes
-
-import (
- "bytes"
- "errors"
- "fmt"
- "io"
- "net/http"
- "os"
- "path"
- "strings"
- "text/template"
- "time"
-
- "code.gitea.io/gitea/modules/context"
- "code.gitea.io/gitea/modules/httpcache"
- "code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/metrics"
- "code.gitea.io/gitea/modules/public"
- "code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/storage"
- "code.gitea.io/gitea/routers"
-
- "gitea.com/go-chi/session"
- "github.com/go-chi/chi"
- "github.com/go-chi/chi/middleware"
- "github.com/prometheus/client_golang/prometheus"
-)
-
-type routerLoggerOptions struct {
- req *http.Request
- Identity *string
- Start *time.Time
- ResponseWriter http.ResponseWriter
-}
-
-// SignedUserName returns signed user's name via context
-// FIXME currently no any data stored on chi.Context but macaron.Context, so this will
-// return "" before we remove macaron totally
-func SignedUserName(req *http.Request) string {
- if v, ok := req.Context().Value("SignedUserName").(string); ok {
- return v
- }
- return ""
-}
-
-func setupAccessLogger(c chi.Router) {
- logger := log.GetLogger("access")
-
- logTemplate, _ := template.New("log").Parse(setting.AccessLogTemplate)
- c.Use(func(next http.Handler) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
- start := time.Now()
- next.ServeHTTP(w, req)
- identity := "-"
- if val := SignedUserName(req); val != "" {
- identity = val
- }
- rw := w
-
- buf := bytes.NewBuffer([]byte{})
- err := logTemplate.Execute(buf, routerLoggerOptions{
- req: req,
- Identity: &identity,
- Start: &start,
- ResponseWriter: rw,
- })
- if err != nil {
- log.Error("Could not set up macaron access logger: %v", err.Error())
- }
-
- err = logger.SendLog(log.INFO, "", "", 0, buf.String(), "")
- if err != nil {
- log.Error("Could not set up macaron access logger: %v", err.Error())
- }
- })
- })
-}
-
-// LoggerHandler is a handler that will log the routing to the default gitea log
-func LoggerHandler(level log.Level) func(next http.Handler) http.Handler {
- return func(next http.Handler) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
- start := time.Now()
-
- _ = log.GetLogger("router").Log(0, level, "Started %s %s for %s", log.ColoredMethod(req.Method), req.URL.RequestURI(), req.RemoteAddr)
-
- next.ServeHTTP(w, req)
-
- var status int
- if v, ok := w.(context.ResponseWriter); ok {
- status = v.Status()
- }
-
- _ = log.GetLogger("router").Log(0, level, "Completed %s %s %v %s in %v", log.ColoredMethod(req.Method), req.URL.RequestURI(), log.ColoredStatus(status), log.ColoredStatus(status, http.StatusText(status)), log.ColoredTime(time.Since(start)))
- })
- }
-}
-
-func storageHandler(storageSetting setting.Storage, prefix string, objStore storage.ObjectStorage) func(next http.Handler) http.Handler {
- return func(next http.Handler) http.Handler {
- if storageSetting.ServeDirect {
- return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
- if req.Method != "GET" && req.Method != "HEAD" {
- next.ServeHTTP(w, req)
- return
- }
-
- if !strings.HasPrefix(req.URL.RequestURI(), "/"+prefix) {
- next.ServeHTTP(w, req)
- return
- }
-
- rPath := strings.TrimPrefix(req.URL.RequestURI(), "/"+prefix)
- u, err := objStore.URL(rPath, path.Base(rPath))
- if err != nil {
- if os.IsNotExist(err) || errors.Is(err, os.ErrNotExist) {
- log.Warn("Unable to find %s %s", prefix, rPath)
- http.Error(w, "file not found", 404)
- return
- }
- log.Error("Error whilst getting URL for %s %s. Error: %v", prefix, rPath, err)
- http.Error(w, fmt.Sprintf("Error whilst getting URL for %s %s", prefix, rPath), 500)
- return
- }
- http.Redirect(
- w,
- req,
- u.String(),
- 301,
- )
- })
- }
-
- return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
- if req.Method != "GET" && req.Method != "HEAD" {
- next.ServeHTTP(w, req)
- return
- }
-
- if !strings.HasPrefix(req.URL.RequestURI(), "/"+prefix) {
- next.ServeHTTP(w, req)
- return
- }
-
- rPath := strings.TrimPrefix(req.URL.RequestURI(), "/"+prefix)
- rPath = strings.TrimPrefix(rPath, "/")
-
- fi, err := objStore.Stat(rPath)
- if err == nil && httpcache.HandleTimeCache(req, w, fi) {
- return
- }
-
- //If we have matched and access to release or issue
- fr, err := objStore.Open(rPath)
- if err != nil {
- if os.IsNotExist(err) || errors.Is(err, os.ErrNotExist) {
- log.Warn("Unable to find %s %s", prefix, rPath)
- http.Error(w, "file not found", 404)
- return
- }
- log.Error("Error whilst opening %s %s. Error: %v", prefix, rPath, err)
- http.Error(w, fmt.Sprintf("Error whilst opening %s %s", prefix, rPath), 500)
- return
- }
- defer fr.Close()
-
- _, err = io.Copy(w, fr)
- if err != nil {
- log.Error("Error whilst rendering %s %s. Error: %v", prefix, rPath, err)
- http.Error(w, fmt.Sprintf("Error whilst rendering %s %s", prefix, rPath), 500)
- return
- }
- })
- }
-}
-
-var (
- sessionManager *session.Manager
-)
-
-// NewChi creates a chi Router
-func NewChi() chi.Router {
- c := chi.NewRouter()
- c.Use(func(next http.Handler) http.Handler {
- return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
- next.ServeHTTP(context.NewResponse(resp), req)
- })
- })
- c.Use(middleware.RealIP)
- if !setting.DisableRouterLog && setting.RouterLogLevel != log.NONE {
- if log.GetLogger("router").GetLevel() <= setting.RouterLogLevel {
- c.Use(LoggerHandler(setting.RouterLogLevel))
- }
- }
-
- var opt = session.Options{
- Provider: setting.SessionConfig.Provider,
- ProviderConfig: setting.SessionConfig.ProviderConfig,
- CookieName: setting.SessionConfig.CookieName,
- CookiePath: setting.SessionConfig.CookiePath,
- Gclifetime: setting.SessionConfig.Gclifetime,
- Maxlifetime: setting.SessionConfig.Maxlifetime,
- Secure: setting.SessionConfig.Secure,
- Domain: setting.SessionConfig.Domain,
- }
- opt = session.PrepareOptions([]session.Options{opt})
-
- var err error
- sessionManager, err = session.NewManager(opt.Provider, opt)
- if err != nil {
- panic(err)
- }
-
- c.Use(Recovery())
- if setting.EnableAccessLog {
- setupAccessLogger(c)
- }
-
- c.Use(public.Custom(
- &public.Options{
- SkipLogging: setting.DisableRouterLog,
- },
- ))
- c.Use(public.Static(
- &public.Options{
- Directory: path.Join(setting.StaticRootPath, "public"),
- SkipLogging: setting.DisableRouterLog,
- },
- ))
-
- c.Use(storageHandler(setting.Avatar.Storage, "avatars", storage.Avatars))
- c.Use(storageHandler(setting.RepoAvatar.Storage, "repo-avatars", storage.RepoAvatars))
-
- return c
-}
-
-// RegisterInstallRoute registers the install routes
-func RegisterInstallRoute(c chi.Router) {
- m := NewMacaron()
- RegisterMacaronInstallRoute(m)
-
- // We need at least one handler in chi so that it does not drop
- // our middleware: https://github.com/go-gitea/gitea/issues/13725#issuecomment-735244395
- c.Get("/", func(w http.ResponseWriter, req *http.Request) {
- m.ServeHTTP(w, req)
- })
-
- c.NotFound(func(w http.ResponseWriter, req *http.Request) {
- m.ServeHTTP(w, req)
- })
-
- c.MethodNotAllowed(func(w http.ResponseWriter, req *http.Request) {
- m.ServeHTTP(w, req)
- })
-}
-
-// NormalRoutes represents non install routes
-func NormalRoutes() http.Handler {
- r := chi.NewRouter()
-
- // for health check
- r.Head("/", func(w http.ResponseWriter, req *http.Request) {
- w.WriteHeader(http.StatusOK)
- })
-
- if setting.HasRobotsTxt {
- r.Get("/robots.txt", func(w http.ResponseWriter, req *http.Request) {
- filePath := path.Join(setting.CustomPath, "robots.txt")
- fi, err := os.Stat(filePath)
- if err == nil && httpcache.HandleTimeCache(req, w, fi) {
- return
- }
- http.ServeFile(w, req, filePath)
- })
- }
-
- r.Get("/apple-touch-icon.png", func(w http.ResponseWriter, req *http.Request) {
- http.Redirect(w, req, path.Join(setting.StaticURLPrefix, "img/apple-touch-icon.png"), 301)
- })
-
- // prometheus metrics endpoint
- if setting.Metrics.Enabled {
- c := metrics.NewCollector()
- prometheus.MustRegister(c)
-
- r.Get("/metrics", routers.Metrics)
- }
-
- return r
-}
-
-// DelegateToMacaron delegates other routes to macaron
-func DelegateToMacaron(r chi.Router) {
- m := NewMacaron()
- RegisterMacaronRoutes(m)
-
- r.NotFound(func(w http.ResponseWriter, req *http.Request) {
- m.ServeHTTP(w, req)
- })
-
- r.MethodNotAllowed(func(w http.ResponseWriter, req *http.Request) {
- m.ServeHTTP(w, req)
- })
-}
--- /dev/null
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package routes
+
+import (
+ "fmt"
+ "net/http"
+ "path"
+
+ "code.gitea.io/gitea/modules/forms"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/middlewares"
+ "code.gitea.io/gitea/modules/public"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
+ "code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/routers"
+
+ "gitea.com/go-chi/session"
+)
+
+func installRecovery() func(next http.Handler) http.Handler {
+ var rnd = templates.HTMLRenderer()
+ return func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ defer func() {
+ // Why we need this? The first recover will try to render a beautiful
+ // error page for user, but the process can still panic again, then
+ // we have to just recover twice and send a simple error page that
+ // should not panic any more.
+ defer func() {
+ if err := recover(); err != nil {
+ combinedErr := fmt.Sprintf("PANIC: %v\n%s", err, string(log.Stack(2)))
+ log.Error(combinedErr)
+ if setting.IsProd() {
+ http.Error(w, http.StatusText(500), 500)
+ } else {
+ http.Error(w, combinedErr, 500)
+ }
+ }
+ }()
+
+ if err := recover(); err != nil {
+ combinedErr := fmt.Sprintf("PANIC: %v\n%s", err, string(log.Stack(2)))
+ log.Error("%v", combinedErr)
+
+ lc := middlewares.Locale(w, req)
+ var store = dataStore{
+ Data: templates.Vars{
+ "Language": lc.Language(),
+ "CurrentURL": setting.AppSubURL + req.URL.RequestURI(),
+ "i18n": lc,
+ "SignedUserID": int64(0),
+ "SignedUserName": "",
+ },
+ }
+
+ w.Header().Set(`X-Frame-Options`, `SAMEORIGIN`)
+
+ if !setting.IsProd() {
+ store.Data["ErrorMsg"] = combinedErr
+ }
+ err = rnd.HTML(w, 500, "status/500", templates.BaseVars().Merge(store.Data))
+ if err != nil {
+ log.Error("%v", err)
+ }
+ }
+ }()
+
+ next.ServeHTTP(w, req)
+ })
+ }
+}
+
+// InstallRoutes registers the install routes
+func InstallRoutes() *web.Route {
+ r := web.NewRoute()
+ for _, middle := range commonMiddlewares() {
+ r.Use(middle)
+ }
+
+ r.Use(session.Sessioner(session.Options{
+ Provider: setting.SessionConfig.Provider,
+ ProviderConfig: setting.SessionConfig.ProviderConfig,
+ CookieName: setting.SessionConfig.CookieName,
+ CookiePath: setting.SessionConfig.CookiePath,
+ Gclifetime: setting.SessionConfig.Gclifetime,
+ Maxlifetime: setting.SessionConfig.Maxlifetime,
+ Secure: setting.SessionConfig.Secure,
+ Domain: setting.SessionConfig.Domain,
+ }))
+
+ r.Use(installRecovery())
+
+ r.Use(public.Custom(
+ &public.Options{
+ SkipLogging: setting.DisableRouterLog,
+ },
+ ))
+ r.Use(public.Static(
+ &public.Options{
+ Directory: path.Join(setting.StaticRootPath, "public"),
+ SkipLogging: setting.DisableRouterLog,
+ },
+ ))
+
+ r.Use(routers.InstallInit)
+ r.Get("/", routers.Install)
+ r.Post("/", web.Bind(forms.InstallForm{}), routers.InstallPost)
+ r.NotFound(func(w http.ResponseWriter, req *http.Request) {
+ http.Redirect(w, req, setting.AppURL, 302)
+ })
+ return r
+}
+++ /dev/null
-// Copyright 2017 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package routes
-
-import (
- "encoding/gob"
-
- "code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
- "code.gitea.io/gitea/modules/context"
- "code.gitea.io/gitea/modules/lfs"
- "code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/options"
- "code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/templates"
- "code.gitea.io/gitea/modules/validation"
- "code.gitea.io/gitea/routers"
- "code.gitea.io/gitea/routers/admin"
- apiv1 "code.gitea.io/gitea/routers/api/v1"
- "code.gitea.io/gitea/routers/dev"
- "code.gitea.io/gitea/routers/events"
- "code.gitea.io/gitea/routers/org"
- "code.gitea.io/gitea/routers/private"
- "code.gitea.io/gitea/routers/repo"
- "code.gitea.io/gitea/routers/user"
- userSetting "code.gitea.io/gitea/routers/user/setting"
- "code.gitea.io/gitea/services/mailer"
-
- // to registers all internal adapters
- _ "code.gitea.io/gitea/modules/session"
-
- "gitea.com/macaron/binding"
- "gitea.com/macaron/cache"
- "gitea.com/macaron/captcha"
- "gitea.com/macaron/cors"
- "gitea.com/macaron/csrf"
- "gitea.com/macaron/gzip"
- "gitea.com/macaron/i18n"
- "gitea.com/macaron/macaron"
- "gitea.com/macaron/session"
- "gitea.com/macaron/toolbox"
- "github.com/tstranex/u2f"
-)
-
-// NewMacaron initializes Macaron instance.
-func NewMacaron() *macaron.Macaron {
- gob.Register(&u2f.Challenge{})
- var m *macaron.Macaron
- if setting.RedirectMacaronLog {
- loggerAsWriter := log.NewLoggerAsWriter("INFO", log.GetLogger("macaron"))
- m = macaron.NewWithLogger(loggerAsWriter)
- } else {
- m = macaron.New()
- }
-
- if setting.EnableGzip {
- m.Use(gzip.Middleware())
- }
- if setting.Protocol == setting.FCGI || setting.Protocol == setting.FCGIUnix {
- m.SetURLPrefix(setting.AppSubURL)
- }
-
- m.Use(templates.HTMLRenderer())
-
- mailer.InitMailRender(templates.Mailer())
-
- localeNames, err := options.Dir("locale")
-
- if err != nil {
- log.Fatal("Failed to list locale files: %v", err)
- }
-
- localFiles := make(map[string][]byte)
-
- for _, name := range localeNames {
- localFiles[name], err = options.Locale(name)
-
- if err != nil {
- log.Fatal("Failed to load %s locale file. %v", name, err)
- }
- }
-
- m.Use(i18n.I18n(i18n.Options{
- SubURL: setting.AppSubURL,
- Files: localFiles,
- Langs: setting.Langs,
- Names: setting.Names,
- DefaultLang: "en-US",
- Redirect: false,
- CookieHttpOnly: true,
- Secure: setting.SessionConfig.Secure,
- CookieDomain: setting.SessionConfig.Domain,
- }))
- m.Use(cache.Cacher(cache.Options{
- Adapter: setting.CacheService.Adapter,
- AdapterConfig: setting.CacheService.Conn,
- Interval: setting.CacheService.Interval,
- }))
- m.Use(captcha.Captchaer(captcha.Options{
- SubURL: setting.AppSubURL,
- }))
- m.Use(session.Sessioner(session.Options{
- Provider: setting.SessionConfig.Provider,
- ProviderConfig: setting.SessionConfig.ProviderConfig,
- CookieName: setting.SessionConfig.CookieName,
- CookiePath: setting.SessionConfig.CookiePath,
- Gclifetime: setting.SessionConfig.Gclifetime,
- Maxlifetime: setting.SessionConfig.Maxlifetime,
- Secure: setting.SessionConfig.Secure,
- Domain: setting.SessionConfig.Domain,
- }))
- m.Use(csrf.Csrfer(csrf.Options{
- Secret: setting.SecretKey,
- Cookie: setting.CSRFCookieName,
- SetCookie: true,
- Secure: setting.SessionConfig.Secure,
- CookieHttpOnly: setting.CSRFCookieHTTPOnly,
- Header: "X-Csrf-Token",
- CookieDomain: setting.SessionConfig.Domain,
- CookiePath: setting.AppSubURL,
- }))
- m.Use(toolbox.Toolboxer(m, toolbox.Options{
- HealthCheckFuncs: []*toolbox.HealthCheckFuncDesc{
- {
- Desc: "Database connection",
- Func: models.Ping,
- },
- },
- DisableDebug: !setting.EnablePprof,
- }))
- m.Use(context.Contexter())
- m.SetAutoHead(true)
- return m
-}
-
-// RegisterMacaronInstallRoute registers the install routes
-func RegisterMacaronInstallRoute(m *macaron.Macaron) {
- m.Combo("/", routers.InstallInit).Get(routers.Install).
- Post(binding.BindIgnErr(auth.InstallForm{}), routers.InstallPost)
- m.NotFound(func(ctx *context.Context) {
- ctx.Redirect(setting.AppURL, 302)
- })
-}
-
-// RegisterMacaronRoutes routes routes to Macaron
-func RegisterMacaronRoutes(m *macaron.Macaron) {
- reqSignIn := context.Toggle(&context.ToggleOptions{SignInRequired: true})
- ignSignIn := context.Toggle(&context.ToggleOptions{SignInRequired: setting.Service.RequireSignInView})
- ignSignInAndCsrf := context.Toggle(&context.ToggleOptions{DisableCSRF: true})
- reqSignOut := context.Toggle(&context.ToggleOptions{SignOutRequired: true})
-
- bindIgnErr := binding.BindIgnErr
- validation.AddBindingRules()
-
- openIDSignInEnabled := func(ctx *context.Context) {
- if !setting.Service.EnableOpenIDSignIn {
- ctx.Error(403)
- return
- }
- }
-
- openIDSignUpEnabled := func(ctx *context.Context) {
- if !setting.Service.EnableOpenIDSignUp {
- ctx.Error(403)
- return
- }
- }
-
- reqMilestonesDashboardPageEnabled := func(ctx *context.Context) {
- if !setting.Service.ShowMilestonesDashboardPage {
- ctx.Error(403)
- return
- }
- }
-
- m.Use(user.GetNotificationCount)
- m.Use(repo.GetActiveStopwatch)
- m.Use(func(ctx *context.Context) {
- ctx.Data["UnitWikiGlobalDisabled"] = models.UnitTypeWiki.UnitGlobalDisabled()
- ctx.Data["UnitIssuesGlobalDisabled"] = models.UnitTypeIssues.UnitGlobalDisabled()
- ctx.Data["UnitPullsGlobalDisabled"] = models.UnitTypePullRequests.UnitGlobalDisabled()
- ctx.Data["UnitProjectsGlobalDisabled"] = models.UnitTypeProjects.UnitGlobalDisabled()
- })
-
- // FIXME: not all routes need go through same middlewares.
- // Especially some AJAX requests, we can reduce middleware number to improve performance.
- // Routers.
- // for health check
- m.Get("/", routers.Home)
- m.Group("/explore", func() {
- m.Get("", func(ctx *context.Context) {
- ctx.Redirect(setting.AppSubURL + "/explore/repos")
- })
- m.Get("/repos", routers.ExploreRepos)
- m.Get("/users", routers.ExploreUsers)
- m.Get("/organizations", routers.ExploreOrganizations)
- m.Get("/code", routers.ExploreCode)
- }, ignSignIn)
- m.Combo("/install", routers.InstallInit).Get(routers.Install).
- Post(bindIgnErr(auth.InstallForm{}), routers.InstallPost)
- m.Get("/issues", reqSignIn, user.Issues)
- m.Get("/pulls", reqSignIn, user.Pulls)
- m.Get("/milestones", reqSignIn, reqMilestonesDashboardPageEnabled, user.Milestones)
-
- // ***** START: User *****
- m.Group("/user", func() {
- m.Get("/login", user.SignIn)
- m.Post("/login", bindIgnErr(auth.SignInForm{}), user.SignInPost)
- m.Group("", func() {
- m.Combo("/login/openid").
- Get(user.SignInOpenID).
- Post(bindIgnErr(auth.SignInOpenIDForm{}), user.SignInOpenIDPost)
- }, openIDSignInEnabled)
- m.Group("/openid", func() {
- m.Combo("/connect").
- Get(user.ConnectOpenID).
- Post(bindIgnErr(auth.ConnectOpenIDForm{}), user.ConnectOpenIDPost)
- m.Group("/register", func() {
- m.Combo("").
- Get(user.RegisterOpenID, openIDSignUpEnabled).
- Post(bindIgnErr(auth.SignUpOpenIDForm{}), user.RegisterOpenIDPost)
- }, openIDSignUpEnabled)
- }, openIDSignInEnabled)
- m.Get("/sign_up", user.SignUp)
- m.Post("/sign_up", bindIgnErr(auth.RegisterForm{}), user.SignUpPost)
- m.Group("/oauth2", func() {
- m.Get("/:provider", user.SignInOAuth)
- m.Get("/:provider/callback", user.SignInOAuthCallback)
- })
- m.Get("/link_account", user.LinkAccount)
- m.Post("/link_account_signin", bindIgnErr(auth.SignInForm{}), user.LinkAccountPostSignIn)
- m.Post("/link_account_signup", bindIgnErr(auth.RegisterForm{}), user.LinkAccountPostRegister)
- m.Group("/two_factor", func() {
- m.Get("", user.TwoFactor)
- m.Post("", bindIgnErr(auth.TwoFactorAuthForm{}), user.TwoFactorPost)
- m.Get("/scratch", user.TwoFactorScratch)
- m.Post("/scratch", bindIgnErr(auth.TwoFactorScratchAuthForm{}), user.TwoFactorScratchPost)
- })
- m.Group("/u2f", func() {
- m.Get("", user.U2F)
- m.Get("/challenge", user.U2FChallenge)
- m.Post("/sign", bindIgnErr(u2f.SignResponse{}), user.U2FSign)
-
- })
- }, reqSignOut)
-
- m.Any("/user/events", reqSignIn, events.Events)
-
- m.Group("/login/oauth", func() {
- m.Get("/authorize", bindIgnErr(auth.AuthorizationForm{}), user.AuthorizeOAuth)
- m.Post("/grant", bindIgnErr(auth.GrantApplicationForm{}), user.GrantApplicationOAuth)
- // TODO manage redirection
- m.Post("/authorize", bindIgnErr(auth.AuthorizationForm{}), user.AuthorizeOAuth)
- }, ignSignInAndCsrf, reqSignIn)
- m.Post("/login/oauth/access_token", bindIgnErr(auth.AccessTokenForm{}), ignSignInAndCsrf, user.AccessTokenOAuth)
-
- m.Group("/user/settings", func() {
- m.Get("", userSetting.Profile)
- m.Post("", bindIgnErr(auth.UpdateProfileForm{}), userSetting.ProfilePost)
- m.Get("/change_password", user.MustChangePassword)
- m.Post("/change_password", bindIgnErr(auth.MustChangePasswordForm{}), user.MustChangePasswordPost)
- m.Post("/avatar", binding.MultipartForm(auth.AvatarForm{}), userSetting.AvatarPost)
- m.Post("/avatar/delete", userSetting.DeleteAvatar)
- m.Group("/account", func() {
- m.Combo("").Get(userSetting.Account).Post(bindIgnErr(auth.ChangePasswordForm{}), userSetting.AccountPost)
- m.Post("/email", bindIgnErr(auth.AddEmailForm{}), userSetting.EmailPost)
- m.Post("/email/delete", userSetting.DeleteEmail)
- m.Post("/delete", userSetting.DeleteAccount)
- m.Post("/theme", bindIgnErr(auth.UpdateThemeForm{}), userSetting.UpdateUIThemePost)
- })
- m.Group("/security", func() {
- m.Get("", userSetting.Security)
- m.Group("/two_factor", func() {
- m.Post("/regenerate_scratch", userSetting.RegenerateScratchTwoFactor)
- m.Post("/disable", userSetting.DisableTwoFactor)
- m.Get("/enroll", userSetting.EnrollTwoFactor)
- m.Post("/enroll", bindIgnErr(auth.TwoFactorAuthForm{}), userSetting.EnrollTwoFactorPost)
- })
- m.Group("/u2f", func() {
- m.Post("/request_register", bindIgnErr(auth.U2FRegistrationForm{}), userSetting.U2FRegister)
- m.Post("/register", bindIgnErr(u2f.RegisterResponse{}), userSetting.U2FRegisterPost)
- m.Post("/delete", bindIgnErr(auth.U2FDeleteForm{}), userSetting.U2FDelete)
- })
- m.Group("/openid", func() {
- m.Post("", bindIgnErr(auth.AddOpenIDForm{}), userSetting.OpenIDPost)
- m.Post("/delete", userSetting.DeleteOpenID)
- m.Post("/toggle_visibility", userSetting.ToggleOpenIDVisibility)
- }, openIDSignInEnabled)
- m.Post("/account_link", userSetting.DeleteAccountLink)
- })
- m.Group("/applications/oauth2", func() {
- m.Get("/:id", userSetting.OAuth2ApplicationShow)
- m.Post("/:id", bindIgnErr(auth.EditOAuth2ApplicationForm{}), userSetting.OAuthApplicationsEdit)
- m.Post("/:id/regenerate_secret", userSetting.OAuthApplicationsRegenerateSecret)
- m.Post("", bindIgnErr(auth.EditOAuth2ApplicationForm{}), userSetting.OAuthApplicationsPost)
- m.Post("/delete", userSetting.DeleteOAuth2Application)
- m.Post("/revoke", userSetting.RevokeOAuth2Grant)
- })
- m.Combo("/applications").Get(userSetting.Applications).
- Post(bindIgnErr(auth.NewAccessTokenForm{}), userSetting.ApplicationsPost)
- m.Post("/applications/delete", userSetting.DeleteApplication)
- m.Combo("/keys").Get(userSetting.Keys).
- Post(bindIgnErr(auth.AddKeyForm{}), userSetting.KeysPost)
- m.Post("/keys/delete", userSetting.DeleteKey)
- m.Get("/organization", userSetting.Organization)
- m.Get("/repos", userSetting.Repos)
- m.Post("/repos/unadopted", userSetting.AdoptOrDeleteRepository)
- }, reqSignIn, func(ctx *context.Context) {
- ctx.Data["PageIsUserSettings"] = true
- ctx.Data["AllThemes"] = setting.UI.Themes
- })
-
- m.Group("/user", func() {
- // r.Get("/feeds", binding.Bind(auth.FeedsForm{}), user.Feeds)
- m.Any("/activate", user.Activate, reqSignIn)
- m.Any("/activate_email", user.ActivateEmail)
- m.Get("/avatar/:username/:size", user.Avatar)
- m.Get("/email2user", user.Email2User)
- m.Get("/recover_account", user.ResetPasswd)
- m.Post("/recover_account", user.ResetPasswdPost)
- m.Get("/forgot_password", user.ForgotPasswd)
- m.Post("/forgot_password", user.ForgotPasswdPost)
- m.Post("/logout", user.SignOut)
- m.Get("/task/:task", user.TaskStatus)
- })
- // ***** END: User *****
-
- m.Get("/avatar/:hash", user.AvatarByEmailHash)
-
- adminReq := context.Toggle(&context.ToggleOptions{SignInRequired: true, AdminRequired: true})
-
- // ***** START: Admin *****
- m.Group("/admin", func() {
- m.Get("", adminReq, admin.Dashboard)
- m.Post("", adminReq, bindIgnErr(auth.AdminDashboardForm{}), admin.DashboardPost)
- m.Get("/config", admin.Config)
- m.Post("/config/test_mail", admin.SendTestMail)
- m.Group("/monitor", func() {
- m.Get("", admin.Monitor)
- m.Post("/cancel/:pid", admin.MonitorCancel)
- m.Group("/queue/:qid", func() {
- m.Get("", admin.Queue)
- m.Post("/set", admin.SetQueueSettings)
- m.Post("/add", admin.AddWorkers)
- m.Post("/cancel/:pid", admin.WorkerCancel)
- m.Post("/flush", admin.Flush)
- })
- })
-
- m.Group("/users", func() {
- m.Get("", admin.Users)
- m.Combo("/new").Get(admin.NewUser).Post(bindIgnErr(auth.AdminCreateUserForm{}), admin.NewUserPost)
- m.Combo("/:userid").Get(admin.EditUser).Post(bindIgnErr(auth.AdminEditUserForm{}), admin.EditUserPost)
- m.Post("/:userid/delete", admin.DeleteUser)
- })
-
- m.Group("/emails", func() {
- m.Get("", admin.Emails)
- m.Post("/activate", admin.ActivateEmail)
- })
-
- m.Group("/orgs", func() {
- m.Get("", admin.Organizations)
- })
-
- m.Group("/repos", func() {
- m.Get("", admin.Repos)
- m.Combo("/unadopted").Get(admin.UnadoptedRepos).Post(admin.AdoptOrDeleteRepository)
- m.Post("/delete", admin.DeleteRepo)
- })
-
- m.Group("/hooks", func() {
- m.Get("", admin.DefaultOrSystemWebhooks)
- m.Post("/delete", admin.DeleteDefaultOrSystemWebhook)
- m.Get("/:id", repo.WebHooksEdit)
- m.Post("/gitea/:id", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost)
- m.Post("/gogs/:id", bindIgnErr(auth.NewGogshookForm{}), repo.GogsHooksEditPost)
- m.Post("/slack/:id", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksEditPost)
- m.Post("/discord/:id", bindIgnErr(auth.NewDiscordHookForm{}), repo.DiscordHooksEditPost)
- m.Post("/dingtalk/:id", bindIgnErr(auth.NewDingtalkHookForm{}), repo.DingtalkHooksEditPost)
- m.Post("/telegram/:id", bindIgnErr(auth.NewTelegramHookForm{}), repo.TelegramHooksEditPost)
- m.Post("/matrix/:id", bindIgnErr(auth.NewMatrixHookForm{}), repo.MatrixHooksEditPost)
- m.Post("/msteams/:id", bindIgnErr(auth.NewMSTeamsHookForm{}), repo.MSTeamsHooksEditPost)
- m.Post("/feishu/:id", bindIgnErr(auth.NewFeishuHookForm{}), repo.FeishuHooksEditPost)
- })
-
- m.Group("/^:configType(default-hooks|system-hooks)$", func() {
- m.Get("/:type/new", repo.WebhooksNew)
- m.Post("/gitea/new", bindIgnErr(auth.NewWebhookForm{}), repo.GiteaHooksNewPost)
- m.Post("/gogs/new", bindIgnErr(auth.NewGogshookForm{}), repo.GogsHooksNewPost)
- m.Post("/slack/new", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksNewPost)
- m.Post("/discord/new", bindIgnErr(auth.NewDiscordHookForm{}), repo.DiscordHooksNewPost)
- m.Post("/dingtalk/new", bindIgnErr(auth.NewDingtalkHookForm{}), repo.DingtalkHooksNewPost)
- m.Post("/telegram/new", bindIgnErr(auth.NewTelegramHookForm{}), repo.TelegramHooksNewPost)
- m.Post("/matrix/new", bindIgnErr(auth.NewMatrixHookForm{}), repo.MatrixHooksNewPost)
- m.Post("/msteams/new", bindIgnErr(auth.NewMSTeamsHookForm{}), repo.MSTeamsHooksNewPost)
- m.Post("/feishu/new", bindIgnErr(auth.NewFeishuHookForm{}), repo.FeishuHooksNewPost)
- })
-
- m.Group("/auths", func() {
- m.Get("", admin.Authentications)
- m.Combo("/new").Get(admin.NewAuthSource).Post(bindIgnErr(auth.AuthenticationForm{}), admin.NewAuthSourcePost)
- m.Combo("/:authid").Get(admin.EditAuthSource).
- Post(bindIgnErr(auth.AuthenticationForm{}), admin.EditAuthSourcePost)
- m.Post("/:authid/delete", admin.DeleteAuthSource)
- })
-
- m.Group("/notices", func() {
- m.Get("", admin.Notices)
- m.Post("/delete", admin.DeleteNotices)
- m.Post("/empty", admin.EmptyNotices)
- })
- }, adminReq)
- // ***** END: Admin *****
-
- m.Group("", func() {
- m.Get("/:username", user.Profile)
- m.Get("/attachments/:uuid", repo.GetAttachment)
- }, ignSignIn)
-
- m.Group("/:username", func() {
- m.Post("/action/:action", user.Action)
- }, reqSignIn)
-
- if macaron.Env == macaron.DEV {
- m.Get("/template/*", dev.TemplatePreview)
- }
-
- reqRepoAdmin := context.RequireRepoAdmin()
- reqRepoCodeWriter := context.RequireRepoWriter(models.UnitTypeCode)
- reqRepoCodeReader := context.RequireRepoReader(models.UnitTypeCode)
- reqRepoReleaseWriter := context.RequireRepoWriter(models.UnitTypeReleases)
- reqRepoReleaseReader := context.RequireRepoReader(models.UnitTypeReleases)
- reqRepoWikiWriter := context.RequireRepoWriter(models.UnitTypeWiki)
- reqRepoIssueWriter := context.RequireRepoWriter(models.UnitTypeIssues)
- reqRepoIssueReader := context.RequireRepoReader(models.UnitTypeIssues)
- reqRepoPullsReader := context.RequireRepoReader(models.UnitTypePullRequests)
- reqRepoIssuesOrPullsWriter := context.RequireRepoWriterOr(models.UnitTypeIssues, models.UnitTypePullRequests)
- reqRepoIssuesOrPullsReader := context.RequireRepoReaderOr(models.UnitTypeIssues, models.UnitTypePullRequests)
- reqRepoProjectsReader := context.RequireRepoReader(models.UnitTypeProjects)
- reqRepoProjectsWriter := context.RequireRepoWriter(models.UnitTypeProjects)
-
- // ***** START: Organization *****
- m.Group("/org", func() {
- m.Group("", func() {
- m.Get("/create", org.Create)
- m.Post("/create", bindIgnErr(auth.CreateOrgForm{}), org.CreatePost)
- })
-
- m.Group("/:org", func() {
- m.Get("/dashboard", user.Dashboard)
- m.Get("/dashboard/:team", user.Dashboard)
- m.Get("/issues", user.Issues)
- m.Get("/issues/:team", user.Issues)
- m.Get("/pulls", user.Pulls)
- m.Get("/pulls/:team", user.Pulls)
- m.Get("/milestones", reqMilestonesDashboardPageEnabled, user.Milestones)
- m.Get("/milestones/:team", reqMilestonesDashboardPageEnabled, user.Milestones)
- m.Get("/members", org.Members)
- m.Post("/members/action/:action", org.MembersAction)
- m.Get("/teams", org.Teams)
- }, context.OrgAssignment(true, false, true))
-
- m.Group("/:org", func() {
- m.Get("/teams/:team", org.TeamMembers)
- m.Get("/teams/:team/repositories", org.TeamRepositories)
- m.Post("/teams/:team/action/:action", org.TeamsAction)
- m.Post("/teams/:team/action/repo/:action", org.TeamsRepoAction)
- }, context.OrgAssignment(true, false, true))
-
- m.Group("/:org", func() {
- m.Get("/teams/new", org.NewTeam)
- m.Post("/teams/new", bindIgnErr(auth.CreateTeamForm{}), org.NewTeamPost)
- m.Get("/teams/:team/edit", org.EditTeam)
- m.Post("/teams/:team/edit", bindIgnErr(auth.CreateTeamForm{}), org.EditTeamPost)
- m.Post("/teams/:team/delete", org.DeleteTeam)
-
- m.Group("/settings", func() {
- m.Combo("").Get(org.Settings).
- Post(bindIgnErr(auth.UpdateOrgSettingForm{}), org.SettingsPost)
- m.Post("/avatar", binding.MultipartForm(auth.AvatarForm{}), org.SettingsAvatar)
- m.Post("/avatar/delete", org.SettingsDeleteAvatar)
-
- m.Group("/hooks", func() {
- m.Get("", org.Webhooks)
- m.Post("/delete", org.DeleteWebhook)
- m.Get("/:type/new", repo.WebhooksNew)
- m.Post("/gitea/new", bindIgnErr(auth.NewWebhookForm{}), repo.GiteaHooksNewPost)
- m.Post("/gogs/new", bindIgnErr(auth.NewGogshookForm{}), repo.GogsHooksNewPost)
- m.Post("/slack/new", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksNewPost)
- m.Post("/discord/new", bindIgnErr(auth.NewDiscordHookForm{}), repo.DiscordHooksNewPost)
- m.Post("/dingtalk/new", bindIgnErr(auth.NewDingtalkHookForm{}), repo.DingtalkHooksNewPost)
- m.Post("/telegram/new", bindIgnErr(auth.NewTelegramHookForm{}), repo.TelegramHooksNewPost)
- m.Post("/matrix/new", bindIgnErr(auth.NewMatrixHookForm{}), repo.MatrixHooksNewPost)
- m.Post("/msteams/new", bindIgnErr(auth.NewMSTeamsHookForm{}), repo.MSTeamsHooksNewPost)
- m.Post("/feishu/new", bindIgnErr(auth.NewFeishuHookForm{}), repo.FeishuHooksNewPost)
- m.Get("/:id", repo.WebHooksEdit)
- m.Post("/gitea/:id", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost)
- m.Post("/gogs/:id", bindIgnErr(auth.NewGogshookForm{}), repo.GogsHooksEditPost)
- m.Post("/slack/:id", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksEditPost)
- m.Post("/discord/:id", bindIgnErr(auth.NewDiscordHookForm{}), repo.DiscordHooksEditPost)
- m.Post("/dingtalk/:id", bindIgnErr(auth.NewDingtalkHookForm{}), repo.DingtalkHooksEditPost)
- m.Post("/telegram/:id", bindIgnErr(auth.NewTelegramHookForm{}), repo.TelegramHooksEditPost)
- m.Post("/matrix/:id", bindIgnErr(auth.NewMatrixHookForm{}), repo.MatrixHooksEditPost)
- m.Post("/msteams/:id", bindIgnErr(auth.NewMSTeamsHookForm{}), repo.MSTeamsHooksEditPost)
- m.Post("/feishu/:id", bindIgnErr(auth.NewFeishuHookForm{}), repo.FeishuHooksEditPost)
- })
-
- m.Group("/labels", func() {
- m.Get("", org.RetrieveLabels, org.Labels)
- m.Post("/new", bindIgnErr(auth.CreateLabelForm{}), org.NewLabel)
- m.Post("/edit", bindIgnErr(auth.CreateLabelForm{}), org.UpdateLabel)
- m.Post("/delete", org.DeleteLabel)
- m.Post("/initialize", bindIgnErr(auth.InitializeLabelsForm{}), org.InitializeLabels)
- })
-
- m.Route("/delete", "GET,POST", org.SettingsDelete)
- })
- }, context.OrgAssignment(true, true))
- }, reqSignIn)
- // ***** END: Organization *****
-
- // ***** START: Repository *****
- m.Group("/repo", func() {
- m.Get("/create", repo.Create)
- m.Post("/create", bindIgnErr(auth.CreateRepoForm{}), repo.CreatePost)
- m.Get("/migrate", repo.Migrate)
- m.Post("/migrate", bindIgnErr(auth.MigrateRepoForm{}), repo.MigratePost)
- m.Group("/fork", func() {
- m.Combo("/:repoid").Get(repo.Fork).
- Post(bindIgnErr(auth.CreateRepoForm{}), repo.ForkPost)
- }, context.RepoIDAssignment(), context.UnitTypes(), reqRepoCodeReader)
- }, reqSignIn)
-
- // ***** Release Attachment Download without Signin
- m.Get("/:username/:reponame/releases/download/:vTag/:fileName", ignSignIn, context.RepoAssignment(), repo.MustBeNotEmpty, repo.RedirectDownload)
-
- m.Group("/:username/:reponame", func() {
- m.Group("/settings", func() {
- m.Combo("").Get(repo.Settings).
- Post(bindIgnErr(auth.RepoSettingForm{}), repo.SettingsPost)
- m.Post("/avatar", binding.MultipartForm(auth.AvatarForm{}), repo.SettingsAvatar)
- m.Post("/avatar/delete", repo.SettingsDeleteAvatar)
-
- m.Group("/collaboration", func() {
- m.Combo("").Get(repo.Collaboration).Post(repo.CollaborationPost)
- m.Post("/access_mode", repo.ChangeCollaborationAccessMode)
- m.Post("/delete", repo.DeleteCollaboration)
- m.Group("/team", func() {
- m.Post("", repo.AddTeamPost)
- m.Post("/delete", repo.DeleteTeam)
- })
- })
- m.Group("/branches", func() {
- m.Combo("").Get(repo.ProtectedBranch).Post(repo.ProtectedBranchPost)
- m.Combo("/*").Get(repo.SettingsProtectedBranch).
- Post(bindIgnErr(auth.ProtectBranchForm{}), context.RepoMustNotBeArchived(), repo.SettingsProtectedBranchPost)
- }, repo.MustBeNotEmpty)
-
- m.Group("/hooks", func() {
- m.Get("", repo.Webhooks)
- m.Post("/delete", repo.DeleteWebhook)
- m.Get("/:type/new", repo.WebhooksNew)
- m.Post("/gitea/new", bindIgnErr(auth.NewWebhookForm{}), repo.GiteaHooksNewPost)
- m.Post("/gogs/new", bindIgnErr(auth.NewGogshookForm{}), repo.GogsHooksNewPost)
- m.Post("/slack/new", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksNewPost)
- m.Post("/discord/new", bindIgnErr(auth.NewDiscordHookForm{}), repo.DiscordHooksNewPost)
- m.Post("/dingtalk/new", bindIgnErr(auth.NewDingtalkHookForm{}), repo.DingtalkHooksNewPost)
- m.Post("/telegram/new", bindIgnErr(auth.NewTelegramHookForm{}), repo.TelegramHooksNewPost)
- m.Post("/matrix/new", bindIgnErr(auth.NewMatrixHookForm{}), repo.MatrixHooksNewPost)
- m.Post("/msteams/new", bindIgnErr(auth.NewMSTeamsHookForm{}), repo.MSTeamsHooksNewPost)
- m.Post("/feishu/new", bindIgnErr(auth.NewFeishuHookForm{}), repo.FeishuHooksNewPost)
- m.Get("/:id", repo.WebHooksEdit)
- m.Post("/:id/test", repo.TestWebhook)
- m.Post("/gitea/:id", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost)
- m.Post("/gogs/:id", bindIgnErr(auth.NewGogshookForm{}), repo.GogsHooksEditPost)
- m.Post("/slack/:id", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksEditPost)
- m.Post("/discord/:id", bindIgnErr(auth.NewDiscordHookForm{}), repo.DiscordHooksEditPost)
- m.Post("/dingtalk/:id", bindIgnErr(auth.NewDingtalkHookForm{}), repo.DingtalkHooksEditPost)
- m.Post("/telegram/:id", bindIgnErr(auth.NewTelegramHookForm{}), repo.TelegramHooksEditPost)
- m.Post("/matrix/:id", bindIgnErr(auth.NewMatrixHookForm{}), repo.MatrixHooksEditPost)
- m.Post("/msteams/:id", bindIgnErr(auth.NewMSTeamsHookForm{}), repo.MSTeamsHooksEditPost)
- m.Post("/feishu/:id", bindIgnErr(auth.NewFeishuHookForm{}), repo.FeishuHooksEditPost)
-
- m.Group("/git", func() {
- m.Get("", repo.GitHooks)
- m.Combo("/:name").Get(repo.GitHooksEdit).
- Post(repo.GitHooksEditPost)
- }, context.GitHookService())
- })
-
- m.Group("/keys", func() {
- m.Combo("").Get(repo.DeployKeys).
- Post(bindIgnErr(auth.AddKeyForm{}), repo.DeployKeysPost)
- m.Post("/delete", repo.DeleteDeployKey)
- })
-
- m.Group("/lfs", func() {
- m.Get("", repo.LFSFiles)
- m.Get("/show/:oid", repo.LFSFileGet)
- m.Post("/delete/:oid", repo.LFSDelete)
- m.Get("/pointers", repo.LFSPointerFiles)
- m.Post("/pointers/associate", repo.LFSAutoAssociate)
- m.Get("/find", repo.LFSFileFind)
- m.Group("/locks", func() {
- m.Get("/", repo.LFSLocks)
- m.Post("/", repo.LFSLockFile)
- m.Post("/:lid/unlock", repo.LFSUnlock)
- })
- })
-
- }, func(ctx *context.Context) {
- ctx.Data["PageIsSettings"] = true
- ctx.Data["LFSStartServer"] = setting.LFS.StartServer
- })
- }, reqSignIn, context.RepoAssignment(), context.UnitTypes(), reqRepoAdmin, context.RepoRef())
-
- m.Post("/:username/:reponame/action/:action", reqSignIn, context.RepoAssignment(), context.UnitTypes(), repo.Action)
-
- // Grouping for those endpoints not requiring authentication
- m.Group("/:username/:reponame", func() {
- m.Group("/milestone", func() {
- m.Get("/:id", repo.MilestoneIssuesAndPulls)
- }, reqRepoIssuesOrPullsReader, context.RepoRef())
- m.Combo("/compare/*", repo.MustBeNotEmpty, reqRepoCodeReader, repo.SetEditorconfigIfExists).
- Get(ignSignIn, repo.SetDiffViewStyle, repo.CompareDiff).
- Post(reqSignIn, context.RepoMustNotBeArchived(), reqRepoPullsReader, repo.MustAllowPulls, bindIgnErr(auth.CreateIssueForm{}), repo.CompareAndPullRequestPost)
- }, context.RepoAssignment(), context.UnitTypes())
-
- // Grouping for those endpoints that do require authentication
- m.Group("/:username/:reponame", func() {
- m.Group("/issues", func() {
- m.Group("/new", func() {
- m.Combo("").Get(context.RepoRef(), repo.NewIssue).
- Post(bindIgnErr(auth.CreateIssueForm{}), repo.NewIssuePost)
- m.Get("/choose", context.RepoRef(), repo.NewIssueChooseTemplate)
- })
- }, context.RepoMustNotBeArchived(), reqRepoIssueReader)
- // FIXME: should use different URLs but mostly same logic for comments of issue and pull reuqest.
- // So they can apply their own enable/disable logic on routers.
- m.Group("/issues", func() {
- m.Group("/:index", func() {
- m.Post("/title", repo.UpdateIssueTitle)
- m.Post("/content", repo.UpdateIssueContent)
- m.Post("/watch", repo.IssueWatch)
- m.Post("/ref", repo.UpdateIssueRef)
- m.Group("/dependency", func() {
- m.Post("/add", repo.AddDependency)
- m.Post("/delete", repo.RemoveDependency)
- })
- m.Combo("/comments").Post(repo.MustAllowUserComment, bindIgnErr(auth.CreateCommentForm{}), repo.NewComment)
- m.Group("/times", func() {
- m.Post("/add", bindIgnErr(auth.AddTimeManuallyForm{}), repo.AddTimeManually)
- m.Group("/stopwatch", func() {
- m.Post("/toggle", repo.IssueStopwatch)
- m.Post("/cancel", repo.CancelStopwatch)
- })
- })
- m.Post("/reactions/:action", bindIgnErr(auth.ReactionForm{}), repo.ChangeIssueReaction)
- m.Post("/lock", reqRepoIssueWriter, bindIgnErr(auth.IssueLockForm{}), repo.LockIssue)
- m.Post("/unlock", reqRepoIssueWriter, repo.UnlockIssue)
- }, context.RepoMustNotBeArchived())
- m.Group("/:index", func() {
- m.Get("/attachments", repo.GetIssueAttachments)
- m.Get("/attachments/:uuid", repo.GetAttachment)
- })
-
- m.Post("/labels", reqRepoIssuesOrPullsWriter, repo.UpdateIssueLabel)
- m.Post("/milestone", reqRepoIssuesOrPullsWriter, repo.UpdateIssueMilestone)
- m.Post("/projects", reqRepoIssuesOrPullsWriter, repo.UpdateIssueProject)
- m.Post("/assignee", reqRepoIssuesOrPullsWriter, repo.UpdateIssueAssignee)
- m.Post("/request_review", reqRepoIssuesOrPullsReader, repo.UpdatePullReviewRequest)
- m.Post("/status", reqRepoIssuesOrPullsWriter, repo.UpdateIssueStatus)
- m.Post("/resolve_conversation", reqRepoIssuesOrPullsReader, repo.UpdateResolveConversation)
- m.Post("/attachments", repo.UploadIssueAttachment)
- m.Post("/attachments/remove", repo.DeleteAttachment)
- }, context.RepoMustNotBeArchived())
- m.Group("/comments/:id", func() {
- m.Post("", repo.UpdateCommentContent)
- m.Post("/delete", repo.DeleteComment)
- m.Post("/reactions/:action", bindIgnErr(auth.ReactionForm{}), repo.ChangeCommentReaction)
- }, context.RepoMustNotBeArchived())
- m.Group("/comments/:id", func() {
- m.Get("/attachments", repo.GetCommentAttachments)
- })
- m.Group("/labels", func() {
- m.Post("/new", bindIgnErr(auth.CreateLabelForm{}), repo.NewLabel)
- m.Post("/edit", bindIgnErr(auth.CreateLabelForm{}), repo.UpdateLabel)
- m.Post("/delete", repo.DeleteLabel)
- m.Post("/initialize", bindIgnErr(auth.InitializeLabelsForm{}), repo.InitializeLabels)
- }, context.RepoMustNotBeArchived(), reqRepoIssuesOrPullsWriter, context.RepoRef())
- m.Group("/milestones", func() {
- m.Combo("/new").Get(repo.NewMilestone).
- Post(bindIgnErr(auth.CreateMilestoneForm{}), repo.NewMilestonePost)
- m.Get("/:id/edit", repo.EditMilestone)
- m.Post("/:id/edit", bindIgnErr(auth.CreateMilestoneForm{}), repo.EditMilestonePost)
- m.Post("/:id/:action", repo.ChangeMilestoneStatus)
- m.Post("/delete", repo.DeleteMilestone)
- }, context.RepoMustNotBeArchived(), reqRepoIssuesOrPullsWriter, context.RepoRef())
- m.Group("/pull", func() {
- m.Post("/:index/target_branch", repo.UpdatePullRequestTarget)
- }, context.RepoMustNotBeArchived())
-
- m.Group("", func() {
- m.Group("", func() {
- m.Combo("/_edit/*").Get(repo.EditFile).
- Post(bindIgnErr(auth.EditRepoFileForm{}), repo.EditFilePost)
- m.Combo("/_new/*").Get(repo.NewFile).
- Post(bindIgnErr(auth.EditRepoFileForm{}), repo.NewFilePost)
- m.Post("/_preview/*", bindIgnErr(auth.EditPreviewDiffForm{}), repo.DiffPreviewPost)
- m.Combo("/_delete/*").Get(repo.DeleteFile).
- Post(bindIgnErr(auth.DeleteRepoFileForm{}), repo.DeleteFilePost)
- m.Combo("/_upload/*", repo.MustBeAbleToUpload).
- Get(repo.UploadFile).
- Post(bindIgnErr(auth.UploadRepoFileForm{}), repo.UploadFilePost)
- }, context.RepoRefByType(context.RepoRefBranch), repo.MustBeEditable)
- m.Group("", func() {
- m.Post("/upload-file", repo.UploadFileToServer)
- m.Post("/upload-remove", bindIgnErr(auth.RemoveUploadFileForm{}), repo.RemoveUploadFileFromServer)
- }, context.RepoRef(), repo.MustBeEditable, repo.MustBeAbleToUpload)
- }, context.RepoMustNotBeArchived(), reqRepoCodeWriter, repo.MustBeNotEmpty)
-
- m.Group("/branches", func() {
- m.Group("/_new/", func() {
- m.Post("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.CreateBranch)
- m.Post("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.CreateBranch)
- m.Post("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.CreateBranch)
- }, bindIgnErr(auth.NewBranchForm{}))
- m.Post("/delete", repo.DeleteBranchPost)
- m.Post("/restore", repo.RestoreBranchPost)
- }, context.RepoMustNotBeArchived(), reqRepoCodeWriter, repo.MustBeNotEmpty)
-
- }, reqSignIn, context.RepoAssignment(), context.UnitTypes())
-
- // Releases
- m.Group("/:username/:reponame", func() {
- m.Get("/tags", repo.TagsList, repo.MustBeNotEmpty,
- reqRepoCodeReader, context.RepoRefByType(context.RepoRefTag))
- m.Group("/releases", func() {
- m.Get("/", repo.Releases)
- m.Get("/tag/*", repo.SingleRelease)
- m.Get("/latest", repo.LatestRelease)
- m.Get("/attachments/:uuid", repo.GetAttachment)
- }, repo.MustBeNotEmpty, reqRepoReleaseReader, context.RepoRefByType(context.RepoRefTag))
- m.Group("/releases", func() {
- m.Get("/new", repo.NewRelease)
- m.Post("/new", bindIgnErr(auth.NewReleaseForm{}), repo.NewReleasePost)
- m.Post("/delete", repo.DeleteRelease)
- m.Post("/attachments", repo.UploadReleaseAttachment)
- m.Post("/attachments/remove", repo.DeleteAttachment)
- }, reqSignIn, repo.MustBeNotEmpty, context.RepoMustNotBeArchived(), reqRepoReleaseWriter, context.RepoRef())
- m.Post("/tags/delete", repo.DeleteTag, reqSignIn,
- repo.MustBeNotEmpty, context.RepoMustNotBeArchived(), reqRepoCodeWriter, context.RepoRef())
- m.Group("/releases", func() {
- m.Get("/edit/*", repo.EditRelease)
- m.Post("/edit/*", bindIgnErr(auth.EditReleaseForm{}), repo.EditReleasePost)
- }, reqSignIn, repo.MustBeNotEmpty, context.RepoMustNotBeArchived(), reqRepoReleaseWriter, func(ctx *context.Context) {
- var err error
- ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
- if err != nil {
- ctx.ServerError("GetBranchCommit", err)
- return
- }
- ctx.Repo.CommitsCount, err = ctx.Repo.GetCommitsCount()
- if err != nil {
- ctx.ServerError("GetCommitsCount", err)
- return
- }
- ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
- })
- }, ignSignIn, context.RepoAssignment(), context.UnitTypes(), reqRepoReleaseReader)
-
- m.Group("/:username/:reponame", func() {
- m.Post("/topics", repo.TopicsPost)
- }, context.RepoAssignment(), context.RepoMustNotBeArchived(), reqRepoAdmin)
-
- m.Group("/:username/:reponame", func() {
- m.Group("", func() {
- m.Get("/^:type(issues|pulls)$", repo.Issues)
- m.Get("/^:type(issues|pulls)$/:index", repo.ViewIssue)
- m.Get("/labels/", reqRepoIssuesOrPullsReader, repo.RetrieveLabels, repo.Labels)
- m.Get("/milestones", reqRepoIssuesOrPullsReader, repo.Milestones)
- }, context.RepoRef())
-
- m.Group("/projects", func() {
- m.Get("", repo.Projects)
- m.Get("/:id", repo.ViewProject)
- m.Group("", func() {
- m.Get("/new", repo.NewProject)
- m.Post("/new", bindIgnErr(auth.CreateProjectForm{}), repo.NewProjectPost)
- m.Group("/:id", func() {
- m.Post("", bindIgnErr(auth.EditProjectBoardTitleForm{}), repo.AddBoardToProjectPost)
- m.Post("/delete", repo.DeleteProject)
-
- m.Get("/edit", repo.EditProject)
- m.Post("/edit", bindIgnErr(auth.CreateProjectForm{}), repo.EditProjectPost)
- m.Post("/^:action(open|close)$", repo.ChangeProjectStatus)
-
- m.Group("/:boardID", func() {
- m.Put("", bindIgnErr(auth.EditProjectBoardTitleForm{}), repo.EditProjectBoardTitle)
- m.Delete("", repo.DeleteProjectBoard)
- m.Post("/default", repo.SetDefaultProjectBoard)
-
- m.Post("/:index", repo.MoveIssueAcrossBoards)
- })
- })
- }, reqRepoProjectsWriter, context.RepoMustNotBeArchived())
- }, reqRepoProjectsReader, repo.MustEnableProjects)
-
- m.Group("/wiki", func() {
- m.Get("/?:page", repo.Wiki)
- m.Get("/_pages", repo.WikiPages)
- m.Get("/:page/_revision", repo.WikiRevision)
- m.Get("/commit/:sha([a-f0-9]{7,40})$", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.Diff)
- m.Get("/commit/:sha([a-f0-9]{7,40})\\.:ext(patch|diff)", repo.RawDiff)
-
- m.Group("", func() {
- m.Combo("/_new").Get(repo.NewWiki).
- Post(bindIgnErr(auth.NewWikiForm{}), repo.NewWikiPost)
- m.Combo("/:page/_edit").Get(repo.EditWiki).
- Post(bindIgnErr(auth.NewWikiForm{}), repo.EditWikiPost)
- m.Post("/:page/delete", repo.DeleteWikiPagePost)
- }, context.RepoMustNotBeArchived(), reqSignIn, reqRepoWikiWriter)
- }, repo.MustEnableWiki, context.RepoRef(), func(ctx *context.Context) {
- ctx.Data["PageIsWiki"] = true
- })
-
- m.Group("/wiki", func() {
- m.Get("/raw/*", repo.WikiRaw)
- }, repo.MustEnableWiki)
-
- m.Group("/activity", func() {
- m.Get("", repo.Activity)
- m.Get("/:period", repo.Activity)
- }, context.RepoRef(), repo.MustBeNotEmpty, context.RequireRepoReaderOr(models.UnitTypePullRequests, models.UnitTypeIssues, models.UnitTypeReleases))
-
- m.Group("/activity_author_data", func() {
- m.Get("", repo.ActivityAuthors)
- m.Get("/:period", repo.ActivityAuthors)
- }, context.RepoRef(), repo.MustBeNotEmpty, context.RequireRepoReaderOr(models.UnitTypeCode))
-
- m.Group("/archive", func() {
- m.Get("/*", repo.Download)
- m.Post("/*", repo.InitiateDownload)
- }, repo.MustBeNotEmpty, reqRepoCodeReader)
-
- m.Group("/branches", func() {
- m.Get("", repo.Branches)
- }, repo.MustBeNotEmpty, context.RepoRef(), reqRepoCodeReader)
-
- m.Group("/blob_excerpt", func() {
- m.Get("/:sha", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.ExcerptBlob)
- }, repo.MustBeNotEmpty, context.RepoRef(), reqRepoCodeReader)
-
- m.Group("/pulls/:index", func() {
- m.Get(".diff", repo.DownloadPullDiff)
- m.Get(".patch", repo.DownloadPullPatch)
- m.Get("/commits", context.RepoRef(), repo.ViewPullCommits)
- m.Post("/merge", context.RepoMustNotBeArchived(), bindIgnErr(auth.MergePullRequestForm{}), repo.MergePullRequest)
- m.Post("/update", repo.UpdatePullRequest)
- m.Post("/cleanup", context.RepoMustNotBeArchived(), context.RepoRef(), repo.CleanUpPullRequest)
- m.Group("/files", func() {
- m.Get("", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.ViewPullFiles)
- m.Group("/reviews", func() {
- m.Get("/new_comment", repo.RenderNewCodeCommentForm)
- m.Post("/comments", bindIgnErr(auth.CodeCommentForm{}), repo.CreateCodeComment)
- m.Post("/submit", bindIgnErr(auth.SubmitReviewForm{}), repo.SubmitReview)
- }, context.RepoMustNotBeArchived())
- })
- }, repo.MustAllowPulls)
-
- m.Group("/media", func() {
- m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.SingleDownloadOrLFS)
- m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.SingleDownloadOrLFS)
- m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.SingleDownloadOrLFS)
- m.Get("/blob/:sha", context.RepoRefByType(context.RepoRefBlob), repo.DownloadByIDOrLFS)
- // "/*" route is deprecated, and kept for backward compatibility
- m.Get("/*", context.RepoRefByType(context.RepoRefLegacy), repo.SingleDownloadOrLFS)
- }, repo.MustBeNotEmpty, reqRepoCodeReader)
-
- m.Group("/raw", func() {
- m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.SingleDownload)
- m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.SingleDownload)
- m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.SingleDownload)
- m.Get("/blob/:sha", context.RepoRefByType(context.RepoRefBlob), repo.DownloadByID)
- // "/*" route is deprecated, and kept for backward compatibility
- m.Get("/*", context.RepoRefByType(context.RepoRefLegacy), repo.SingleDownload)
- }, repo.MustBeNotEmpty, reqRepoCodeReader)
-
- m.Group("/commits", func() {
- m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.RefCommits)
- m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.RefCommits)
- m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.RefCommits)
- // "/*" route is deprecated, and kept for backward compatibility
- m.Get("/*", context.RepoRefByType(context.RepoRefLegacy), repo.RefCommits)
- }, repo.MustBeNotEmpty, reqRepoCodeReader)
-
- m.Group("/blame", func() {
- m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.RefBlame)
- m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.RefBlame)
- m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.RefBlame)
- }, repo.MustBeNotEmpty, reqRepoCodeReader)
-
- m.Group("", func() {
- m.Get("/graph", repo.Graph)
- m.Get("/commit/:sha([a-f0-9]{7,40})$", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.Diff)
- }, repo.MustBeNotEmpty, context.RepoRef(), reqRepoCodeReader)
-
- m.Group("/src", func() {
- m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.Home)
- m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.Home)
- m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.Home)
- // "/*" route is deprecated, and kept for backward compatibility
- m.Get("/*", context.RepoRefByType(context.RepoRefLegacy), repo.Home)
- }, repo.SetEditorconfigIfExists)
-
- m.Group("", func() {
- m.Get("/forks", repo.Forks)
- }, context.RepoRef(), reqRepoCodeReader)
- m.Get("/commit/:sha([a-f0-9]{7,40})\\.:ext(patch|diff)",
- repo.MustBeNotEmpty, reqRepoCodeReader, repo.RawDiff)
- }, ignSignIn, context.RepoAssignment(), context.UnitTypes())
- m.Group("/:username/:reponame", func() {
- m.Get("/stars", repo.Stars)
- m.Get("/watchers", repo.Watchers)
- m.Get("/search", reqRepoCodeReader, repo.Search)
- }, ignSignIn, context.RepoAssignment(), context.RepoRef(), context.UnitTypes())
-
- m.Group("/:username", func() {
- m.Group("/:reponame", func() {
- m.Get("", repo.SetEditorconfigIfExists, repo.Home)
- m.Get("\\.git$", repo.SetEditorconfigIfExists, repo.Home)
- }, ignSignIn, context.RepoAssignment(), context.RepoRef(), context.UnitTypes())
-
- m.Group("/:reponame", func() {
- m.Group("\\.git/info/lfs", func() {
- m.Post("/objects/batch", lfs.BatchHandler)
- m.Get("/objects/:oid/:filename", lfs.ObjectOidHandler)
- m.Any("/objects/:oid", lfs.ObjectOidHandler)
- m.Post("/objects", lfs.PostHandler)
- m.Post("/verify", lfs.VerifyHandler)
- m.Group("/locks", func() {
- m.Get("/", lfs.GetListLockHandler)
- m.Post("/", lfs.PostLockHandler)
- m.Post("/verify", lfs.VerifyLockHandler)
- m.Post("/:lid/unlock", lfs.UnLockHandler)
- })
- m.Any("/*", func(ctx *context.Context) {
- ctx.NotFound("", nil)
- })
- }, ignSignInAndCsrf)
- m.Any("/*", ignSignInAndCsrf, repo.HTTP)
- m.Head("/tasks/trigger", repo.TriggerTask)
- })
- })
- // ***** END: Repository *****
-
- m.Group("/notifications", func() {
- m.Get("", user.Notifications)
- m.Post("/status", user.NotificationStatusPost)
- m.Post("/purge", user.NotificationPurgePost)
- }, reqSignIn)
-
- if setting.API.EnableSwagger {
- m.Get("/swagger.v1.json", templates.JSONRenderer(), routers.SwaggerV1Json)
- }
-
- var handlers []macaron.Handler
- if setting.CORSConfig.Enabled {
- handlers = append(handlers, cors.CORS(cors.Options{
- Scheme: setting.CORSConfig.Scheme,
- AllowDomain: setting.CORSConfig.AllowDomain,
- AllowSubdomain: setting.CORSConfig.AllowSubdomain,
- Methods: setting.CORSConfig.Methods,
- MaxAgeSeconds: int(setting.CORSConfig.MaxAge.Seconds()),
- AllowCredentials: setting.CORSConfig.AllowCredentials,
- }))
- }
- handlers = append(handlers, ignSignIn)
- m.Group("/api", func() {
- apiv1.RegisterRoutes(m)
- }, handlers...)
-
- m.Group("/api/internal", func() {
- // package name internal is ideal but Golang is not allowed, so we use private as package name.
- private.RegisterRoutes(m)
- })
-
- // Not found handler.
- m.NotFound(routers.NotFound)
-}
+++ /dev/null
-// Copyright 2020 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package routes
-
-import (
- "fmt"
- "net/http"
-
- "code.gitea.io/gitea/modules/auth/sso"
- "code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/middlewares"
- "code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/templates"
-
- "github.com/unrolled/render"
-)
-
-type dataStore struct {
- Data map[string]interface{}
-}
-
-func (d *dataStore) GetData() map[string]interface{} {
- return d.Data
-}
-
-// Recovery returns a middleware that recovers from any panics and writes a 500 and a log if so.
-// Although similar to macaron.Recovery() the main difference is that this error will be created
-// with the gitea 500 page.
-func Recovery() func(next http.Handler) http.Handler {
- return func(next http.Handler) http.Handler {
- rnd := render.New(render.Options{
- Extensions: []string{".tmpl"},
- Directory: "templates",
- Funcs: templates.NewFuncMap(),
- Asset: templates.GetAsset,
- AssetNames: templates.GetAssetNames,
- IsDevelopment: !setting.IsProd(),
- })
-
- return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
- defer func() {
- // Why we need this? The first recover will try to render a beautiful
- // error page for user, but the process can still panic again, then
- // we have to just recover twice and send a simple error page that
- // should not panic any more.
- defer func() {
- if err := recover(); err != nil {
- combinedErr := fmt.Sprintf("PANIC: %v\n%s", err, string(log.Stack(2)))
- log.Error(combinedErr)
- if setting.IsProd() {
- http.Error(w, http.StatusText(500), 500)
- } else {
- http.Error(w, combinedErr, 500)
- }
- }
- }()
-
- if err := recover(); err != nil {
- combinedErr := fmt.Sprintf("PANIC: %v\n%s", err, string(log.Stack(2)))
- log.Error("%v", combinedErr)
-
- lc := middlewares.Locale(w, req)
-
- // TODO: this should be replaced by real session after macaron removed totally
- sessionStore, err := sessionManager.Start(w, req)
- if err != nil {
- // Just invoke the above recover catch
- panic("session(start): " + err.Error())
- }
-
- var store = dataStore{
- Data: templates.Vars{
- "Language": lc.Language(),
- "CurrentURL": setting.AppSubURL + req.URL.RequestURI(),
- "i18n": lc,
- },
- }
-
- // Get user from session if logged in.
- user, _ := sso.SignedInUser(req, w, &store, sessionStore)
- if user != nil {
- store.Data["IsSigned"] = true
- store.Data["SignedUser"] = user
- store.Data["SignedUserID"] = user.ID
- store.Data["SignedUserName"] = user.Name
- store.Data["IsAdmin"] = user.IsAdmin
- } else {
- store.Data["SignedUserID"] = int64(0)
- store.Data["SignedUserName"] = ""
- }
-
- w.Header().Set(`X-Frame-Options`, `SAMEORIGIN`)
-
- if !setting.IsProd() {
- store.Data["ErrorMsg"] = combinedErr
- }
- err = rnd.HTML(w, 500, "status/500", templates.BaseVars().Merge(store.Data))
- if err != nil {
- log.Error("%v", err)
- }
- }
- }()
-
- next.ServeHTTP(w, req)
- })
- }
-}
--- /dev/null
+// Copyright 2017 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package routes
+
+import (
+ "encoding/gob"
+ "fmt"
+ "net/http"
+ "os"
+ "path"
+ "strings"
+
+ "code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/modules/context"
+ auth "code.gitea.io/gitea/modules/forms"
+ "code.gitea.io/gitea/modules/httpcache"
+ "code.gitea.io/gitea/modules/lfs"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/metrics"
+ "code.gitea.io/gitea/modules/public"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/storage"
+ "code.gitea.io/gitea/modules/templates"
+ "code.gitea.io/gitea/modules/validation"
+ "code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/routers"
+ "code.gitea.io/gitea/routers/admin"
+ apiv1 "code.gitea.io/gitea/routers/api/v1"
+ "code.gitea.io/gitea/routers/api/v1/misc"
+ "code.gitea.io/gitea/routers/dev"
+ "code.gitea.io/gitea/routers/events"
+ "code.gitea.io/gitea/routers/org"
+ "code.gitea.io/gitea/routers/private"
+ "code.gitea.io/gitea/routers/repo"
+ "code.gitea.io/gitea/routers/user"
+ userSetting "code.gitea.io/gitea/routers/user/setting"
+ "code.gitea.io/gitea/services/mailer"
+
+ // to registers all internal adapters
+ _ "code.gitea.io/gitea/modules/session"
+
+ "gitea.com/go-chi/captcha"
+ "gitea.com/go-chi/session"
+ "github.com/NYTimes/gziphandler"
+ "github.com/go-chi/chi/middleware"
+ "github.com/prometheus/client_golang/prometheus"
+ "github.com/tstranex/u2f"
+)
+
+const (
+ // GzipMinSize represents min size to compress for the body size of response
+ GzipMinSize = 1400
+)
+
+func commonMiddlewares() []func(http.Handler) http.Handler {
+ var handlers = []func(http.Handler) http.Handler{
+ func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
+ next.ServeHTTP(context.NewResponse(resp), req)
+ })
+ },
+ middleware.RealIP,
+ }
+ if !setting.DisableRouterLog && setting.RouterLogLevel != log.NONE {
+ if log.GetLogger("router").GetLevel() <= setting.RouterLogLevel {
+ handlers = append(handlers, LoggerHandler(setting.RouterLogLevel))
+ }
+ }
+ handlers = append(handlers, func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
+ // Why we need this? The Recovery() will try to render a beautiful
+ // error page for user, but the process can still panic again, and other
+ // middleware like session also may panic then we have to recover twice
+ // and send a simple error page that should not panic any more.
+ defer func() {
+ if err := recover(); err != nil {
+ combinedErr := fmt.Sprintf("PANIC: %v\n%s", err, string(log.Stack(2)))
+ log.Error("%v", combinedErr)
+ if setting.IsProd() {
+ http.Error(resp, http.StatusText(500), 500)
+ } else {
+ http.Error(resp, combinedErr, 500)
+ }
+ }
+ }()
+ next.ServeHTTP(resp, req)
+ })
+ })
+
+ if setting.EnableAccessLog {
+ handlers = append(handlers, accessLogger())
+ }
+ return handlers
+}
+
+// NormalRoutes represents non install routes
+func NormalRoutes() *web.Route {
+ r := web.NewRoute()
+ for _, middle := range commonMiddlewares() {
+ r.Use(middle)
+ }
+ r.Use(Recovery())
+
+ r.Mount("/", WebRoutes())
+ r.Mount("/api/v1", apiv1.Routes())
+ r.Mount("/api/internal", private.Routes())
+ return r
+}
+
+// WebRoutes returns all web routes
+func WebRoutes() *web.Route {
+ r := web.NewRoute()
+
+ r.Use(session.Sessioner(session.Options{
+ Provider: setting.SessionConfig.Provider,
+ ProviderConfig: setting.SessionConfig.ProviderConfig,
+ CookieName: setting.SessionConfig.CookieName,
+ CookiePath: setting.SessionConfig.CookiePath,
+ Gclifetime: setting.SessionConfig.Gclifetime,
+ Maxlifetime: setting.SessionConfig.Maxlifetime,
+ Secure: setting.SessionConfig.Secure,
+ Domain: setting.SessionConfig.Domain,
+ }))
+
+ r.Use(public.Custom(
+ &public.Options{
+ SkipLogging: setting.DisableRouterLog,
+ },
+ ))
+ r.Use(public.Static(
+ &public.Options{
+ Directory: path.Join(setting.StaticRootPath, "public"),
+ SkipLogging: setting.DisableRouterLog,
+ },
+ ))
+
+ r.Use(storageHandler(setting.Avatar.Storage, "avatars", storage.Avatars))
+ r.Use(storageHandler(setting.RepoAvatar.Storage, "repo-avatars", storage.RepoAvatars))
+
+ gob.Register(&u2f.Challenge{})
+
+ if setting.EnableGzip {
+ h, err := gziphandler.GzipHandlerWithOpts(gziphandler.MinSize(GzipMinSize))
+ if err != nil {
+ log.Fatal("GzipHandlerWithOpts failed: %v", err)
+ }
+ r.Use(h)
+ }
+
+ if (setting.Protocol == setting.FCGI || setting.Protocol == setting.FCGIUnix) && setting.AppSubURL != "" {
+ r.Use(func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
+ req.URL.Path = strings.TrimPrefix(req.URL.Path, setting.AppSubURL)
+ next.ServeHTTP(resp, req)
+ })
+ })
+ }
+
+ mailer.InitMailRender(templates.Mailer())
+
+ r.Use(captcha.Captchaer(context.GetImageCaptcha()))
+ // Removed: toolbox.Toolboxer middleware will provide debug informations which seems unnecessary
+ r.Use(context.Contexter())
+ // Removed: SetAutoHead allow a get request redirect to head if get method is not exist
+
+ r.Use(user.GetNotificationCount)
+ r.Use(repo.GetActiveStopwatch)
+ r.Use(func(ctx *context.Context) {
+ ctx.Data["UnitWikiGlobalDisabled"] = models.UnitTypeWiki.UnitGlobalDisabled()
+ ctx.Data["UnitIssuesGlobalDisabled"] = models.UnitTypeIssues.UnitGlobalDisabled()
+ ctx.Data["UnitPullsGlobalDisabled"] = models.UnitTypePullRequests.UnitGlobalDisabled()
+ ctx.Data["UnitProjectsGlobalDisabled"] = models.UnitTypeProjects.UnitGlobalDisabled()
+ })
+
+ // for health check
+ r.Head("/", func(w http.ResponseWriter, req *http.Request) {
+ w.WriteHeader(http.StatusOK)
+ })
+
+ if setting.HasRobotsTxt {
+ r.Get("/robots.txt", func(w http.ResponseWriter, req *http.Request) {
+ filePath := path.Join(setting.CustomPath, "robots.txt")
+ fi, err := os.Stat(filePath)
+ if err == nil && httpcache.HandleTimeCache(req, w, fi) {
+ return
+ }
+ http.ServeFile(w, req, filePath)
+ })
+ }
+
+ r.Get("/apple-touch-icon.png", func(w http.ResponseWriter, req *http.Request) {
+ http.Redirect(w, req, path.Join(setting.StaticURLPrefix, "img/apple-touch-icon.png"), 301)
+ })
+
+ // prometheus metrics endpoint
+ if setting.Metrics.Enabled {
+ c := metrics.NewCollector()
+ prometheus.MustRegister(c)
+
+ r.Get("/metrics", routers.Metrics)
+ }
+
+ if setting.API.EnableSwagger {
+ // Note: The route moved from apiroutes because it's in fact want to render a web page
+ r.Get("/api/swagger", misc.Swagger) // Render V1 by default
+ }
+
+ RegisterRoutes(r)
+
+ return r
+}
+
+// RegisterRoutes routes routes to Macaron
+func RegisterRoutes(m *web.Route) {
+ reqSignIn := context.Toggle(&context.ToggleOptions{SignInRequired: true})
+ ignSignIn := context.Toggle(&context.ToggleOptions{SignInRequired: setting.Service.RequireSignInView})
+ ignSignInAndCsrf := context.Toggle(&context.ToggleOptions{DisableCSRF: true})
+ reqSignOut := context.Toggle(&context.ToggleOptions{SignOutRequired: true})
+
+ //bindIgnErr := binding.BindIgnErr
+ bindIgnErr := web.Bind
+ validation.AddBindingRules()
+
+ openIDSignInEnabled := func(ctx *context.Context) {
+ if !setting.Service.EnableOpenIDSignIn {
+ ctx.Error(403)
+ return
+ }
+ }
+
+ openIDSignUpEnabled := func(ctx *context.Context) {
+ if !setting.Service.EnableOpenIDSignUp {
+ ctx.Error(403)
+ return
+ }
+ }
+
+ reqMilestonesDashboardPageEnabled := func(ctx *context.Context) {
+ if !setting.Service.ShowMilestonesDashboardPage {
+ ctx.Error(403)
+ return
+ }
+ }
+
+ // FIXME: not all routes need go through same middlewares.
+ // Especially some AJAX requests, we can reduce middleware number to improve performance.
+ // Routers.
+ // for health check
+ m.Get("/", routers.Home)
+ m.Group("/explore", func() {
+ m.Get("", func(ctx *context.Context) {
+ ctx.Redirect(setting.AppSubURL + "/explore/repos")
+ })
+ m.Get("/repos", routers.ExploreRepos)
+ m.Get("/users", routers.ExploreUsers)
+ m.Get("/organizations", routers.ExploreOrganizations)
+ m.Get("/code", routers.ExploreCode)
+ }, ignSignIn)
+ m.Get("/issues", reqSignIn, user.Issues)
+ m.Get("/pulls", reqSignIn, user.Pulls)
+ m.Get("/milestones", reqSignIn, reqMilestonesDashboardPageEnabled, user.Milestones)
+
+ // ***** START: User *****
+ m.Group("/user", func() {
+ m.Get("/login", user.SignIn)
+ m.Post("/login", bindIgnErr(auth.SignInForm{}), user.SignInPost)
+ m.Group("", func() {
+ m.Combo("/login/openid").
+ Get(user.SignInOpenID).
+ Post(bindIgnErr(auth.SignInOpenIDForm{}), user.SignInOpenIDPost)
+ }, openIDSignInEnabled)
+ m.Group("/openid", func() {
+ m.Combo("/connect").
+ Get(user.ConnectOpenID).
+ Post(bindIgnErr(auth.ConnectOpenIDForm{}), user.ConnectOpenIDPost)
+ m.Group("/register", func() {
+ m.Combo("").
+ Get(user.RegisterOpenID, openIDSignUpEnabled).
+ Post(bindIgnErr(auth.SignUpOpenIDForm{}), user.RegisterOpenIDPost)
+ }, openIDSignUpEnabled)
+ }, openIDSignInEnabled)
+ m.Get("/sign_up", user.SignUp)
+ m.Post("/sign_up", bindIgnErr(auth.RegisterForm{}), user.SignUpPost)
+ m.Group("/oauth2", func() {
+ m.Get("/{provider}", user.SignInOAuth)
+ m.Get("/{provider}/callback", user.SignInOAuthCallback)
+ })
+ m.Get("/link_account", user.LinkAccount)
+ m.Post("/link_account_signin", bindIgnErr(auth.SignInForm{}), user.LinkAccountPostSignIn)
+ m.Post("/link_account_signup", bindIgnErr(auth.RegisterForm{}), user.LinkAccountPostRegister)
+ m.Group("/two_factor", func() {
+ m.Get("", user.TwoFactor)
+ m.Post("", bindIgnErr(auth.TwoFactorAuthForm{}), user.TwoFactorPost)
+ m.Get("/scratch", user.TwoFactorScratch)
+ m.Post("/scratch", bindIgnErr(auth.TwoFactorScratchAuthForm{}), user.TwoFactorScratchPost)
+ })
+ m.Group("/u2f", func() {
+ m.Get("", user.U2F)
+ m.Get("/challenge", user.U2FChallenge)
+ m.Post("/sign", bindIgnErr(u2f.SignResponse{}), user.U2FSign)
+
+ })
+ }, reqSignOut)
+
+ m.Any("/user/events", reqSignIn, events.Events)
+
+ m.Group("/login/oauth", func() {
+ m.Get("/authorize", bindIgnErr(auth.AuthorizationForm{}), user.AuthorizeOAuth)
+ m.Post("/grant", bindIgnErr(auth.GrantApplicationForm{}), user.GrantApplicationOAuth)
+ // TODO manage redirection
+ m.Post("/authorize", bindIgnErr(auth.AuthorizationForm{}), user.AuthorizeOAuth)
+ }, ignSignInAndCsrf, reqSignIn)
+ m.Post("/login/oauth/access_token", bindIgnErr(auth.AccessTokenForm{}), ignSignInAndCsrf, user.AccessTokenOAuth)
+
+ m.Group("/user/settings", func() {
+ m.Get("", userSetting.Profile)
+ m.Post("", bindIgnErr(auth.UpdateProfileForm{}), userSetting.ProfilePost)
+ m.Get("/change_password", user.MustChangePassword)
+ m.Post("/change_password", bindIgnErr(auth.MustChangePasswordForm{}), user.MustChangePasswordPost)
+ m.Post("/avatar", bindIgnErr(auth.AvatarForm{}), userSetting.AvatarPost)
+ m.Post("/avatar/delete", userSetting.DeleteAvatar)
+ m.Group("/account", func() {
+ m.Combo("").Get(userSetting.Account).Post(bindIgnErr(auth.ChangePasswordForm{}), userSetting.AccountPost)
+ m.Post("/email", bindIgnErr(auth.AddEmailForm{}), userSetting.EmailPost)
+ m.Post("/email/delete", userSetting.DeleteEmail)
+ m.Post("/delete", userSetting.DeleteAccount)
+ m.Post("/theme", bindIgnErr(auth.UpdateThemeForm{}), userSetting.UpdateUIThemePost)
+ })
+ m.Group("/security", func() {
+ m.Get("", userSetting.Security)
+ m.Group("/two_factor", func() {
+ m.Post("/regenerate_scratch", userSetting.RegenerateScratchTwoFactor)
+ m.Post("/disable", userSetting.DisableTwoFactor)
+ m.Get("/enroll", userSetting.EnrollTwoFactor)
+ m.Post("/enroll", bindIgnErr(auth.TwoFactorAuthForm{}), userSetting.EnrollTwoFactorPost)
+ })
+ m.Group("/u2f", func() {
+ m.Post("/request_register", bindIgnErr(auth.U2FRegistrationForm{}), userSetting.U2FRegister)
+ m.Post("/register", bindIgnErr(u2f.RegisterResponse{}), userSetting.U2FRegisterPost)
+ m.Post("/delete", bindIgnErr(auth.U2FDeleteForm{}), userSetting.U2FDelete)
+ })
+ m.Group("/openid", func() {
+ m.Post("", bindIgnErr(auth.AddOpenIDForm{}), userSetting.OpenIDPost)
+ m.Post("/delete", userSetting.DeleteOpenID)
+ m.Post("/toggle_visibility", userSetting.ToggleOpenIDVisibility)
+ }, openIDSignInEnabled)
+ m.Post("/account_link", userSetting.DeleteAccountLink)
+ })
+ m.Group("/applications/oauth2", func() {
+ m.Get("/{id}", userSetting.OAuth2ApplicationShow)
+ m.Post("/{id}", bindIgnErr(auth.EditOAuth2ApplicationForm{}), userSetting.OAuthApplicationsEdit)
+ m.Post("/{id}/regenerate_secret", userSetting.OAuthApplicationsRegenerateSecret)
+ m.Post("", bindIgnErr(auth.EditOAuth2ApplicationForm{}), userSetting.OAuthApplicationsPost)
+ m.Post("/delete", userSetting.DeleteOAuth2Application)
+ m.Post("/revoke", userSetting.RevokeOAuth2Grant)
+ })
+ m.Combo("/applications").Get(userSetting.Applications).
+ Post(bindIgnErr(auth.NewAccessTokenForm{}), userSetting.ApplicationsPost)
+ m.Post("/applications/delete", userSetting.DeleteApplication)
+ m.Combo("/keys").Get(userSetting.Keys).
+ Post(bindIgnErr(auth.AddKeyForm{}), userSetting.KeysPost)
+ m.Post("/keys/delete", userSetting.DeleteKey)
+ m.Get("/organization", userSetting.Organization)
+ m.Get("/repos", userSetting.Repos)
+ m.Post("/repos/unadopted", userSetting.AdoptOrDeleteRepository)
+ }, reqSignIn, func(ctx *context.Context) {
+ ctx.Data["PageIsUserSettings"] = true
+ ctx.Data["AllThemes"] = setting.UI.Themes
+ })
+
+ m.Group("/user", func() {
+ // r.Get("/feeds", binding.Bind(auth.FeedsForm{}), user.Feeds)
+ m.Any("/activate", user.Activate, reqSignIn)
+ m.Any("/activate_email", user.ActivateEmail)
+ m.Get("/avatar/{username}/{size}", user.Avatar)
+ m.Get("/email2user", user.Email2User)
+ m.Get("/recover_account", user.ResetPasswd)
+ m.Post("/recover_account", user.ResetPasswdPost)
+ m.Get("/forgot_password", user.ForgotPasswd)
+ m.Post("/forgot_password", user.ForgotPasswdPost)
+ m.Post("/logout", user.SignOut)
+ m.Get("/task/{task}", user.TaskStatus)
+ })
+ // ***** END: User *****
+
+ m.Get("/avatar/{hash}", user.AvatarByEmailHash)
+
+ adminReq := context.Toggle(&context.ToggleOptions{SignInRequired: true, AdminRequired: true})
+
+ // ***** START: Admin *****
+ m.Group("/admin", func() {
+ m.Get("", adminReq, admin.Dashboard)
+ m.Post("", adminReq, bindIgnErr(auth.AdminDashboardForm{}), admin.DashboardPost)
+ m.Get("/config", admin.Config)
+ m.Post("/config/test_mail", admin.SendTestMail)
+ m.Group("/monitor", func() {
+ m.Get("", admin.Monitor)
+ m.Post("/cancel/{pid}", admin.MonitorCancel)
+ m.Group("/queue/{qid}", func() {
+ m.Get("", admin.Queue)
+ m.Post("/set", admin.SetQueueSettings)
+ m.Post("/add", admin.AddWorkers)
+ m.Post("/cancel/{pid}", admin.WorkerCancel)
+ m.Post("/flush", admin.Flush)
+ })
+ })
+
+ m.Group("/users", func() {
+ m.Get("", admin.Users)
+ m.Combo("/new").Get(admin.NewUser).Post(bindIgnErr(auth.AdminCreateUserForm{}), admin.NewUserPost)
+ m.Combo("/{userid}").Get(admin.EditUser).Post(bindIgnErr(auth.AdminEditUserForm{}), admin.EditUserPost)
+ m.Post("/{userid}/delete", admin.DeleteUser)
+ })
+
+ m.Group("/emails", func() {
+ m.Get("", admin.Emails)
+ m.Post("/activate", admin.ActivateEmail)
+ })
+
+ m.Group("/orgs", func() {
+ m.Get("", admin.Organizations)
+ })
+
+ m.Group("/repos", func() {
+ m.Get("", admin.Repos)
+ m.Combo("/unadopted").Get(admin.UnadoptedRepos).Post(admin.AdoptOrDeleteRepository)
+ m.Post("/delete", admin.DeleteRepo)
+ })
+
+ m.Group("/hooks", func() {
+ m.Get("", admin.DefaultOrSystemWebhooks)
+ m.Post("/delete", admin.DeleteDefaultOrSystemWebhook)
+ m.Get("/{id}", repo.WebHooksEdit)
+ m.Post("/gitea/{id}", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost)
+ m.Post("/gogs/{id}", bindIgnErr(auth.NewGogshookForm{}), repo.GogsHooksEditPost)
+ m.Post("/slack/{id}", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksEditPost)
+ m.Post("/discord/{id}", bindIgnErr(auth.NewDiscordHookForm{}), repo.DiscordHooksEditPost)
+ m.Post("/dingtalk/{id}", bindIgnErr(auth.NewDingtalkHookForm{}), repo.DingtalkHooksEditPost)
+ m.Post("/telegram/{id}", bindIgnErr(auth.NewTelegramHookForm{}), repo.TelegramHooksEditPost)
+ m.Post("/matrix/{id}", bindIgnErr(auth.NewMatrixHookForm{}), repo.MatrixHooksEditPost)
+ m.Post("/msteams/{id}", bindIgnErr(auth.NewMSTeamsHookForm{}), repo.MSTeamsHooksEditPost)
+ m.Post("/feishu/{id}", bindIgnErr(auth.NewFeishuHookForm{}), repo.FeishuHooksEditPost)
+ })
+
+ m.Group("/{configType:default-hooks|system-hooks}", func() {
+ m.Get("/{type}/new", repo.WebhooksNew)
+ m.Post("/gitea/new", bindIgnErr(auth.NewWebhookForm{}), repo.GiteaHooksNewPost)
+ m.Post("/gogs/new", bindIgnErr(auth.NewGogshookForm{}), repo.GogsHooksNewPost)
+ m.Post("/slack/new", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksNewPost)
+ m.Post("/discord/new", bindIgnErr(auth.NewDiscordHookForm{}), repo.DiscordHooksNewPost)
+ m.Post("/dingtalk/new", bindIgnErr(auth.NewDingtalkHookForm{}), repo.DingtalkHooksNewPost)
+ m.Post("/telegram/new", bindIgnErr(auth.NewTelegramHookForm{}), repo.TelegramHooksNewPost)
+ m.Post("/matrix/new", bindIgnErr(auth.NewMatrixHookForm{}), repo.MatrixHooksNewPost)
+ m.Post("/msteams/new", bindIgnErr(auth.NewMSTeamsHookForm{}), repo.MSTeamsHooksNewPost)
+ m.Post("/feishu/new", bindIgnErr(auth.NewFeishuHookForm{}), repo.FeishuHooksNewPost)
+ })
+
+ m.Group("/auths", func() {
+ m.Get("", admin.Authentications)
+ m.Combo("/new").Get(admin.NewAuthSource).Post(bindIgnErr(auth.AuthenticationForm{}), admin.NewAuthSourcePost)
+ m.Combo("/{authid}").Get(admin.EditAuthSource).
+ Post(bindIgnErr(auth.AuthenticationForm{}), admin.EditAuthSourcePost)
+ m.Post("/{authid}/delete", admin.DeleteAuthSource)
+ })
+
+ m.Group("/notices", func() {
+ m.Get("", admin.Notices)
+ m.Post("/delete", admin.DeleteNotices)
+ m.Post("/empty", admin.EmptyNotices)
+ })
+ }, adminReq)
+ // ***** END: Admin *****
+
+ m.Group("", func() {
+ m.Get("/{username}", user.Profile)
+ m.Get("/attachments/{uuid}", repo.GetAttachment)
+ }, ignSignIn)
+
+ m.Group("/{username}", func() {
+ m.Post("/action/{action}", user.Action)
+ }, reqSignIn)
+
+ if !setting.IsProd() {
+ m.Get("/template/*", dev.TemplatePreview)
+ }
+
+ reqRepoAdmin := context.RequireRepoAdmin()
+ reqRepoCodeWriter := context.RequireRepoWriter(models.UnitTypeCode)
+ reqRepoCodeReader := context.RequireRepoReader(models.UnitTypeCode)
+ reqRepoReleaseWriter := context.RequireRepoWriter(models.UnitTypeReleases)
+ reqRepoReleaseReader := context.RequireRepoReader(models.UnitTypeReleases)
+ reqRepoWikiWriter := context.RequireRepoWriter(models.UnitTypeWiki)
+ reqRepoIssueWriter := context.RequireRepoWriter(models.UnitTypeIssues)
+ reqRepoIssueReader := context.RequireRepoReader(models.UnitTypeIssues)
+ reqRepoPullsReader := context.RequireRepoReader(models.UnitTypePullRequests)
+ reqRepoIssuesOrPullsWriter := context.RequireRepoWriterOr(models.UnitTypeIssues, models.UnitTypePullRequests)
+ reqRepoIssuesOrPullsReader := context.RequireRepoReaderOr(models.UnitTypeIssues, models.UnitTypePullRequests)
+ reqRepoProjectsReader := context.RequireRepoReader(models.UnitTypeProjects)
+ reqRepoProjectsWriter := context.RequireRepoWriter(models.UnitTypeProjects)
+
+ // ***** START: Organization *****
+ m.Group("/org", func() {
+ m.Group("", func() {
+ m.Get("/create", org.Create)
+ m.Post("/create", bindIgnErr(auth.CreateOrgForm{}), org.CreatePost)
+ })
+
+ m.Group("/{org}", func() {
+ m.Get("/dashboard", user.Dashboard)
+ m.Get("/dashboard/{team}", user.Dashboard)
+ m.Get("/issues", user.Issues)
+ m.Get("/issues/{team}", user.Issues)
+ m.Get("/pulls", user.Pulls)
+ m.Get("/pulls/{team}", user.Pulls)
+ m.Get("/milestones", reqMilestonesDashboardPageEnabled, user.Milestones)
+ m.Get("/milestones/{team}", reqMilestonesDashboardPageEnabled, user.Milestones)
+ m.Get("/members", org.Members)
+ m.Post("/members/action/{action}", org.MembersAction)
+ m.Get("/teams", org.Teams)
+ }, context.OrgAssignment(true, false, true))
+
+ m.Group("/{org}", func() {
+ m.Get("/teams/{team}", org.TeamMembers)
+ m.Get("/teams/{team}/repositories", org.TeamRepositories)
+ m.Post("/teams/{team}/action/{action}", org.TeamsAction)
+ m.Post("/teams/{team}/action/repo/{action}", org.TeamsRepoAction)
+ }, context.OrgAssignment(true, false, true))
+
+ m.Group("/{org}", func() {
+ m.Get("/teams/new", org.NewTeam)
+ m.Post("/teams/new", bindIgnErr(auth.CreateTeamForm{}), org.NewTeamPost)
+ m.Get("/teams/{team}/edit", org.EditTeam)
+ m.Post("/teams/{team}/edit", bindIgnErr(auth.CreateTeamForm{}), org.EditTeamPost)
+ m.Post("/teams/{team}/delete", org.DeleteTeam)
+
+ m.Group("/settings", func() {
+ m.Combo("").Get(org.Settings).
+ Post(bindIgnErr(auth.UpdateOrgSettingForm{}), org.SettingsPost)
+ m.Post("/avatar", bindIgnErr(auth.AvatarForm{}), org.SettingsAvatar)
+ m.Post("/avatar/delete", org.SettingsDeleteAvatar)
+
+ m.Group("/hooks", func() {
+ m.Get("", org.Webhooks)
+ m.Post("/delete", org.DeleteWebhook)
+ m.Get("/{type}/new", repo.WebhooksNew)
+ m.Post("/gitea/new", bindIgnErr(auth.NewWebhookForm{}), repo.GiteaHooksNewPost)
+ m.Post("/gogs/new", bindIgnErr(auth.NewGogshookForm{}), repo.GogsHooksNewPost)
+ m.Post("/slack/new", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksNewPost)
+ m.Post("/discord/new", bindIgnErr(auth.NewDiscordHookForm{}), repo.DiscordHooksNewPost)
+ m.Post("/dingtalk/new", bindIgnErr(auth.NewDingtalkHookForm{}), repo.DingtalkHooksNewPost)
+ m.Post("/telegram/new", bindIgnErr(auth.NewTelegramHookForm{}), repo.TelegramHooksNewPost)
+ m.Post("/matrix/new", bindIgnErr(auth.NewMatrixHookForm{}), repo.MatrixHooksNewPost)
+ m.Post("/msteams/new", bindIgnErr(auth.NewMSTeamsHookForm{}), repo.MSTeamsHooksNewPost)
+ m.Post("/feishu/new", bindIgnErr(auth.NewFeishuHookForm{}), repo.FeishuHooksNewPost)
+ m.Get("/{id}", repo.WebHooksEdit)
+ m.Post("/gitea/{id}", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost)
+ m.Post("/gogs/{id}", bindIgnErr(auth.NewGogshookForm{}), repo.GogsHooksEditPost)
+ m.Post("/slack/{id}", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksEditPost)
+ m.Post("/discord/{id}", bindIgnErr(auth.NewDiscordHookForm{}), repo.DiscordHooksEditPost)
+ m.Post("/dingtalk/{id}", bindIgnErr(auth.NewDingtalkHookForm{}), repo.DingtalkHooksEditPost)
+ m.Post("/telegram/{id}", bindIgnErr(auth.NewTelegramHookForm{}), repo.TelegramHooksEditPost)
+ m.Post("/matrix/{id}", bindIgnErr(auth.NewMatrixHookForm{}), repo.MatrixHooksEditPost)
+ m.Post("/msteams/{id}", bindIgnErr(auth.NewMSTeamsHookForm{}), repo.MSTeamsHooksEditPost)
+ m.Post("/feishu/{id}", bindIgnErr(auth.NewFeishuHookForm{}), repo.FeishuHooksEditPost)
+ })
+
+ m.Group("/labels", func() {
+ m.Get("", org.RetrieveLabels, org.Labels)
+ m.Post("/new", bindIgnErr(auth.CreateLabelForm{}), org.NewLabel)
+ m.Post("/edit", bindIgnErr(auth.CreateLabelForm{}), org.UpdateLabel)
+ m.Post("/delete", org.DeleteLabel)
+ m.Post("/initialize", bindIgnErr(auth.InitializeLabelsForm{}), org.InitializeLabels)
+ })
+
+ m.Route("/delete", "GET,POST", org.SettingsDelete)
+ })
+ }, context.OrgAssignment(true, true))
+ }, reqSignIn)
+ // ***** END: Organization *****
+
+ // ***** START: Repository *****
+ m.Group("/repo", func() {
+ m.Get("/create", repo.Create)
+ m.Post("/create", bindIgnErr(auth.CreateRepoForm{}), repo.CreatePost)
+ m.Get("/migrate", repo.Migrate)
+ m.Post("/migrate", bindIgnErr(auth.MigrateRepoForm{}), repo.MigratePost)
+ m.Group("/fork", func() {
+ m.Combo("/{repoid}").Get(repo.Fork).
+ Post(bindIgnErr(auth.CreateRepoForm{}), repo.ForkPost)
+ }, context.RepoIDAssignment(), context.UnitTypes(), reqRepoCodeReader)
+ }, reqSignIn)
+
+ // ***** Release Attachment Download without Signin
+ m.Get("/{username}/{reponame}/releases/download/{vTag}/{fileName}", ignSignIn, context.RepoAssignment(), repo.MustBeNotEmpty, repo.RedirectDownload)
+
+ m.Group("/{username}/{reponame}", func() {
+ m.Group("/settings", func() {
+ m.Combo("").Get(repo.Settings).
+ Post(bindIgnErr(auth.RepoSettingForm{}), repo.SettingsPost)
+ m.Post("/avatar", bindIgnErr(auth.AvatarForm{}), repo.SettingsAvatar)
+ m.Post("/avatar/delete", repo.SettingsDeleteAvatar)
+
+ m.Group("/collaboration", func() {
+ m.Combo("").Get(repo.Collaboration).Post(repo.CollaborationPost)
+ m.Post("/access_mode", repo.ChangeCollaborationAccessMode)
+ m.Post("/delete", repo.DeleteCollaboration)
+ m.Group("/team", func() {
+ m.Post("", repo.AddTeamPost)
+ m.Post("/delete", repo.DeleteTeam)
+ })
+ })
+ m.Group("/branches", func() {
+ m.Combo("").Get(repo.ProtectedBranch).Post(repo.ProtectedBranchPost)
+ m.Combo("/*").Get(repo.SettingsProtectedBranch).
+ Post(bindIgnErr(auth.ProtectBranchForm{}), context.RepoMustNotBeArchived(), repo.SettingsProtectedBranchPost)
+ }, repo.MustBeNotEmpty)
+
+ m.Group("/hooks", func() {
+ m.Get("", repo.Webhooks)
+ m.Post("/delete", repo.DeleteWebhook)
+ m.Get("/{type}/new", repo.WebhooksNew)
+ m.Post("/gitea/new", bindIgnErr(auth.NewWebhookForm{}), repo.GiteaHooksNewPost)
+ m.Post("/gogs/new", bindIgnErr(auth.NewGogshookForm{}), repo.GogsHooksNewPost)
+ m.Post("/slack/new", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksNewPost)
+ m.Post("/discord/new", bindIgnErr(auth.NewDiscordHookForm{}), repo.DiscordHooksNewPost)
+ m.Post("/dingtalk/new", bindIgnErr(auth.NewDingtalkHookForm{}), repo.DingtalkHooksNewPost)
+ m.Post("/telegram/new", bindIgnErr(auth.NewTelegramHookForm{}), repo.TelegramHooksNewPost)
+ m.Post("/matrix/new", bindIgnErr(auth.NewMatrixHookForm{}), repo.MatrixHooksNewPost)
+ m.Post("/msteams/new", bindIgnErr(auth.NewMSTeamsHookForm{}), repo.MSTeamsHooksNewPost)
+ m.Post("/feishu/new", bindIgnErr(auth.NewFeishuHookForm{}), repo.FeishuHooksNewPost)
+ m.Get("/{id}", repo.WebHooksEdit)
+ m.Post("/{id}/test", repo.TestWebhook)
+ m.Post("/gitea/{id}", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost)
+ m.Post("/gogs/{id}", bindIgnErr(auth.NewGogshookForm{}), repo.GogsHooksEditPost)
+ m.Post("/slack/{id}", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksEditPost)
+ m.Post("/discord/{id}", bindIgnErr(auth.NewDiscordHookForm{}), repo.DiscordHooksEditPost)
+ m.Post("/dingtalk/{id}", bindIgnErr(auth.NewDingtalkHookForm{}), repo.DingtalkHooksEditPost)
+ m.Post("/telegram/{id}", bindIgnErr(auth.NewTelegramHookForm{}), repo.TelegramHooksEditPost)
+ m.Post("/matrix/{id}", bindIgnErr(auth.NewMatrixHookForm{}), repo.MatrixHooksEditPost)
+ m.Post("/msteams/{id}", bindIgnErr(auth.NewMSTeamsHookForm{}), repo.MSTeamsHooksEditPost)
+ m.Post("/feishu/{id}", bindIgnErr(auth.NewFeishuHookForm{}), repo.FeishuHooksEditPost)
+
+ m.Group("/git", func() {
+ m.Get("", repo.GitHooks)
+ m.Combo("/{name}").Get(repo.GitHooksEdit).
+ Post(repo.GitHooksEditPost)
+ }, context.GitHookService())
+ })
+
+ m.Group("/keys", func() {
+ m.Combo("").Get(repo.DeployKeys).
+ Post(bindIgnErr(auth.AddKeyForm{}), repo.DeployKeysPost)
+ m.Post("/delete", repo.DeleteDeployKey)
+ })
+
+ m.Group("/lfs", func() {
+ m.Get("/", repo.LFSFiles)
+ m.Get("/show/{oid}", repo.LFSFileGet)
+ m.Post("/delete/{oid}", repo.LFSDelete)
+ m.Get("/pointers", repo.LFSPointerFiles)
+ m.Post("/pointers/associate", repo.LFSAutoAssociate)
+ m.Get("/find", repo.LFSFileFind)
+ m.Group("/locks", func() {
+ m.Get("/", repo.LFSLocks)
+ m.Post("/", repo.LFSLockFile)
+ m.Post("/{lid}/unlock", repo.LFSUnlock)
+ })
+ })
+
+ }, func(ctx *context.Context) {
+ ctx.Data["PageIsSettings"] = true
+ ctx.Data["LFSStartServer"] = setting.LFS.StartServer
+ })
+ }, reqSignIn, context.RepoAssignment(), context.UnitTypes(), reqRepoAdmin, context.RepoRef())
+
+ m.Post("/{username}/{reponame}/action/{action}", reqSignIn, context.RepoAssignment(), context.UnitTypes(), repo.Action)
+
+ // Grouping for those endpoints not requiring authentication
+ m.Group("/{username}/{reponame}", func() {
+ m.Group("/milestone", func() {
+ m.Get("/{id}", repo.MilestoneIssuesAndPulls)
+ }, reqRepoIssuesOrPullsReader, context.RepoRef())
+ m.Combo("/compare/*", repo.MustBeNotEmpty, reqRepoCodeReader, repo.SetEditorconfigIfExists).
+ Get(ignSignIn, repo.SetDiffViewStyle, repo.CompareDiff).
+ Post(reqSignIn, context.RepoMustNotBeArchived(), reqRepoPullsReader, repo.MustAllowPulls, bindIgnErr(auth.CreateIssueForm{}), repo.CompareAndPullRequestPost)
+ }, context.RepoAssignment(), context.UnitTypes())
+
+ // Grouping for those endpoints that do require authentication
+ m.Group("/{username}/{reponame}", func() {
+ m.Group("/issues", func() {
+ m.Group("/new", func() {
+ m.Combo("").Get(context.RepoRef(), repo.NewIssue).
+ Post(bindIgnErr(auth.CreateIssueForm{}), repo.NewIssuePost)
+ m.Get("/choose", context.RepoRef(), repo.NewIssueChooseTemplate)
+ })
+ }, context.RepoMustNotBeArchived(), reqRepoIssueReader)
+ // FIXME: should use different URLs but mostly same logic for comments of issue and pull reuqest.
+ // So they can apply their own enable/disable logic on routers.
+ m.Group("/issues", func() {
+ m.Group("/{index}", func() {
+ m.Post("/title", repo.UpdateIssueTitle)
+ m.Post("/content", repo.UpdateIssueContent)
+ m.Post("/watch", repo.IssueWatch)
+ m.Post("/ref", repo.UpdateIssueRef)
+ m.Group("/dependency", func() {
+ m.Post("/add", repo.AddDependency)
+ m.Post("/delete", repo.RemoveDependency)
+ })
+ m.Combo("/comments").Post(repo.MustAllowUserComment, bindIgnErr(auth.CreateCommentForm{}), repo.NewComment)
+ m.Group("/times", func() {
+ m.Post("/add", bindIgnErr(auth.AddTimeManuallyForm{}), repo.AddTimeManually)
+ m.Group("/stopwatch", func() {
+ m.Post("/toggle", repo.IssueStopwatch)
+ m.Post("/cancel", repo.CancelStopwatch)
+ })
+ })
+ m.Post("/reactions/{action}", bindIgnErr(auth.ReactionForm{}), repo.ChangeIssueReaction)
+ m.Post("/lock", reqRepoIssueWriter, bindIgnErr(auth.IssueLockForm{}), repo.LockIssue)
+ m.Post("/unlock", reqRepoIssueWriter, repo.UnlockIssue)
+ }, context.RepoMustNotBeArchived())
+ m.Group("/{index}", func() {
+ m.Get("/attachments", repo.GetIssueAttachments)
+ m.Get("/attachments/{uuid}", repo.GetAttachment)
+ })
+
+ m.Post("/labels", reqRepoIssuesOrPullsWriter, repo.UpdateIssueLabel)
+ m.Post("/milestone", reqRepoIssuesOrPullsWriter, repo.UpdateIssueMilestone)
+ m.Post("/projects", reqRepoIssuesOrPullsWriter, repo.UpdateIssueProject)
+ m.Post("/assignee", reqRepoIssuesOrPullsWriter, repo.UpdateIssueAssignee)
+ m.Post("/request_review", reqRepoIssuesOrPullsReader, repo.UpdatePullReviewRequest)
+ m.Post("/status", reqRepoIssuesOrPullsWriter, repo.UpdateIssueStatus)
+ m.Post("/resolve_conversation", reqRepoIssuesOrPullsReader, repo.UpdateResolveConversation)
+ m.Post("/attachments", repo.UploadIssueAttachment)
+ m.Post("/attachments/remove", repo.DeleteAttachment)
+ }, context.RepoMustNotBeArchived())
+ m.Group("/comments/{id}", func() {
+ m.Post("", repo.UpdateCommentContent)
+ m.Post("/delete", repo.DeleteComment)
+ m.Post("/reactions/{action}", bindIgnErr(auth.ReactionForm{}), repo.ChangeCommentReaction)
+ }, context.RepoMustNotBeArchived())
+ m.Group("/comments/{id}", func() {
+ m.Get("/attachments", repo.GetCommentAttachments)
+ })
+ m.Group("/labels", func() {
+ m.Post("/new", bindIgnErr(auth.CreateLabelForm{}), repo.NewLabel)
+ m.Post("/edit", bindIgnErr(auth.CreateLabelForm{}), repo.UpdateLabel)
+ m.Post("/delete", repo.DeleteLabel)
+ m.Post("/initialize", bindIgnErr(auth.InitializeLabelsForm{}), repo.InitializeLabels)
+ }, context.RepoMustNotBeArchived(), reqRepoIssuesOrPullsWriter, context.RepoRef())
+ m.Group("/milestones", func() {
+ m.Combo("/new").Get(repo.NewMilestone).
+ Post(bindIgnErr(auth.CreateMilestoneForm{}), repo.NewMilestonePost)
+ m.Get("/{id}/edit", repo.EditMilestone)
+ m.Post("/{id}/edit", bindIgnErr(auth.CreateMilestoneForm{}), repo.EditMilestonePost)
+ m.Post("/{id}/{action}", repo.ChangeMilestoneStatus)
+ m.Post("/delete", repo.DeleteMilestone)
+ }, context.RepoMustNotBeArchived(), reqRepoIssuesOrPullsWriter, context.RepoRef())
+ m.Group("/pull", func() {
+ m.Post("/{index}/target_branch", repo.UpdatePullRequestTarget)
+ }, context.RepoMustNotBeArchived())
+
+ m.Group("", func() {
+ m.Group("", func() {
+ m.Combo("/_edit/*").Get(repo.EditFile).
+ Post(bindIgnErr(auth.EditRepoFileForm{}), repo.EditFilePost)
+ m.Combo("/_new/*").Get(repo.NewFile).
+ Post(bindIgnErr(auth.EditRepoFileForm{}), repo.NewFilePost)
+ m.Post("/_preview/*", bindIgnErr(auth.EditPreviewDiffForm{}), repo.DiffPreviewPost)
+ m.Combo("/_delete/*").Get(repo.DeleteFile).
+ Post(bindIgnErr(auth.DeleteRepoFileForm{}), repo.DeleteFilePost)
+ m.Combo("/_upload/*", repo.MustBeAbleToUpload).
+ Get(repo.UploadFile).
+ Post(bindIgnErr(auth.UploadRepoFileForm{}), repo.UploadFilePost)
+ }, context.RepoRefByType(context.RepoRefBranch), repo.MustBeEditable)
+ m.Group("", func() {
+ m.Post("/upload-file", repo.UploadFileToServer)
+ m.Post("/upload-remove", bindIgnErr(auth.RemoveUploadFileForm{}), repo.RemoveUploadFileFromServer)
+ }, context.RepoRef(), repo.MustBeEditable, repo.MustBeAbleToUpload)
+ }, context.RepoMustNotBeArchived(), reqRepoCodeWriter, repo.MustBeNotEmpty)
+
+ m.Group("/branches", func() {
+ m.Group("/_new", func() {
+ m.Post("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.CreateBranch)
+ m.Post("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.CreateBranch)
+ m.Post("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.CreateBranch)
+ }, bindIgnErr(auth.NewBranchForm{}))
+ m.Post("/delete", repo.DeleteBranchPost)
+ m.Post("/restore", repo.RestoreBranchPost)
+ }, context.RepoMustNotBeArchived(), reqRepoCodeWriter, repo.MustBeNotEmpty)
+
+ }, reqSignIn, context.RepoAssignment(), context.UnitTypes())
+
+ // Releases
+ m.Group("/{username}/{reponame}", func() {
+ m.Get("/tags", repo.TagsList, repo.MustBeNotEmpty,
+ reqRepoCodeReader, context.RepoRefByType(context.RepoRefTag))
+ m.Group("/releases", func() {
+ m.Get("/", repo.Releases)
+ m.Get("/tag/*", repo.SingleRelease)
+ m.Get("/latest", repo.LatestRelease)
+ m.Get("/attachments/{uuid}", repo.GetAttachment)
+ }, repo.MustBeNotEmpty, reqRepoReleaseReader, context.RepoRefByType(context.RepoRefTag))
+ m.Group("/releases", func() {
+ m.Get("/new", repo.NewRelease)
+ m.Post("/new", bindIgnErr(auth.NewReleaseForm{}), repo.NewReleasePost)
+ m.Post("/delete", repo.DeleteRelease)
+ m.Post("/attachments", repo.UploadReleaseAttachment)
+ m.Post("/attachments/remove", repo.DeleteAttachment)
+ }, reqSignIn, repo.MustBeNotEmpty, context.RepoMustNotBeArchived(), reqRepoReleaseWriter, context.RepoRef())
+ m.Post("/tags/delete", repo.DeleteTag, reqSignIn,
+ repo.MustBeNotEmpty, context.RepoMustNotBeArchived(), reqRepoCodeWriter, context.RepoRef())
+ m.Group("/releases", func() {
+ m.Get("/edit/*", repo.EditRelease)
+ m.Post("/edit/*", bindIgnErr(auth.EditReleaseForm{}), repo.EditReleasePost)
+ }, reqSignIn, repo.MustBeNotEmpty, context.RepoMustNotBeArchived(), reqRepoReleaseWriter, func(ctx *context.Context) {
+ var err error
+ ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
+ if err != nil {
+ ctx.ServerError("GetBranchCommit", err)
+ return
+ }
+ ctx.Repo.CommitsCount, err = ctx.Repo.GetCommitsCount()
+ if err != nil {
+ ctx.ServerError("GetCommitsCount", err)
+ return
+ }
+ ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
+ })
+ }, ignSignIn, context.RepoAssignment(), context.UnitTypes(), reqRepoReleaseReader)
+
+ m.Group("/{username}/{reponame}", func() {
+ m.Post("/topics", repo.TopicsPost)
+ }, context.RepoAssignment(), context.RepoMustNotBeArchived(), reqRepoAdmin)
+
+ m.Group("/{username}/{reponame}", func() {
+ m.Group("", func() {
+ m.Get("/{type:issues|pulls}", repo.Issues)
+ m.Get("/{type:issues|pulls}/{index}", repo.ViewIssue)
+ m.Get("/labels", reqRepoIssuesOrPullsReader, repo.RetrieveLabels, repo.Labels)
+ m.Get("/milestones", reqRepoIssuesOrPullsReader, repo.Milestones)
+ }, context.RepoRef())
+
+ m.Group("/projects", func() {
+ m.Get("", repo.Projects)
+ m.Get("/{id}", repo.ViewProject)
+ m.Group("", func() {
+ m.Get("/new", repo.NewProject)
+ m.Post("/new", bindIgnErr(auth.CreateProjectForm{}), repo.NewProjectPost)
+ m.Group("/{id}", func() {
+ m.Post("", bindIgnErr(auth.EditProjectBoardTitleForm{}), repo.AddBoardToProjectPost)
+ m.Post("/delete", repo.DeleteProject)
+
+ m.Get("/edit", repo.EditProject)
+ m.Post("/edit", bindIgnErr(auth.CreateProjectForm{}), repo.EditProjectPost)
+ m.Post("/{action:open|close}", repo.ChangeProjectStatus)
+
+ m.Group("/{boardID}", func() {
+ m.Put("", bindIgnErr(auth.EditProjectBoardTitleForm{}), repo.EditProjectBoardTitle)
+ m.Delete("", repo.DeleteProjectBoard)
+ m.Post("/default", repo.SetDefaultProjectBoard)
+
+ m.Post("/{index}", repo.MoveIssueAcrossBoards)
+ })
+ })
+ }, reqRepoProjectsWriter, context.RepoMustNotBeArchived())
+ }, reqRepoProjectsReader, repo.MustEnableProjects)
+
+ m.Group("/wiki", func() {
+ m.Get("/", repo.Wiki)
+ m.Get("/{page}", repo.Wiki)
+ m.Get("/_pages", repo.WikiPages)
+ m.Get("/{page}/_revision", repo.WikiRevision)
+ m.Get("/commit/{sha:[a-f0-9]{7,40}}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.Diff)
+ m.Get("/commit/{sha:[a-f0-9]{7,40}}.{:patch|diff}", repo.RawDiff)
+
+ m.Group("", func() {
+ m.Combo("/_new").Get(repo.NewWiki).
+ Post(bindIgnErr(auth.NewWikiForm{}), repo.NewWikiPost)
+ m.Combo("/{page}/_edit").Get(repo.EditWiki).
+ Post(bindIgnErr(auth.NewWikiForm{}), repo.EditWikiPost)
+ m.Post("/{page}/delete", repo.DeleteWikiPagePost)
+ }, context.RepoMustNotBeArchived(), reqSignIn, reqRepoWikiWriter)
+ }, repo.MustEnableWiki, context.RepoRef(), func(ctx *context.Context) {
+ ctx.Data["PageIsWiki"] = true
+ })
+
+ m.Group("/wiki", func() {
+ m.Get("/raw/*", repo.WikiRaw)
+ }, repo.MustEnableWiki)
+
+ m.Group("/activity", func() {
+ m.Get("", repo.Activity)
+ m.Get("/{period}", repo.Activity)
+ }, context.RepoRef(), repo.MustBeNotEmpty, context.RequireRepoReaderOr(models.UnitTypePullRequests, models.UnitTypeIssues, models.UnitTypeReleases))
+
+ m.Group("/activity_author_data", func() {
+ m.Get("", repo.ActivityAuthors)
+ m.Get("/{period}", repo.ActivityAuthors)
+ }, context.RepoRef(), repo.MustBeNotEmpty, context.RequireRepoReaderOr(models.UnitTypeCode))
+
+ m.Group("/archive", func() {
+ m.Get("/*", repo.Download)
+ m.Post("/*", repo.InitiateDownload)
+ }, repo.MustBeNotEmpty, reqRepoCodeReader)
+
+ m.Group("/branches", func() {
+ m.Get("", repo.Branches)
+ }, repo.MustBeNotEmpty, context.RepoRef(), reqRepoCodeReader)
+
+ m.Group("/blob_excerpt", func() {
+ m.Get("/{sha}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.ExcerptBlob)
+ }, repo.MustBeNotEmpty, context.RepoRef(), reqRepoCodeReader)
+
+ m.Group("/pulls/{index}", func() {
+ m.Get(".diff", repo.DownloadPullDiff)
+ m.Get(".patch", repo.DownloadPullPatch)
+ m.Get("/commits", context.RepoRef(), repo.ViewPullCommits)
+ m.Post("/merge", context.RepoMustNotBeArchived(), bindIgnErr(auth.MergePullRequestForm{}), repo.MergePullRequest)
+ m.Post("/update", repo.UpdatePullRequest)
+ m.Post("/cleanup", context.RepoMustNotBeArchived(), context.RepoRef(), repo.CleanUpPullRequest)
+ m.Group("/files", func() {
+ m.Get("", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.ViewPullFiles)
+ m.Group("/reviews", func() {
+ m.Get("/new_comment", repo.RenderNewCodeCommentForm)
+ m.Post("/comments", bindIgnErr(auth.CodeCommentForm{}), repo.CreateCodeComment)
+ m.Post("/submit", bindIgnErr(auth.SubmitReviewForm{}), repo.SubmitReview)
+ }, context.RepoMustNotBeArchived())
+ })
+ }, repo.MustAllowPulls)
+
+ m.Group("/media", func() {
+ m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.SingleDownloadOrLFS)
+ m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.SingleDownloadOrLFS)
+ m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.SingleDownloadOrLFS)
+ m.Get("/blob/{sha}", context.RepoRefByType(context.RepoRefBlob), repo.DownloadByIDOrLFS)
+ // "/*" route is deprecated, and kept for backward compatibility
+ m.Get("/*", context.RepoRefByType(context.RepoRefLegacy), repo.SingleDownloadOrLFS)
+ }, repo.MustBeNotEmpty, reqRepoCodeReader)
+
+ m.Group("/raw", func() {
+ m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.SingleDownload)
+ m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.SingleDownload)
+ m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.SingleDownload)
+ m.Get("/blob/{sha}", context.RepoRefByType(context.RepoRefBlob), repo.DownloadByID)
+ // "/*" route is deprecated, and kept for backward compatibility
+ m.Get("/*", context.RepoRefByType(context.RepoRefLegacy), repo.SingleDownload)
+ }, repo.MustBeNotEmpty, reqRepoCodeReader)
+
+ m.Group("/commits", func() {
+ m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.RefCommits)
+ m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.RefCommits)
+ m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.RefCommits)
+ // "/*" route is deprecated, and kept for backward compatibility
+ m.Get("/*", context.RepoRefByType(context.RepoRefLegacy), repo.RefCommits)
+ }, repo.MustBeNotEmpty, reqRepoCodeReader)
+
+ m.Group("/blame", func() {
+ m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.RefBlame)
+ m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.RefBlame)
+ m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.RefBlame)
+ }, repo.MustBeNotEmpty, reqRepoCodeReader)
+
+ m.Group("", func() {
+ m.Get("/graph", repo.Graph)
+ m.Get("/commit/{sha:([a-f0-9]{7,40})$}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.Diff)
+ }, repo.MustBeNotEmpty, context.RepoRef(), reqRepoCodeReader)
+
+ m.Group("/src", func() {
+ m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.Home)
+ m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.Home)
+ m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.Home)
+ // "/*" route is deprecated, and kept for backward compatibility
+ m.Get("/*", context.RepoRefByType(context.RepoRefLegacy), repo.Home)
+ }, repo.SetEditorconfigIfExists)
+
+ m.Group("", func() {
+ m.Get("/forks", repo.Forks)
+ }, context.RepoRef(), reqRepoCodeReader)
+ m.Get("/commit/{sha:([a-f0-9]{7,40})}.{ext:patch|diff}",
+ repo.MustBeNotEmpty, reqRepoCodeReader, repo.RawDiff)
+ }, ignSignIn, context.RepoAssignment(), context.UnitTypes())
+ m.Group("/{username}/{reponame}", func() {
+ m.Get("/stars", repo.Stars)
+ m.Get("/watchers", repo.Watchers)
+ m.Get("/search", reqRepoCodeReader, repo.Search)
+ }, ignSignIn, context.RepoAssignment(), context.RepoRef(), context.UnitTypes())
+
+ m.Group("/{username}", func() {
+ m.Group("/{reponame}", func() {
+ m.Get("", repo.SetEditorconfigIfExists, repo.Home)
+ }, ignSignIn, context.RepoAssignment(), context.RepoRef(), context.UnitTypes())
+
+ m.Group("/{reponame}", func() {
+ m.Group("/info/lfs", func() {
+ m.Post("/objects/batch", lfs.BatchHandler)
+ m.Get("/objects/{oid}/{filename}", lfs.ObjectOidHandler)
+ m.Any("/objects/{oid}", lfs.ObjectOidHandler)
+ m.Post("/objects", lfs.PostHandler)
+ m.Post("/verify", lfs.VerifyHandler)
+ m.Group("/locks", func() {
+ m.Get("/", lfs.GetListLockHandler)
+ m.Post("/", lfs.PostLockHandler)
+ m.Post("/verify", lfs.VerifyLockHandler)
+ m.Post("/{lid}/unlock", lfs.UnLockHandler)
+ })
+ m.Any("/*", func(ctx *context.Context) {
+ ctx.NotFound("", nil)
+ })
+ }, ignSignInAndCsrf)
+
+ m.Group("", func() {
+ m.Post("/git-upload-pack", repo.ServiceUploadPack)
+ m.Post("/git-receive-pack", repo.ServiceReceivePack)
+ m.Get("/info/refs", repo.GetInfoRefs)
+ m.Get("/HEAD", repo.GetTextFile("HEAD"))
+ m.Get("/objects/info/alternates", repo.GetTextFile("objects/info/alternates"))
+ m.Get("/objects/info/http-alternates", repo.GetTextFile("objects/info/http-alternates"))
+ m.Get("/objects/info/packs", repo.GetInfoPacks)
+ m.Get("/objects/info/{file:[^/]*}", repo.GetTextFile(""))
+ m.Get("/objects/{head:[0-9a-f]{2}}/{hash:[0-9a-f]{38}}", repo.GetLooseObject)
+ m.Get("/objects/pack/pack-{file:[0-9a-f]{40}}.pack", repo.GetPackFile)
+ m.Get("/objects/pack/pack-{file:[0-9a-f]{40}}.idx", repo.GetIdxFile)
+ }, ignSignInAndCsrf)
+
+ m.Head("/tasks/trigger", repo.TriggerTask)
+ })
+ })
+ // ***** END: Repository *****
+
+ m.Group("/notifications", func() {
+ m.Get("", user.Notifications)
+ m.Post("/status", user.NotificationStatusPost)
+ m.Post("/purge", user.NotificationPurgePost)
+ }, reqSignIn)
+
+ if setting.API.EnableSwagger {
+ m.Get("/swagger.v1.json", routers.SwaggerV1Json)
+ }
+
+ // Not found handler.
+ m.NotFound(web.Wrap(routers.NotFound))
+}
import (
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/log"
)
// tplSwaggerV1Json swagger v1 json template
// SwaggerV1Json render swagger v1 json
func SwaggerV1Json(ctx *context.Context) {
- ctx.HTML(200, tplSwaggerV1Json)
+ t := ctx.Render.TemplateLookup(string(tplSwaggerV1Json))
+ ctx.Resp.Header().Set("Content-Type", "application/json")
+ if err := t.Execute(ctx.Resp, ctx.Data); err != nil {
+ log.Error("%v", err)
+ ctx.Error(500)
+ }
}
"strings"
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/auth/oauth2"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/eventsource"
+ auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/hcaptcha"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/password"
"code.gitea.io/gitea/modules/recaptcha"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
+ "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/utils"
"code.gitea.io/gitea/services/externalaccount"
"code.gitea.io/gitea/services/mailer"
- "gitea.com/macaron/captcha"
"github.com/markbates/goth"
"github.com/tstranex/u2f"
)
}
// SignInPost response for sign in request
-func SignInPost(ctx *context.Context, form auth.SignInForm) {
+func SignInPost(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("sign_in")
orderedOAuth2Names, oauth2Providers, err := models.GetActiveOAuth2Providers()
return
}
+ form := web.GetForm(ctx).(*auth.SignInForm)
u, err := models.UserSignIn(form.UserName, form.Password)
if err != nil {
if models.IsErrUserNotExist(err) {
}
// TwoFactorPost validates a user's two-factor authentication token.
-func TwoFactorPost(ctx *context.Context, form auth.TwoFactorAuthForm) {
+func TwoFactorPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.TwoFactorAuthForm)
ctx.Data["Title"] = ctx.Tr("twofa")
// Ensure user is in a 2FA session.
}
// TwoFactorScratchPost validates and invalidates a user's two-factor scratch token.
-func TwoFactorScratchPost(ctx *context.Context, form auth.TwoFactorScratchAuthForm) {
+func TwoFactorScratchPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.TwoFactorScratchAuthForm)
ctx.Data["Title"] = ctx.Tr("twofa_scratch")
// Ensure user is in a 2FA session.
}
// U2FSign authenticates the user by signResp
-func U2FSign(ctx *context.Context, signResp u2f.SignResponse) {
+func U2FSign(ctx *context.Context) {
+ signResp := web.GetForm(ctx).(*u2f.SignResponse)
challSess := ctx.Session.Get("u2fChallenge")
idSess := ctx.Session.Get("twofaUid")
if challSess == nil || idSess == nil {
log.Fatal("parsing u2f registration: %v", err)
continue
}
- newCounter, authErr := r.Authenticate(signResp, *challenge, reg.Counter)
+ newCounter, authErr := r.Authenticate(*signResp, *challenge, reg.Counter)
if authErr == nil {
reg.Counter = newCounter
user, err := models.GetUserByID(id)
}
// try to do a direct callback flow, so we don't authenticate the user again but use the valid accesstoken to get the user
- user, gothUser, err := oAuth2UserLoginCallback(loginSource, ctx.Req.Request, ctx.Resp)
+ user, gothUser, err := oAuth2UserLoginCallback(loginSource, ctx.Req, ctx.Resp)
if err == nil && user != nil {
// we got the user without going through the whole OAuth2 authentication flow again
handleOAuth2SignIn(user, gothUser, ctx, err)
return
}
- if err = oauth2.Auth(loginSource.Name, ctx.Req.Request, ctx.Resp); err != nil {
+ if err = oauth2.Auth(loginSource.Name, ctx.Req, ctx.Resp); err != nil {
if strings.Contains(err.Error(), "no provider for ") {
if err = models.ResetOAuth2(); err != nil {
ctx.ServerError("SignIn", err)
return
}
- if err = oauth2.Auth(loginSource.Name, ctx.Req.Request, ctx.Resp); err != nil {
+ if err = oauth2.Auth(loginSource.Name, ctx.Req, ctx.Resp); err != nil {
ctx.ServerError("SignIn", err)
}
return
return
}
- u, gothUser, err := oAuth2UserLoginCallback(loginSource, ctx.Req.Request, ctx.Resp)
+ u, gothUser, err := oAuth2UserLoginCallback(loginSource, ctx.Req, ctx.Resp)
handleOAuth2SignIn(u, gothUser, ctx, err)
}
}
// LinkAccountPostSignIn handle the coupling of external account with another account using signIn
-func LinkAccountPostSignIn(ctx *context.Context, signInForm auth.SignInForm) {
+func LinkAccountPostSignIn(ctx *context.Context) {
+ signInForm := web.GetForm(ctx).(*auth.SignInForm)
ctx.Data["DisablePassword"] = !setting.Service.RequireExternalRegistrationPassword || setting.Service.AllowOnlyExternalRegistration
ctx.Data["Title"] = ctx.Tr("link_account")
ctx.Data["LinkAccountMode"] = true
}
// LinkAccountPostRegister handle the creation of a new account for an external account using signUp
-func LinkAccountPostRegister(ctx *context.Context, cpt *captcha.Captcha, form auth.RegisterForm) {
+func LinkAccountPostRegister(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.RegisterForm)
// TODO Make insecure passwords optional for local accounts also,
// once email-based Second-Factor Auth is available
ctx.Data["DisablePassword"] = !setting.Service.RequireExternalRegistrationPassword || setting.Service.AllowOnlyExternalRegistration
var err error
switch setting.Service.CaptchaType {
case setting.ImageCaptcha:
- valid = cpt.VerifyReq(ctx.Req)
+ valid = context.GetImageCaptcha().VerifyReq(ctx.Req)
case setting.ReCaptcha:
valid, err = recaptcha.Verify(ctx.Req.Context(), form.GRecaptchaResponse)
case setting.HCaptcha:
// HandleSignOut resets the session and sets the cookies
func HandleSignOut(ctx *context.Context) {
_ = ctx.Session.Flush()
- _ = ctx.Session.Destroy(ctx.Context)
+ _ = ctx.Session.Destroy(ctx.Resp, ctx.Req)
ctx.SetCookie(setting.CookieUserName, "", -1, setting.AppSubURL, setting.SessionConfig.Domain, setting.SessionConfig.Secure, true)
ctx.SetCookie(setting.CookieRememberName, "", -1, setting.AppSubURL, setting.SessionConfig.Domain, setting.SessionConfig.Secure, true)
ctx.SetCookie(setting.CSRFCookieName, "", -1, setting.AppSubURL, setting.SessionConfig.Domain, setting.SessionConfig.Secure, true)
}
// SignUpPost response for sign up information submission
-func SignUpPost(ctx *context.Context, cpt *captcha.Captcha, form auth.RegisterForm) {
+func SignUpPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.RegisterForm)
ctx.Data["Title"] = ctx.Tr("sign_up")
ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/sign_up"
var err error
switch setting.Service.CaptchaType {
case setting.ImageCaptcha:
- valid = cpt.VerifyReq(ctx.Req)
+ valid = context.GetImageCaptcha().VerifyReq(ctx.Req)
case setting.ReCaptcha:
valid, err = recaptcha.Verify(ctx.Req.Context(), form.GRecaptchaResponse)
case setting.HCaptcha:
// MustChangePasswordPost response for updating a user's password after his/her
// account was created by an admin
-func MustChangePasswordPost(ctx *context.Context, cpt *captcha.Captcha, form auth.MustChangePasswordForm) {
+func MustChangePasswordPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.MustChangePasswordForm)
ctx.Data["Title"] = ctx.Tr("auth.must_change_password")
ctx.Data["ChangePasscodeLink"] = setting.AppSubURL + "/user/settings/change_password"
if ctx.HasError() {
"net/url"
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/auth/openid"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
+ auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/generate"
"code.gitea.io/gitea/modules/hcaptcha"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/recaptcha"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
+ "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/mailer"
-
- "gitea.com/macaron/captcha"
)
const (
}
// SignInOpenIDPost response for openid sign in request
-func SignInOpenIDPost(ctx *context.Context, form auth.SignInOpenIDForm) {
+func SignInOpenIDPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.SignInOpenIDForm)
ctx.Data["Title"] = ctx.Tr("sign_in")
ctx.Data["PageIsSignIn"] = true
ctx.Data["PageIsLoginOpenID"] = true
// signInOpenIDVerify handles response from OpenID provider
func signInOpenIDVerify(ctx *context.Context) {
- log.Trace("Incoming call to: " + ctx.Req.Request.URL.String())
+ log.Trace("Incoming call to: " + ctx.Req.URL.String())
- fullURL := setting.AppURL + ctx.Req.Request.URL.String()[1:]
+ fullURL := setting.AppURL + ctx.Req.URL.String()[1:]
log.Trace("Full URL: " + fullURL)
var id, err = openid.Verify(fullURL)
}
// ConnectOpenIDPost handles submission of a form to connect an OpenID URI to an existing account
-func ConnectOpenIDPost(ctx *context.Context, form auth.ConnectOpenIDForm) {
-
+func ConnectOpenIDPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.ConnectOpenIDForm)
oid, _ := ctx.Session.Get("openid_verified_uri").(string)
if oid == "" {
ctx.Redirect(setting.AppSubURL + "/user/login/openid")
}
// RegisterOpenIDPost handles submission of a form to create a new user authenticated via an OpenID URI
-func RegisterOpenIDPost(ctx *context.Context, cpt *captcha.Captcha, form auth.SignUpOpenIDForm) {
+func RegisterOpenIDPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.SignUpOpenIDForm)
oid, _ := ctx.Session.Get("openid_verified_uri").(string)
if oid == "" {
ctx.Redirect(setting.AppSubURL + "/user/login/openid")
var err error
switch setting.Service.CaptchaType {
case setting.ImageCaptcha:
- valid = cpt.VerifyReq(ctx.Req)
+ valid = context.GetImageCaptcha().VerifyReq(ctx.Req)
case setting.ReCaptcha:
if err := ctx.Req.ParseForm(); err != nil {
ctx.ServerError("", err)
"strings"
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
+ auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
+ "code.gitea.io/gitea/modules/web"
- "gitea.com/macaron/binding"
+ "gitea.com/go-chi/binding"
"github.com/dgrijalva/jwt-go"
)
}
// AuthorizeOAuth manages authorize requests
-func AuthorizeOAuth(ctx *context.Context, form auth.AuthorizationForm) {
+func AuthorizeOAuth(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.AuthorizationForm)
errs := binding.Errors{}
- errs = form.Validate(ctx.Context, errs)
+ errs = form.Validate(ctx.Req, errs)
if len(errs) > 0 {
errstring := ""
for _, e := range errs {
}
// GrantApplicationOAuth manages the post request submitted when a user grants access to an application
-func GrantApplicationOAuth(ctx *context.Context, form auth.GrantApplicationForm) {
+func GrantApplicationOAuth(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.GrantApplicationForm)
if ctx.Session.Get("client_id") != form.ClientID || ctx.Session.Get("state") != form.State ||
ctx.Session.Get("redirect_uri") != form.RedirectURI {
ctx.Error(400)
}
// AccessTokenOAuth manages all access token requests by the client
-func AccessTokenOAuth(ctx *context.Context, form auth.AccessTokenForm) {
+func AccessTokenOAuth(ctx *context.Context) {
+ form := *web.GetForm(ctx).(*auth.AccessTokenForm)
if form.ClientID == "" {
authHeader := ctx.Req.Header.Get("Authorization")
authContent := strings.SplitN(authHeader, " ", 2)
"time"
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
+ auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/password"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
+ "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/mailer"
)
}
// AccountPost response for change user's password
-func AccountPost(ctx *context.Context, form auth.ChangePasswordForm) {
+func AccountPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.ChangePasswordForm)
ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["PageIsSettingsAccount"] = true
}
// EmailPost response for change user's email
-func EmailPost(ctx *context.Context, form auth.AddEmailForm) {
+func EmailPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.AddEmailForm)
ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["PageIsSettingsAccount"] = true
}
// UpdateUIThemePost is used to update users' specific theme
-func UpdateUIThemePost(ctx *context.Context, form auth.UpdateThemeForm) {
-
+func UpdateUIThemePost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.UpdateThemeForm)
ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["PageIsSettingsAccount"] = true
"testing"
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
+ auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test"
+ "code.gitea.io/gitea/modules/web"
"github.com/stretchr/testify/assert"
)
test.LoadUser(t, ctx, 2)
test.LoadRepo(t, ctx, 1)
- AccountPost(ctx, auth.ChangePasswordForm{
+ web.SetForm(ctx, &auth.ChangePasswordForm{
OldPassword: req.OldPassword,
Password: req.NewPassword,
Retype: req.Retype,
})
+ AccountPost(ctx)
assert.Contains(t, ctx.Flash.ErrorMsg, req.Message)
assert.EqualValues(t, http.StatusFound, ctx.Resp.Status())
import (
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
+ auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/web"
)
const (
}
// ApplicationsPost response for add user's access token
-func ApplicationsPost(ctx *context.Context, form auth.NewAccessTokenForm) {
+func ApplicationsPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.NewAccessTokenForm)
ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["PageIsSettingsApplications"] = true
import (
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
+ auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/web"
)
const (
}
// KeysPost response for change user's SSH/GPG keys
-func KeysPost(ctx *context.Context, form auth.AddKeyForm) {
+func KeysPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.AddKeyForm)
ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["PageIsSettingsKeys"] = true
ctx.Data["DisableSSH"] = setting.SSH.Disabled
"fmt"
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
+ auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/web"
)
const (
)
// OAuthApplicationsPost response for adding a oauth2 application
-func OAuthApplicationsPost(ctx *context.Context, form auth.EditOAuth2ApplicationForm) {
+func OAuthApplicationsPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.EditOAuth2ApplicationForm)
ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["PageIsSettingsApplications"] = true
}
// OAuthApplicationsEdit response for editing oauth2 application
-func OAuthApplicationsEdit(ctx *context.Context, form auth.EditOAuth2ApplicationForm) {
+func OAuthApplicationsEdit(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.EditOAuth2ApplicationForm)
ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["PageIsSettingsApplications"] = true
"strings"
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
+ auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/web"
"github.com/unknwon/i18n"
)
}
// ProfilePost response for change user's profile
-func ProfilePost(ctx *context.Context, form auth.UpdateProfileForm) {
+func ProfilePost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.UpdateProfileForm)
ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["PageIsSettingsProfile"] = true
// UpdateAvatarSetting update user's avatar
// FIXME: limit size.
-func UpdateAvatarSetting(ctx *context.Context, form auth.AvatarForm, ctxUser *models.User) error {
+func UpdateAvatarSetting(ctx *context.Context, form *auth.AvatarForm, ctxUser *models.User) error {
ctxUser.UseCustomAvatar = form.Source == auth.AvatarLocal
if len(form.Gravatar) > 0 {
if form.Avatar != nil {
}
// AvatarPost response for change user's avatar request
-func AvatarPost(ctx *context.Context, form auth.AvatarForm) {
+func AvatarPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.AvatarForm)
if err := UpdateAvatarSetting(ctx, form, ctx.User); err != nil {
ctx.Flash.Error(err.Error())
} else {
import (
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/auth/openid"
"code.gitea.io/gitea/modules/context"
+ auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/web"
)
// OpenIDPost response for change user's openid
-func OpenIDPost(ctx *context.Context, form auth.AddOpenIDForm) {
+func OpenIDPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.AddOpenIDForm)
ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["PageIsSettingsSecurity"] = true
}
func settingsOpenIDVerify(ctx *context.Context) {
- log.Trace("Incoming call to: " + ctx.Req.Request.URL.String())
+ log.Trace("Incoming call to: " + ctx.Req.URL.String())
- fullURL := setting.AppURL + ctx.Req.Request.URL.String()[1:]
+ fullURL := setting.AppURL + ctx.Req.URL.String()[1:]
log.Trace("Full URL: " + fullURL)
id, err := openid.Verify(fullURL)
"strings"
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/context"
+ auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/web"
"github.com/pquerna/otp"
"github.com/pquerna/otp/totp"
}
// EnrollTwoFactorPost handles enrolling the user into 2FA.
-func EnrollTwoFactorPost(ctx *context.Context, form auth.TwoFactorAuthForm) {
+func EnrollTwoFactorPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.TwoFactorAuthForm)
ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["PageIsSettingsSecurity"] = true
"errors"
"code.gitea.io/gitea/models"
- "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/context"
+ auth "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/web"
"github.com/tstranex/u2f"
)
// U2FRegister initializes the u2f registration procedure
-func U2FRegister(ctx *context.Context, form auth.U2FRegistrationForm) {
+func U2FRegister(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.U2FRegistrationForm)
if form.Name == "" {
ctx.Error(409)
return
}
// U2FRegisterPost receives the response of the security key
-func U2FRegisterPost(ctx *context.Context, response u2f.RegisterResponse) {
+func U2FRegisterPost(ctx *context.Context) {
+ response := web.GetForm(ctx).(*u2f.RegisterResponse)
challSess := ctx.Session.Get("u2fChallenge")
u2fName := ctx.Session.Get("u2fName")
if challSess == nil || u2fName == nil {
// certificate by default.
SkipAttestationVerify: true,
}
- reg, err := u2f.Register(response, *challenge, config)
+ reg, err := u2f.Register(*response, *challenge, config)
if err != nil {
ctx.ServerError("u2f.Register", err)
return
}
// U2FDelete deletes an security key by id
-func U2FDelete(ctx *context.Context, form auth.U2FDeleteForm) {
+func U2FDelete(ctx *context.Context) {
+ form := web.GetForm(ctx).(*auth.U2FDeleteForm)
reg, err := models.GetU2FRegistrationByID(form.ID)
if err != nil {
if models.IsErrU2FRegistrationNotExist(err) {
<dd><pre>{{.Config | JsonPrettyPrint}}</pre></dd>
{{end}}
<div class="ui divider"></div>
- <dt>{{$.i18n.Tr "admin.config.macaron_log_mode"}}</dt>
- {{if .RedirectMacaronLog}}
- {{if .Loggers.macaron.SubLogDescriptions}}
- <dd>{{$.i18n.Tr "admin.config.own_named_logger"}}</dd>
- {{range .Loggers.macaron.SubLogDescriptions}}
- <dt>{{$.i18n.Tr "admin.config.log_mode"}}</dt>
- <dd>{{.Name}} ({{.Provider}})</dd>
- <dt>{{$.i18n.Tr "admin.config.log_config"}}</dt>
- <dd><pre>{{.Config | JsonPrettyPrint}}</pre></dd>
- {{end}}
- {{else}}
- <dd>{{$.i18n.Tr "admin.config.routes_to_default_logger"}}</dd>
- {{end}}
- {{else}}
- <dd>{{$.i18n.Tr "admin.config.go_log"}}</dd>
- {{end}}
- <div class="ui divider"></div>
<dt>{{$.i18n.Tr "admin.config.router_log_mode"}}</dt>
{{if .DisableRouterLog}}
<dd>{{$.i18n.Tr "admin.config.disabled_logger"}}</dd>
<a class="ui" href="{{.RepoLink}}/commits{{if .IsViewBranch}}/branch{{else if .IsViewTag}}/tag{{else if .IsViewCommit}}/commit{{end}}/{{EscapePound .BranchName}}">{{svg "octicon-history"}} <b>{{.CommitsCount}}</b> {{.i18n.Tr (TrN .i18n.Lang .CommitsCount "repo.commit" "repo.commits") }}</a>
</div>
<div class="item{{if .PageIsBranches}} active{{end}}">
- <a class="ui" href="{{.RepoLink}}/branches/">{{svg "octicon-git-branch"}} <b>{{.BranchesCount}}</b> {{.i18n.Tr (TrN .i18n.Lang .BranchesCount "repo.branch" "repo.branches") }}</a>
+ <a class="ui" href="{{.RepoLink}}/branches">{{svg "octicon-git-branch"}} <b>{{.BranchesCount}}</b> {{.i18n.Tr (TrN .i18n.Lang .BranchesCount "repo.branch" "repo.branches") }}</a>
</div>
{{if $.Permission.CanRead $.UnitTypeCode}}
<div class="item">
}
},
"x-go-name": "MergePullRequestForm",
- "x-go-package": "code.gitea.io/gitea/modules/auth"
+ "x-go-package": "code.gitea.io/gitea/modules/forms"
},
"MigrateRepoForm": {
"description": "MigrateRepoForm form for migrating repository\nthis is used to interact with web ui",
"x-go-name": "Wiki"
}
},
- "x-go-package": "code.gitea.io/gitea/modules/auth"
+ "x-go-package": "code.gitea.io/gitea/modules/forms"
},
"MigrateRepoOptions": {
"description": "MigrateRepoOptions options for migrating repository's\nthis is used to interact with api v1",
--- /dev/null
+kind: pipeline
+name: go1-1-1
+
+steps:
+- name: test
+ image: golang:1.11
+ environment:
+ GOPROXY: https://goproxy.cn
+ commands:
+ - go build -v
+ - go test -v -race -coverprofile=coverage.txt -covermode=atomic
+
+---
+kind: pipeline
+name: go1-1-2
+
+steps:
+- name: test
+ image: golang:1.12
+ environment:
+ GOPROXY: https://goproxy.cn
+ commands:
+ - go build -v
+ - go test -v -race -coverprofile=coverage.txt -covermode=atomic
\ No newline at end of file
--- /dev/null
+Apache License
+Version 2.0, January 2004
+http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+"License" shall mean the terms and conditions for use, reproduction, and
+distribution as defined by Sections 1 through 9 of this document.
+
+"Licensor" shall mean the copyright owner or entity authorized by the copyright
+owner that is granting the License.
+
+"Legal Entity" shall mean the union of the acting entity and all other entities
+that control, are controlled by, or are under common control with that entity.
+For the purposes of this definition, "control" means (i) the power, direct or
+indirect, to cause the direction or management of such entity, whether by
+contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
+outstanding shares, or (iii) beneficial ownership of such entity.
+
+"You" (or "Your") shall mean an individual or Legal Entity exercising
+permissions granted by this License.
+
+"Source" form shall mean the preferred form for making modifications, including
+but not limited to software source code, documentation source, and configuration
+files.
+
+"Object" form shall mean any form resulting from mechanical transformation or
+translation of a Source form, including but not limited to compiled object code,
+generated documentation, and conversions to other media types.
+
+"Work" shall mean the work of authorship, whether in Source or Object form, made
+available under the License, as indicated by a copyright notice that is included
+in or attached to the work (an example is provided in the Appendix below).
+
+"Derivative Works" shall mean any work, whether in Source or Object form, that
+is based on (or derived from) the Work and for which the editorial revisions,
+annotations, elaborations, or other modifications represent, as a whole, an
+original work of authorship. For the purposes of this License, Derivative Works
+shall not include works that remain separable from, or merely link (or bind by
+name) to the interfaces of, the Work and Derivative Works thereof.
+
+"Contribution" shall mean any work of authorship, including the original version
+of the Work and any modifications or additions to that Work or Derivative Works
+thereof, that is intentionally submitted to Licensor for inclusion in the Work
+by the copyright owner or by an individual or Legal Entity authorized to submit
+on behalf of the copyright owner. For the purposes of this definition,
+"submitted" means any form of electronic, verbal, or written communication sent
+to the Licensor or its representatives, including but not limited to
+communication on electronic mailing lists, source code control systems, and
+issue tracking systems that are managed by, or on behalf of, the Licensor for
+the purpose of discussing and improving the Work, but excluding communication
+that is conspicuously marked or otherwise designated in writing by the copyright
+owner as "Not a Contribution."
+
+"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
+of whom a Contribution has been received by Licensor and subsequently
+incorporated within the Work.
+
+2. Grant of Copyright License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable copyright license to reproduce, prepare Derivative Works of,
+publicly display, publicly perform, sublicense, and distribute the Work and such
+Derivative Works in Source or Object form.
+
+3. Grant of Patent License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable (except as stated in this section) patent license to make, have
+made, use, offer to sell, sell, import, and otherwise transfer the Work, where
+such license applies only to those patent claims licensable by such Contributor
+that are necessarily infringed by their Contribution(s) alone or by combination
+of their Contribution(s) with the Work to which such Contribution(s) was
+submitted. If You institute patent litigation against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that the Work or a
+Contribution incorporated within the Work constitutes direct or contributory
+patent infringement, then any patent licenses granted to You under this License
+for that Work shall terminate as of the date such litigation is filed.
+
+4. Redistribution.
+
+You may reproduce and distribute copies of the Work or Derivative Works thereof
+in any medium, with or without modifications, and in Source or Object form,
+provided that You meet the following conditions:
+
+You must give any other recipients of the Work or Derivative Works a copy of
+this License; and
+You must cause any modified files to carry prominent notices stating that You
+changed the files; and
+You must retain, in the Source form of any Derivative Works that You distribute,
+all copyright, patent, trademark, and attribution notices from the Source form
+of the Work, excluding those notices that do not pertain to any part of the
+Derivative Works; and
+If the Work includes a "NOTICE" text file as part of its distribution, then any
+Derivative Works that You distribute must include a readable copy of the
+attribution notices contained within such NOTICE file, excluding those notices
+that do not pertain to any part of the Derivative Works, in at least one of the
+following places: within a NOTICE text file distributed as part of the
+Derivative Works; within the Source form or documentation, if provided along
+with the Derivative Works; or, within a display generated by the Derivative
+Works, if and wherever such third-party notices normally appear. The contents of
+the NOTICE file are for informational purposes only and do not modify the
+License. You may add Your own attribution notices within Derivative Works that
+You distribute, alongside or as an addendum to the NOTICE text from the Work,
+provided that such additional attribution notices cannot be construed as
+modifying the License.
+You may add Your own copyright statement to Your modifications and may provide
+additional or different license terms and conditions for use, reproduction, or
+distribution of Your modifications, or for any such Derivative Works as a whole,
+provided Your use, reproduction, and distribution of the Work otherwise complies
+with the conditions stated in this License.
+
+5. Submission of Contributions.
+
+Unless You explicitly state otherwise, any Contribution intentionally submitted
+for inclusion in the Work by You to the Licensor shall be under the terms and
+conditions of this License, without any additional terms or conditions.
+Notwithstanding the above, nothing herein shall supersede or modify the terms of
+any separate license agreement you may have executed with Licensor regarding
+such Contributions.
+
+6. Trademarks.
+
+This License does not grant permission to use the trade names, trademarks,
+service marks, or product names of the Licensor, except as required for
+reasonable and customary use in describing the origin of the Work and
+reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty.
+
+Unless required by applicable law or agreed to in writing, Licensor provides the
+Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
+including, without limitation, any warranties or conditions of TITLE,
+NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
+solely responsible for determining the appropriateness of using or
+redistributing the Work and assume any risks associated with Your exercise of
+permissions under this License.
+
+8. Limitation of Liability.
+
+In no event and under no legal theory, whether in tort (including negligence),
+contract, or otherwise, unless required by applicable law (such as deliberate
+and grossly negligent acts) or agreed to in writing, shall any Contributor be
+liable to You for damages, including any direct, indirect, special, incidental,
+or consequential damages of any character arising as a result of this License or
+out of the use or inability to use the Work (including but not limited to
+damages for loss of goodwill, work stoppage, computer failure or malfunction, or
+any and all other commercial damages or losses), even if such Contributor has
+been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability.
+
+While redistributing the Work or Derivative Works thereof, You may choose to
+offer, and charge a fee for, acceptance of support, warranty, indemnity, or
+other liability obligations and/or rights consistent with this License. However,
+in accepting such obligations, You may act only on Your own behalf and on Your
+sole responsibility, not on behalf of any other Contributor, and only if You
+agree to indemnify, defend, and hold each Contributor harmless for any liability
+incurred by, or claims asserted against, such Contributor by reason of your
+accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work
+
+To apply the Apache License to your work, attach the following boilerplate
+notice, with the fields enclosed by brackets "[]" replaced with your own
+identifying information. (Don't include the brackets!) The text should be
+enclosed in the appropriate comment syntax for the file format. We also
+recommend that a file or class name and description of purpose be included on
+the same "printed page" as the copyright notice for easier identification within
+third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
\ No newline at end of file
--- /dev/null
+Middleware binding provides request data binding and validation for net/http, It's a fork of [Macaron](https://github.com/go-macaron/macaron).
+
+## License
+
+This project is under the Apache License, Version 2.0. See the [LICENSE](LICENSE) file for the full license text.
\ No newline at end of file
--- /dev/null
+// Copyright 2014 Martini Authors
+// Copyright 2014 The Macaron Authors
+// Copyright 2020 The Gitea Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+// Package binding is a middleware that provides request data binding and validation for Chi.
+package binding
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "mime/multipart"
+ "net/http"
+ "net/url"
+ "reflect"
+ "regexp"
+ "strconv"
+ "strings"
+ "unicode/utf8"
+
+ "github.com/unknwon/com"
+)
+
+// Bind wraps up the functionality of the Form and Json middleware
+// according to the Content-Type and verb of the request.
+// A Content-Type is required for POST and PUT requests.
+// Bind invokes the ErrorHandler middleware to bail out if errors
+// occurred. If you want to perform your own error handling, use
+// Form or Json middleware directly. An interface pointer can
+// be added as a second argument in order to map the struct to
+// a specific interface.
+func Bind(req *http.Request, obj interface{}) Errors {
+ contentType := req.Header.Get("Content-Type")
+ if req.Method == "POST" || req.Method == "PUT" || len(contentType) > 0 {
+ switch {
+ case strings.Contains(contentType, "form-urlencoded"):
+ return Form(req, obj)
+ case strings.Contains(contentType, "multipart/form-data"):
+ return MultipartForm(req, obj)
+ case strings.Contains(contentType, "json"):
+ return JSON(req, obj)
+ default:
+ var errors Errors
+ if contentType == "" {
+ errors.Add([]string{}, ERR_CONTENT_TYPE, "Empty Content-Type")
+ } else {
+ errors.Add([]string{}, ERR_CONTENT_TYPE, "Unsupported Content-Type")
+ }
+ return errors
+ }
+ } else {
+ return Form(req, obj)
+ }
+}
+
+const (
+ _JSON_CONTENT_TYPE = "application/json; charset=utf-8"
+ STATUS_UNPROCESSABLE_ENTITY = 422
+)
+
+// errorHandler simply counts the number of errors in the
+// context and, if more than 0, writes a response with an
+// error code and a JSON payload describing the errors.
+// The response will have a JSON content-type.
+// Middleware remaining on the stack will not even see the request
+// if, by this point, there are any errors.
+// This is a "default" handler, of sorts, and you are
+// welcome to use your own instead. The Bind middleware
+// invokes this automatically for convenience.
+func errorHandler(errs Errors, rw http.ResponseWriter) {
+ if len(errs) > 0 {
+ rw.Header().Set("Content-Type", _JSON_CONTENT_TYPE)
+ if errs.Has(ERR_DESERIALIZATION) {
+ rw.WriteHeader(http.StatusBadRequest)
+ } else if errs.Has(ERR_CONTENT_TYPE) {
+ rw.WriteHeader(http.StatusUnsupportedMediaType)
+ } else {
+ rw.WriteHeader(STATUS_UNPROCESSABLE_ENTITY)
+ }
+ errOutput, _ := json.Marshal(errs)
+ rw.Write(errOutput)
+ return
+ }
+}
+
+// Form is middleware to deserialize form-urlencoded data from the request.
+// It gets data from the form-urlencoded body, if present, or from the
+// query string. It uses the http.Request.ParseForm() method
+// to perform deserialization, then reflection is used to map each field
+// into the struct with the proper type. Structs with primitive slice types
+// (bool, float, int, string) can support deserialization of repeated form
+// keys, for example: key=val1&key=val2&key=val3
+// An interface pointer can be added as a second argument in order
+// to map the struct to a specific interface.
+func Form(req *http.Request, formStruct interface{}) Errors {
+ var errors Errors
+
+ ensurePointer(formStruct)
+ formStructV := reflect.ValueOf(formStruct)
+ parseErr := req.ParseForm()
+
+ // Format validation of the request body or the URL would add considerable overhead,
+ // and ParseForm does not complain when URL encoding is off.
+ // Because an empty request body or url can also mean absence of all needed values,
+ // it is not in all cases a bad request, so let's return 422.
+ if parseErr != nil {
+ errors.Add([]string{}, ERR_DESERIALIZATION, parseErr.Error())
+ }
+ errors = mapForm(formStructV, req.Form, nil, errors)
+ return append(errors, Validate(req, formStruct)...)
+}
+
+// MaxMemory represents maximum amount of memory to use when parsing a multipart form.
+// Set this to whatever value you prefer; default is 10 MB.
+var MaxMemory = int64(1024 * 1024 * 10)
+
+// MultipartForm works much like Form, except it can parse multipart forms
+// and handle file uploads. Like the other deserialization middleware handlers,
+// you can pass in an interface to make the interface available for injection
+// into other handlers later.
+func MultipartForm(req *http.Request, formStruct interface{}) Errors {
+ var errors Errors
+ ensurePointer(formStruct)
+ formStructV := reflect.ValueOf(formStruct)
+ // This if check is necessary due to https://github.com/martini-contrib/csrf/issues/6
+ if req.MultipartForm == nil {
+ // Workaround for multipart forms returning nil instead of an error
+ // when content is not multipart; see https://code.google.com/p/go/issues/detail?id=6334
+ if multipartReader, err := req.MultipartReader(); err != nil {
+ errors.Add([]string{}, ERR_DESERIALIZATION, err.Error())
+ } else {
+ form, parseErr := multipartReader.ReadForm(MaxMemory)
+ if parseErr != nil {
+ errors.Add([]string{}, ERR_DESERIALIZATION, parseErr.Error())
+ }
+
+ if req.Form == nil {
+ req.ParseForm()
+ }
+ for k, v := range form.Value {
+ req.Form[k] = append(req.Form[k], v...)
+ }
+
+ req.MultipartForm = form
+ }
+ }
+ errors = mapForm(formStructV, req.MultipartForm.Value, req.MultipartForm.File, errors)
+ return append(errors, Validate(req, formStruct)...)
+}
+
+// JSON is middleware to deserialize a JSON payload from the request
+// into the struct that is passed in. The resulting struct is then
+// validated, but no error handling is actually performed here.
+// An interface pointer can be added as a second argument in order
+// to map the struct to a specific interface.
+func JSON(req *http.Request, jsonStruct interface{}) Errors {
+ var errors Errors
+ ensurePointer(jsonStruct)
+
+ if req.Body != nil {
+ defer req.Body.Close()
+ err := json.NewDecoder(req.Body).Decode(jsonStruct)
+ if err != nil && err != io.EOF {
+ errors.Add([]string{}, ERR_DESERIALIZATION, err.Error())
+ }
+ }
+ return append(errors, Validate(req, jsonStruct)...)
+}
+
+// RawValidate is same as Validate but does not require a HTTP context,
+// and can be used independently just for validation.
+// This function does not support Validator interface.
+func RawValidate(obj interface{}) Errors {
+ var errs Errors
+ v := reflect.ValueOf(obj)
+ k := v.Kind()
+ if k == reflect.Interface || k == reflect.Ptr {
+ v = v.Elem()
+ k = v.Kind()
+ }
+ if k == reflect.Slice || k == reflect.Array {
+ for i := 0; i < v.Len(); i++ {
+ e := v.Index(i).Interface()
+ errs = validateStruct(errs, e)
+ }
+ } else {
+ errs = validateStruct(errs, obj)
+ }
+ return errs
+}
+
+// Validate is middleware to enforce required fields. If the struct
+// passed in implements Validator, then the user-defined Validate method
+// is executed, and its errors are mapped to the context. This middleware
+// performs no error handling: it merely detects errors and maps them.
+func Validate(req *http.Request, obj interface{}) Errors {
+ var errs Errors
+ v := reflect.ValueOf(obj)
+ k := v.Kind()
+ if k == reflect.Interface || k == reflect.Ptr {
+ v = v.Elem()
+ k = v.Kind()
+ }
+ if k == reflect.Slice || k == reflect.Array {
+ for i := 0; i < v.Len(); i++ {
+ e := v.Index(i).Interface()
+ errs = validateStruct(errs, e)
+ if validator, ok := e.(Validator); ok {
+ errs = validator.Validate(req, errs)
+ }
+ }
+ } else {
+ errs = validateStruct(errs, obj)
+ if validator, ok := obj.(Validator); ok {
+ errs = validator.Validate(req, errs)
+ }
+ }
+ return errs
+}
+
+var (
+ AlphaDashPattern = regexp.MustCompile("[^\\d\\w-_]")
+ AlphaDashDotPattern = regexp.MustCompile("[^\\d\\w-_\\.]")
+ EmailPattern = regexp.MustCompile("[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[a-zA-Z0-9](?:[\\w-]*[\\w])?")
+)
+
+// Copied from github.com/asaskevich/govalidator.
+const _MAX_URL_RUNE_COUNT = 2083
+const _MIN_URL_RUNE_COUNT = 3
+
+var (
+ urlSchemaRx = `((ftp|tcp|udp|wss?|https?):\/\/)`
+ urlUsernameRx = `(\S+(:\S*)?@)`
+ urlIPRx = `([1-9]\d?|1\d\d|2[01]\d|22[0-3])(\.(1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.([0-9]\d?|1\d\d|2[0-4]\d|25[0-4]))`
+ ipRx = `(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))`
+ urlSubdomainRx = `((www\.)|([a-zA-Z0-9]([-\.][-\._a-zA-Z0-9]+)*))`
+ urlPortRx = `(:(\d{1,5}))`
+ urlPathRx = `((\/|\?|#)[^\s]*)`
+ URLPattern = regexp.MustCompile(`^` + urlSchemaRx + `?` + urlUsernameRx + `?` + `((` + urlIPRx + `|(\[` + ipRx + `\])|(([a-zA-Z0-9]([a-zA-Z0-9-_]+)?[a-zA-Z0-9]([-\.][a-zA-Z0-9]+)*)|(` + urlSubdomainRx + `?))?(([a-zA-Z\x{00a1}-\x{ffff}0-9]+-?-?)*[a-zA-Z\x{00a1}-\x{ffff}0-9]+)(?:\.([a-zA-Z\x{00a1}-\x{ffff}]{1,}))?))\.?` + urlPortRx + `?` + urlPathRx + `?$`)
+)
+
+// IsURL check if the string is an URL.
+func isURL(str string) bool {
+ if str == "" || utf8.RuneCountInString(str) >= _MAX_URL_RUNE_COUNT || len(str) <= _MIN_URL_RUNE_COUNT || strings.HasPrefix(str, ".") {
+ return false
+ }
+ u, err := url.Parse(str)
+ if err != nil {
+ return false
+ }
+ if strings.HasPrefix(u.Host, ".") {
+ return false
+ }
+ if u.Host == "" && (u.Path != "" && !strings.Contains(u.Path, ".")) {
+ return false
+ }
+ return URLPattern.MatchString(str)
+
+}
+
+type (
+ // Rule represents a validation rule.
+ Rule struct {
+ // IsMatch checks if rule matches.
+ IsMatch func(string) bool
+ // IsValid applies validation rule to condition.
+ IsValid func(Errors, string, interface{}) (bool, Errors)
+ }
+
+ // ParamRule does same thing as Rule but passes rule itself to IsValid method.
+ ParamRule struct {
+ // IsMatch checks if rule matches.
+ IsMatch func(string) bool
+ // IsValid applies validation rule to condition.
+ IsValid func(Errors, string, string, interface{}) (bool, Errors)
+ }
+
+ // RuleMapper and ParamRuleMapper represent validation rule mappers,
+ // it allwos users to add custom validation rules.
+ RuleMapper []*Rule
+ ParamRuleMapper []*ParamRule
+)
+
+var ruleMapper RuleMapper
+var paramRuleMapper ParamRuleMapper
+
+// AddRule adds new validation rule.
+func AddRule(r *Rule) {
+ ruleMapper = append(ruleMapper, r)
+}
+
+// AddParamRule adds new validation rule.
+func AddParamRule(r *ParamRule) {
+ paramRuleMapper = append(paramRuleMapper, r)
+}
+
+func in(fieldValue interface{}, arr string) bool {
+ val := fmt.Sprintf("%v", fieldValue)
+ vals := strings.Split(arr, ",")
+ isIn := false
+ for _, v := range vals {
+ if v == val {
+ isIn = true
+ break
+ }
+ }
+ return isIn
+}
+
+func parseFormName(raw, actual string) string {
+ if len(actual) > 0 {
+ return actual
+ }
+ return nameMapper(raw)
+}
+
+// Performs required field checking on a struct
+func validateStruct(errors Errors, obj interface{}) Errors {
+ typ := reflect.TypeOf(obj)
+ val := reflect.ValueOf(obj)
+
+ if typ.Kind() == reflect.Ptr {
+ typ = typ.Elem()
+ val = val.Elem()
+ }
+
+ for i := 0; i < typ.NumField(); i++ {
+ field := typ.Field(i)
+
+ // Allow ignored fields in the struct
+ if field.Tag.Get("form") == "-" || !val.Field(i).CanInterface() {
+ continue
+ }
+
+ fieldVal := val.Field(i)
+ fieldValue := fieldVal.Interface()
+ zero := reflect.Zero(field.Type).Interface()
+
+ // Validate nested and embedded structs (if pointer, only do so if not nil)
+ if field.Type.Kind() == reflect.Struct ||
+ (field.Type.Kind() == reflect.Ptr && !reflect.DeepEqual(zero, fieldValue) &&
+ field.Type.Elem().Kind() == reflect.Struct) {
+ errors = validateStruct(errors, fieldValue)
+ }
+ errors = validateField(errors, zero, field, fieldVal, fieldValue)
+ }
+ return errors
+}
+
+// Don't pass in pointers to bind to. Can lead to bugs.
+func ensureNotPointer(obj interface{}) {
+ if reflect.TypeOf(obj).Kind() == reflect.Ptr {
+ panic("Pointers are not accepted as binding models")
+ }
+}
+
+func validateField(errors Errors, zero interface{}, field reflect.StructField, fieldVal reflect.Value, fieldValue interface{}) Errors {
+ if fieldVal.Kind() == reflect.Slice {
+ for i := 0; i < fieldVal.Len(); i++ {
+ sliceVal := fieldVal.Index(i)
+ if sliceVal.Kind() == reflect.Ptr {
+ sliceVal = sliceVal.Elem()
+ }
+
+ sliceValue := sliceVal.Interface()
+ zero := reflect.Zero(sliceVal.Type()).Interface()
+ if sliceVal.Kind() == reflect.Struct ||
+ (sliceVal.Kind() == reflect.Ptr && !reflect.DeepEqual(zero, sliceValue) &&
+ sliceVal.Elem().Kind() == reflect.Struct) {
+ errors = validateStruct(errors, sliceValue)
+ }
+ /* Apply validation rules to each item in a slice. ISSUE #3
+ else {
+ errors = validateField(errors, zero, field, sliceVal, sliceValue)
+ }*/
+ }
+ }
+
+VALIDATE_RULES:
+ for _, rule := range strings.Split(field.Tag.Get("binding"), ";") {
+ if len(rule) == 0 {
+ continue
+ }
+
+ switch {
+ case rule == "OmitEmpty":
+ if reflect.DeepEqual(zero, fieldValue) {
+ break VALIDATE_RULES
+ }
+ case rule == "Required":
+ v := reflect.ValueOf(fieldValue)
+ if v.Kind() == reflect.Slice {
+ if v.Len() == 0 {
+ errors.Add([]string{field.Name}, ERR_REQUIRED, "Required")
+ break VALIDATE_RULES
+ }
+
+ continue
+ }
+
+ if reflect.DeepEqual(zero, fieldValue) {
+ errors.Add([]string{field.Name}, ERR_REQUIRED, "Required")
+ break VALIDATE_RULES
+ }
+ case rule == "AlphaDash":
+ if AlphaDashPattern.MatchString(fmt.Sprintf("%v", fieldValue)) {
+ errors.Add([]string{field.Name}, ERR_ALPHA_DASH, "AlphaDash")
+ break VALIDATE_RULES
+ }
+ case rule == "AlphaDashDot":
+ if AlphaDashDotPattern.MatchString(fmt.Sprintf("%v", fieldValue)) {
+ errors.Add([]string{field.Name}, ERR_ALPHA_DASH_DOT, "AlphaDashDot")
+ break VALIDATE_RULES
+ }
+ case strings.HasPrefix(rule, "Size("):
+ size, _ := strconv.Atoi(rule[5 : len(rule)-1])
+ if str, ok := fieldValue.(string); ok && utf8.RuneCountInString(str) != size {
+ errors.Add([]string{field.Name}, ERR_SIZE, "Size")
+ break VALIDATE_RULES
+ }
+ v := reflect.ValueOf(fieldValue)
+ if v.Kind() == reflect.Slice && v.Len() != size {
+ errors.Add([]string{field.Name}, ERR_SIZE, "Size")
+ break VALIDATE_RULES
+ }
+ case strings.HasPrefix(rule, "MinSize("):
+ min, _ := strconv.Atoi(rule[8 : len(rule)-1])
+ if str, ok := fieldValue.(string); ok && utf8.RuneCountInString(str) < min {
+ errors.Add([]string{field.Name}, ERR_MIN_SIZE, "MinSize")
+ break VALIDATE_RULES
+ }
+ v := reflect.ValueOf(fieldValue)
+ if v.Kind() == reflect.Slice && v.Len() < min {
+ errors.Add([]string{field.Name}, ERR_MIN_SIZE, "MinSize")
+ break VALIDATE_RULES
+ }
+ case strings.HasPrefix(rule, "MaxSize("):
+ max, _ := strconv.Atoi(rule[8 : len(rule)-1])
+ if str, ok := fieldValue.(string); ok && utf8.RuneCountInString(str) > max {
+ errors.Add([]string{field.Name}, ERR_MAX_SIZE, "MaxSize")
+ break VALIDATE_RULES
+ }
+ v := reflect.ValueOf(fieldValue)
+ if v.Kind() == reflect.Slice && v.Len() > max {
+ errors.Add([]string{field.Name}, ERR_MAX_SIZE, "MaxSize")
+ break VALIDATE_RULES
+ }
+ case strings.HasPrefix(rule, "Range("):
+ nums := strings.Split(rule[6:len(rule)-1], ",")
+ if len(nums) != 2 {
+ break VALIDATE_RULES
+ }
+ val := com.StrTo(fmt.Sprintf("%v", fieldValue)).MustInt()
+ if val < com.StrTo(nums[0]).MustInt() || val > com.StrTo(nums[1]).MustInt() {
+ errors.Add([]string{field.Name}, ERR_RANGE, "Range")
+ break VALIDATE_RULES
+ }
+ case rule == "Email":
+ if !EmailPattern.MatchString(fmt.Sprintf("%v", fieldValue)) {
+ errors.Add([]string{field.Name}, ERR_EMAIL, "Email")
+ break VALIDATE_RULES
+ }
+ case rule == "Url":
+ str := fmt.Sprintf("%v", fieldValue)
+ if len(str) == 0 {
+ continue
+ } else if !isURL(str) {
+ errors.Add([]string{field.Name}, ERR_URL, "Url")
+ break VALIDATE_RULES
+ }
+ case strings.HasPrefix(rule, "In("):
+ if !in(fieldValue, rule[3:len(rule)-1]) {
+ errors.Add([]string{field.Name}, ERR_IN, "In")
+ break VALIDATE_RULES
+ }
+ case strings.HasPrefix(rule, "NotIn("):
+ if in(fieldValue, rule[6:len(rule)-1]) {
+ errors.Add([]string{field.Name}, ERR_NOT_INT, "NotIn")
+ break VALIDATE_RULES
+ }
+ case strings.HasPrefix(rule, "Include("):
+ if !strings.Contains(fmt.Sprintf("%v", fieldValue), rule[8:len(rule)-1]) {
+ errors.Add([]string{field.Name}, ERR_INCLUDE, "Include")
+ break VALIDATE_RULES
+ }
+ case strings.HasPrefix(rule, "Exclude("):
+ if strings.Contains(fmt.Sprintf("%v", fieldValue), rule[8:len(rule)-1]) {
+ errors.Add([]string{field.Name}, ERR_EXCLUDE, "Exclude")
+ break VALIDATE_RULES
+ }
+ case strings.HasPrefix(rule, "Default("):
+ if reflect.DeepEqual(zero, fieldValue) {
+ if fieldVal.CanAddr() {
+ errors = setWithProperType(field.Type.Kind(), rule[8:len(rule)-1], fieldVal, field.Tag.Get("form"), errors)
+ } else {
+ errors.Add([]string{field.Name}, ERR_EXCLUDE, "Default")
+ break VALIDATE_RULES
+ }
+ }
+ default:
+ // Apply custom validation rules
+ var isValid bool
+ for i := range ruleMapper {
+ if ruleMapper[i].IsMatch(rule) {
+ isValid, errors = ruleMapper[i].IsValid(errors, field.Name, fieldValue)
+ if !isValid {
+ break VALIDATE_RULES
+ }
+ }
+ }
+ for i := range paramRuleMapper {
+ if paramRuleMapper[i].IsMatch(rule) {
+ isValid, errors = paramRuleMapper[i].IsValid(errors, rule, field.Name, fieldValue)
+ if !isValid {
+ break VALIDATE_RULES
+ }
+ }
+ }
+ }
+ }
+ return errors
+}
+
+// NameMapper represents a form tag name mapper.
+type NameMapper func(string) string
+
+var (
+ nameMapper = func(field string) string {
+ newstr := make([]rune, 0, len(field))
+ for i, chr := range field {
+ if isUpper := 'A' <= chr && chr <= 'Z'; isUpper {
+ if i > 0 {
+ newstr = append(newstr, '_')
+ }
+ chr -= ('A' - 'a')
+ }
+ newstr = append(newstr, chr)
+ }
+ return string(newstr)
+ }
+)
+
+// SetNameMapper sets name mapper.
+func SetNameMapper(nm NameMapper) {
+ nameMapper = nm
+}
+
+// Takes values from the form data and puts them into a struct
+func mapForm(formStruct reflect.Value, form map[string][]string,
+ formfile map[string][]*multipart.FileHeader, errors Errors) Errors {
+
+ if formStruct.Kind() == reflect.Ptr {
+ formStruct = formStruct.Elem()
+ }
+ typ := formStruct.Type()
+
+ for i := 0; i < typ.NumField(); i++ {
+ typeField := typ.Field(i)
+ structField := formStruct.Field(i)
+
+ if typeField.Type.Kind() == reflect.Ptr && typeField.Anonymous {
+ structField.Set(reflect.New(typeField.Type.Elem()))
+ errors = mapForm(structField.Elem(), form, formfile, errors)
+ if reflect.DeepEqual(structField.Elem().Interface(), reflect.Zero(structField.Elem().Type()).Interface()) {
+ structField.Set(reflect.Zero(structField.Type()))
+ }
+ } else if typeField.Type.Kind() == reflect.Struct {
+ errors = mapForm(structField, form, formfile, errors)
+ }
+
+ inputFieldName := parseFormName(typeField.Name, typeField.Tag.Get("form"))
+ if len(inputFieldName) == 0 || !structField.CanSet() {
+ continue
+ }
+
+ inputValue, exists := form[inputFieldName]
+ if exists {
+ numElems := len(inputValue)
+ if structField.Kind() == reflect.Slice && numElems > 0 {
+ sliceOf := structField.Type().Elem().Kind()
+ slice := reflect.MakeSlice(structField.Type(), numElems, numElems)
+ for i := 0; i < numElems; i++ {
+ errors = setWithProperType(sliceOf, inputValue[i], slice.Index(i), inputFieldName, errors)
+ }
+ formStruct.Field(i).Set(slice)
+ } else {
+ errors = setWithProperType(typeField.Type.Kind(), inputValue[0], structField, inputFieldName, errors)
+ }
+ continue
+ }
+
+ inputFile, exists := formfile[inputFieldName]
+ if !exists {
+ continue
+ }
+ fhType := reflect.TypeOf((*multipart.FileHeader)(nil))
+ numElems := len(inputFile)
+ if structField.Kind() == reflect.Slice && numElems > 0 && structField.Type().Elem() == fhType {
+ slice := reflect.MakeSlice(structField.Type(), numElems, numElems)
+ for i := 0; i < numElems; i++ {
+ slice.Index(i).Set(reflect.ValueOf(inputFile[i]))
+ }
+ structField.Set(slice)
+ } else if structField.Type() == fhType {
+ structField.Set(reflect.ValueOf(inputFile[0]))
+ }
+ }
+ return errors
+}
+
+// This sets the value in a struct of an indeterminate type to the
+// matching value from the request (via Form middleware) in the
+// same type, so that not all deserialized values have to be strings.
+// Supported types are string, int, float, and bool.
+func setWithProperType(valueKind reflect.Kind, val string, structField reflect.Value, nameInTag string, errors Errors) Errors {
+ switch valueKind {
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ if val == "" {
+ val = "0"
+ }
+ intVal, err := strconv.ParseInt(val, 10, 64)
+ if err != nil {
+ errors.Add([]string{nameInTag}, ERR_INTERGER_TYPE, "Value could not be parsed as integer")
+ } else {
+ structField.SetInt(intVal)
+ }
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+ if val == "" {
+ val = "0"
+ }
+ uintVal, err := strconv.ParseUint(val, 10, 64)
+ if err != nil {
+ errors.Add([]string{nameInTag}, ERR_INTERGER_TYPE, "Value could not be parsed as unsigned integer")
+ } else {
+ structField.SetUint(uintVal)
+ }
+ case reflect.Bool:
+ if val == "on" {
+ structField.SetBool(true)
+ break
+ }
+
+ if val == "" {
+ val = "false"
+ }
+ boolVal, err := strconv.ParseBool(val)
+ if err != nil {
+ errors.Add([]string{nameInTag}, ERR_BOOLEAN_TYPE, "Value could not be parsed as boolean")
+ } else if boolVal {
+ structField.SetBool(true)
+ }
+ case reflect.Float32:
+ if val == "" {
+ val = "0.0"
+ }
+ floatVal, err := strconv.ParseFloat(val, 32)
+ if err != nil {
+ errors.Add([]string{nameInTag}, ERR_FLOAT_TYPE, "Value could not be parsed as 32-bit float")
+ } else {
+ structField.SetFloat(floatVal)
+ }
+ case reflect.Float64:
+ if val == "" {
+ val = "0.0"
+ }
+ floatVal, err := strconv.ParseFloat(val, 64)
+ if err != nil {
+ errors.Add([]string{nameInTag}, ERR_FLOAT_TYPE, "Value could not be parsed as 64-bit float")
+ } else {
+ structField.SetFloat(floatVal)
+ }
+ case reflect.String:
+ structField.SetString(val)
+ }
+ return errors
+}
+
+// Pointers must be bind to.
+func ensurePointer(obj interface{}) {
+ if reflect.TypeOf(obj).Kind() != reflect.Ptr {
+ panic("Pointers are only accepted as binding models")
+ }
+}
+
+type (
+ // ErrorHandler is the interface that has custom error handling process.
+ ErrorHandler interface {
+ // Error handles validation errors with custom process.
+ Error(*http.Request, Errors)
+ }
+
+ // Validator is the interface that handles some rudimentary
+ // request validation logic so your application doesn't have to.
+ Validator interface {
+ // Validate validates that the request is OK. It is recommended
+ // that validation be limited to checking values for syntax and
+ // semantics, enough to know that you can make sense of the request
+ // in your application. For example, you might verify that a credit
+ // card number matches a valid pattern, but you probably wouldn't
+ // perform an actual credit card authorization here.
+ Validate(*http.Request, Errors) Errors
+ }
+)
--- /dev/null
+// Copyright 2014 Martini Authors
+// Copyright 2014 The Macaron Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package binding
+
+const (
+ // Type mismatch errors.
+ ERR_CONTENT_TYPE = "ContentTypeError"
+ ERR_DESERIALIZATION = "DeserializationError"
+ ERR_INTERGER_TYPE = "IntegerTypeError"
+ ERR_BOOLEAN_TYPE = "BooleanTypeError"
+ ERR_FLOAT_TYPE = "FloatTypeError"
+
+ // Validation errors.
+ ERR_REQUIRED = "RequiredError"
+ ERR_ALPHA_DASH = "AlphaDashError"
+ ERR_ALPHA_DASH_DOT = "AlphaDashDotError"
+ ERR_SIZE = "SizeError"
+ ERR_MIN_SIZE = "MinSizeError"
+ ERR_MAX_SIZE = "MaxSizeError"
+ ERR_RANGE = "RangeError"
+ ERR_EMAIL = "EmailError"
+ ERR_URL = "UrlError"
+ ERR_IN = "InError"
+ ERR_NOT_INT = "NotInError"
+ ERR_INCLUDE = "IncludeError"
+ ERR_EXCLUDE = "ExcludeError"
+ ERR_DEFAULT = "DefaultError"
+)
+
+type (
+ // Errors may be generated during deserialization, binding,
+ // or validation. This type is mapped to the context so you
+ // can inject it into your own handlers and use it in your
+ // application if you want all your errors to look the same.
+ Errors []Error
+
+ Error struct {
+ // An error supports zero or more field names, because an
+ // error can morph three ways: (1) it can indicate something
+ // wrong with the request as a whole, (2) it can point to a
+ // specific problem with a particular input field, or (3) it
+ // can span multiple related input fields.
+ FieldNames []string `json:"fieldNames,omitempty"`
+
+ // The classification is like an error code, convenient to
+ // use when processing or categorizing an error programmatically.
+ // It may also be called the "kind" of error.
+ Classification string `json:"classification,omitempty"`
+
+ // Message should be human-readable and detailed enough to
+ // pinpoint and resolve the problem, but it should be brief. For
+ // example, a payload of 100 objects in a JSON array might have
+ // an error in the 41st object. The message should help the
+ // end user find and fix the error with their request.
+ Message string `json:"message,omitempty"`
+ }
+)
+
+// Add adds an error associated with the fields indicated
+// by fieldNames, with the given classification and message.
+func (e *Errors) Add(fieldNames []string, classification, message string) {
+ *e = append(*e, Error{
+ FieldNames: fieldNames,
+ Classification: classification,
+ Message: message,
+ })
+}
+
+// Len returns the number of errors.
+func (e *Errors) Len() int {
+ return len(*e)
+}
+
+// Has determines whether an Errors slice has an Error with
+// a given classification in it; it does not search on messages
+// or field names.
+func (e *Errors) Has(class string) bool {
+ for _, err := range *e {
+ if err.Kind() == class {
+ return true
+ }
+ }
+ return false
+}
+
+/*
+// WithClass gets a copy of errors that are classified by the
+// the given classification.
+func (e *Errors) WithClass(classification string) Errors {
+ var errs Errors
+ for _, err := range *e {
+ if err.Kind() == classification {
+ errs = append(errs, err)
+ }
+ }
+ return errs
+}
+
+// ForField gets a copy of errors that are associated with the
+// field by the given name.
+func (e *Errors) ForField(name string) Errors {
+ var errs Errors
+ for _, err := range *e {
+ for _, fieldName := range err.Fields() {
+ if fieldName == name {
+ errs = append(errs, err)
+ break
+ }
+ }
+ }
+ return errs
+}
+
+// Get gets errors of a particular class for the specified
+// field name.
+func (e *Errors) Get(class, fieldName string) Errors {
+ var errs Errors
+ for _, err := range *e {
+ if err.Kind() == class {
+ for _, nameOfField := range err.Fields() {
+ if nameOfField == fieldName {
+ errs = append(errs, err)
+ break
+ }
+ }
+ }
+ }
+ return errs
+}
+*/
+
+// Fields returns the list of field names this error is
+// associated with.
+func (e Error) Fields() []string {
+ return e.FieldNames
+}
+
+// Kind returns this error's classification.
+func (e Error) Kind() string {
+ return e.Classification
+}
+
+// Error returns this error's message.
+func (e Error) Error() string {
+ return e.Message
+}
--- /dev/null
+module gitea.com/go-chi/binding
+
+go 1.13
+
+require (
+ github.com/go-chi/chi v1.5.1
+ github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337
+ github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e
+)
--- /dev/null
+github.com/go-chi/chi v1.5.1 h1:kfTK3Cxd/dkMu/rKs5ZceWYp+t5CtiE7vmaTv3LjC6w=
+github.com/go-chi/chi v1.5.1/go.mod h1:REp24E+25iKvxgeTfHmdUoL5x15kBiDBlnIl5bCwe2k=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
+github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
+github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY=
+github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
+github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8=
+github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
+github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e h1:GSGeB9EAKY2spCABz6xOX5DbxZEXolK+nBSvmsQwRjM=
+github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
--- /dev/null
+kind: pipeline
+name: go1-1-1
+
+steps:
+- name: test
+ image: golang:1.11
+ environment:
+ GOPROXY: https://goproxy.cn
+ commands:
+ - go build -v
+ - go test -v -race -coverprofile=coverage.txt -covermode=atomic
+
+---
+kind: pipeline
+name: go1-1-2
+
+steps:
+- name: test
+ image: golang:1.12
+ environment:
+ GOPROXY: https://goproxy.cn
+ commands:
+ - go build -v
+ - go test -v -race -coverprofile=coverage.txt -covermode=atomic
\ No newline at end of file
--- /dev/null
+nodb/cache
+ledis/tmp.db/
+nodb/tmp.db/
+/vendor
+/.idea
--- /dev/null
+Apache License
+Version 2.0, January 2004
+http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+"License" shall mean the terms and conditions for use, reproduction, and
+distribution as defined by Sections 1 through 9 of this document.
+
+"Licensor" shall mean the copyright owner or entity authorized by the copyright
+owner that is granting the License.
+
+"Legal Entity" shall mean the union of the acting entity and all other entities
+that control, are controlled by, or are under common control with that entity.
+For the purposes of this definition, "control" means (i) the power, direct or
+indirect, to cause the direction or management of such entity, whether by
+contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
+outstanding shares, or (iii) beneficial ownership of such entity.
+
+"You" (or "Your") shall mean an individual or Legal Entity exercising
+permissions granted by this License.
+
+"Source" form shall mean the preferred form for making modifications, including
+but not limited to software source code, documentation source, and configuration
+files.
+
+"Object" form shall mean any form resulting from mechanical transformation or
+translation of a Source form, including but not limited to compiled object code,
+generated documentation, and conversions to other media types.
+
+"Work" shall mean the work of authorship, whether in Source or Object form, made
+available under the License, as indicated by a copyright notice that is included
+in or attached to the work (an example is provided in the Appendix below).
+
+"Derivative Works" shall mean any work, whether in Source or Object form, that
+is based on (or derived from) the Work and for which the editorial revisions,
+annotations, elaborations, or other modifications represent, as a whole, an
+original work of authorship. For the purposes of this License, Derivative Works
+shall not include works that remain separable from, or merely link (or bind by
+name) to the interfaces of, the Work and Derivative Works thereof.
+
+"Contribution" shall mean any work of authorship, including the original version
+of the Work and any modifications or additions to that Work or Derivative Works
+thereof, that is intentionally submitted to Licensor for inclusion in the Work
+by the copyright owner or by an individual or Legal Entity authorized to submit
+on behalf of the copyright owner. For the purposes of this definition,
+"submitted" means any form of electronic, verbal, or written communication sent
+to the Licensor or its representatives, including but not limited to
+communication on electronic mailing lists, source code control systems, and
+issue tracking systems that are managed by, or on behalf of, the Licensor for
+the purpose of discussing and improving the Work, but excluding communication
+that is conspicuously marked or otherwise designated in writing by the copyright
+owner as "Not a Contribution."
+
+"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
+of whom a Contribution has been received by Licensor and subsequently
+incorporated within the Work.
+
+2. Grant of Copyright License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable copyright license to reproduce, prepare Derivative Works of,
+publicly display, publicly perform, sublicense, and distribute the Work and such
+Derivative Works in Source or Object form.
+
+3. Grant of Patent License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable (except as stated in this section) patent license to make, have
+made, use, offer to sell, sell, import, and otherwise transfer the Work, where
+such license applies only to those patent claims licensable by such Contributor
+that are necessarily infringed by their Contribution(s) alone or by combination
+of their Contribution(s) with the Work to which such Contribution(s) was
+submitted. If You institute patent litigation against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that the Work or a
+Contribution incorporated within the Work constitutes direct or contributory
+patent infringement, then any patent licenses granted to You under this License
+for that Work shall terminate as of the date such litigation is filed.
+
+4. Redistribution.
+
+You may reproduce and distribute copies of the Work or Derivative Works thereof
+in any medium, with or without modifications, and in Source or Object form,
+provided that You meet the following conditions:
+
+You must give any other recipients of the Work or Derivative Works a copy of
+this License; and
+You must cause any modified files to carry prominent notices stating that You
+changed the files; and
+You must retain, in the Source form of any Derivative Works that You distribute,
+all copyright, patent, trademark, and attribution notices from the Source form
+of the Work, excluding those notices that do not pertain to any part of the
+Derivative Works; and
+If the Work includes a "NOTICE" text file as part of its distribution, then any
+Derivative Works that You distribute must include a readable copy of the
+attribution notices contained within such NOTICE file, excluding those notices
+that do not pertain to any part of the Derivative Works, in at least one of the
+following places: within a NOTICE text file distributed as part of the
+Derivative Works; within the Source form or documentation, if provided along
+with the Derivative Works; or, within a display generated by the Derivative
+Works, if and wherever such third-party notices normally appear. The contents of
+the NOTICE file are for informational purposes only and do not modify the
+License. You may add Your own attribution notices within Derivative Works that
+You distribute, alongside or as an addendum to the NOTICE text from the Work,
+provided that such additional attribution notices cannot be construed as
+modifying the License.
+You may add Your own copyright statement to Your modifications and may provide
+additional or different license terms and conditions for use, reproduction, or
+distribution of Your modifications, or for any such Derivative Works as a whole,
+provided Your use, reproduction, and distribution of the Work otherwise complies
+with the conditions stated in this License.
+
+5. Submission of Contributions.
+
+Unless You explicitly state otherwise, any Contribution intentionally submitted
+for inclusion in the Work by You to the Licensor shall be under the terms and
+conditions of this License, without any additional terms or conditions.
+Notwithstanding the above, nothing herein shall supersede or modify the terms of
+any separate license agreement you may have executed with Licensor regarding
+such Contributions.
+
+6. Trademarks.
+
+This License does not grant permission to use the trade names, trademarks,
+service marks, or product names of the Licensor, except as required for
+reasonable and customary use in describing the origin of the Work and
+reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty.
+
+Unless required by applicable law or agreed to in writing, Licensor provides the
+Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
+including, without limitation, any warranties or conditions of TITLE,
+NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
+solely responsible for determining the appropriateness of using or
+redistributing the Work and assume any risks associated with Your exercise of
+permissions under this License.
+
+8. Limitation of Liability.
+
+In no event and under no legal theory, whether in tort (including negligence),
+contract, or otherwise, unless required by applicable law (such as deliberate
+and grossly negligent acts) or agreed to in writing, shall any Contributor be
+liable to You for damages, including any direct, indirect, special, incidental,
+or consequential damages of any character arising as a result of this License or
+out of the use or inability to use the Work (including but not limited to
+damages for loss of goodwill, work stoppage, computer failure or malfunction, or
+any and all other commercial damages or losses), even if such Contributor has
+been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability.
+
+While redistributing the Work or Derivative Works thereof, You may choose to
+offer, and charge a fee for, acceptance of support, warranty, indemnity, or
+other liability obligations and/or rights consistent with this License. However,
+in accepting such obligations, You may act only on Your own behalf and on Your
+sole responsibility, not on behalf of any other Contributor, and only if You
+agree to indemnify, defend, and hold each Contributor harmless for any liability
+incurred by, or claims asserted against, such Contributor by reason of your
+accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work
+
+To apply the Apache License to your work, attach the following boilerplate
+notice, with the fields enclosed by brackets "[]" replaced with your own
+identifying information. (Don't include the brackets!) The text should be
+enclosed in the appropriate comment syntax for the file format. We also
+recommend that a file or class name and description of purpose be included on
+the same "printed page" as the copyright notice for easier identification within
+third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
\ No newline at end of file
--- /dev/null
+# cache
+
+Middleware cache provides cache management for [Macaron](https://github.com/go-macaron/macaron). It can use many cache adapters, including memory, file, Redis, Memcache, PostgreSQL, MySQL, Ledis and Nodb.
+
+### Installation
+
+ go get gitea.com/macaron/cache
+
+## Getting Help
+
+- [API Reference](https://gowalker.org/gitea.com/macaron/cache)
+- [Documentation](http://go-macaron.com/docs/middlewares/cache)
+
+## Credits
+
+This package is a modified version of [go-macaron/cache](github.com/go-macaron/cache).
+
+## License
+
+This project is under the Apache License, Version 2.0. See the [LICENSE](LICENSE) file for the full license text.
\ No newline at end of file
--- /dev/null
+// Copyright 2013 Beego Authors
+// Copyright 2014 The Macaron Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+// Package cache is a middleware that provides the cache management of Macaron.
+package cache
+
+import (
+ "fmt"
+)
+
+// Cache is the interface that operates the cache data.
+type Cache interface {
+ // Put puts value into cache with key and expire time.
+ Put(key string, val interface{}, timeout int64) error
+ // Get gets cached value by given key.
+ Get(key string) interface{}
+ // Delete deletes cached value by given key.
+ Delete(key string) error
+ // Incr increases cached int-type value by given key as a counter.
+ Incr(key string) error
+ // Decr decreases cached int-type value by given key as a counter.
+ Decr(key string) error
+ // IsExist returns true if cached value exists.
+ IsExist(key string) bool
+ // Flush deletes all cached data.
+ Flush() error
+ // StartAndGC starts GC routine based on config string settings.
+ StartAndGC(opt Options) error
+}
+
+// Options represents a struct for specifying configuration options for the cache middleware.
+type Options struct {
+ // Name of adapter. Default is "memory".
+ Adapter string
+ // Adapter configuration, it's corresponding to adapter.
+ AdapterConfig string
+ // GC interval time in seconds. Default is 60.
+ Interval int
+ // Occupy entire database. Default is false.
+ OccupyMode bool
+ // Configuration section name. Default is "cache".
+ Section string
+}
+
+func prepareOptions(opt Options) Options {
+ if len(opt.Section) == 0 {
+ opt.Section = "cache"
+ }
+ if len(opt.Adapter) == 0 {
+ opt.Adapter = "memory"
+ }
+ if opt.Interval == 0 {
+ opt.Interval = 60
+ }
+ if len(opt.AdapterConfig) == 0 {
+ opt.AdapterConfig = "data/caches"
+ }
+
+ return opt
+}
+
+// NewCacher creates and returns a new cacher by given adapter name and configuration.
+// It panics when given adapter isn't registered and starts GC automatically.
+func NewCacher(opt Options) (Cache, error) {
+ opt = prepareOptions(opt)
+ adapter, ok := adapters[opt.Adapter]
+ if !ok {
+ return nil, fmt.Errorf("cache: unknown adapter '%s'(forgot to import?)", opt.Adapter)
+ }
+ return adapter, adapter.StartAndGC(opt)
+}
+
+var adapters = make(map[string]Cache)
+
+// Register registers a adapter.
+func Register(name string, adapter Cache) {
+ if adapter == nil {
+ panic("cache: cannot register adapter with nil value")
+ }
+ if _, dup := adapters[name]; dup {
+ panic(fmt.Errorf("cache: cannot register adapter '%s' twice", name))
+ }
+ adapters[name] = adapter
+}
--- /dev/null
+// Copyright 2013 Beego Authors
+// Copyright 2014 The Macaron Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package cache
+
+import (
+ "crypto/md5"
+ "encoding/hex"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "os"
+ "path/filepath"
+ "sync"
+ "time"
+
+ "github.com/unknwon/com"
+)
+
+// Item represents a cache item.
+type Item struct {
+ Val interface{}
+ Created int64
+ Expire int64
+}
+
+func (item *Item) hasExpired() bool {
+ return item.Expire > 0 &&
+ (time.Now().Unix()-item.Created) >= item.Expire
+}
+
+// FileCacher represents a file cache adapter implementation.
+type FileCacher struct {
+ lock sync.Mutex
+ rootPath string
+ interval int // GC interval.
+}
+
+// NewFileCacher creates and returns a new file cacher.
+func NewFileCacher() *FileCacher {
+ return &FileCacher{}
+}
+
+func (c *FileCacher) filepath(key string) string {
+ m := md5.Sum([]byte(key))
+ hash := hex.EncodeToString(m[:])
+ return filepath.Join(c.rootPath, string(hash[0]), string(hash[1]), hash)
+}
+
+// Put puts value into cache with key and expire time.
+// If expired is 0, it will be deleted by next GC operation.
+func (c *FileCacher) Put(key string, val interface{}, expire int64) error {
+ filename := c.filepath(key)
+ item := &Item{val, time.Now().Unix(), expire}
+ data, err := EncodeGob(item)
+ if err != nil {
+ return err
+ }
+
+ os.MkdirAll(filepath.Dir(filename), os.ModePerm)
+ return ioutil.WriteFile(filename, data, os.ModePerm)
+}
+
+func (c *FileCacher) read(key string) (*Item, error) {
+ filename := c.filepath(key)
+
+ data, err := ioutil.ReadFile(filename)
+ if err != nil {
+ return nil, err
+ }
+
+ item := new(Item)
+ return item, DecodeGob(data, item)
+}
+
+// Get gets cached value by given key.
+func (c *FileCacher) Get(key string) interface{} {
+ item, err := c.read(key)
+ if err != nil {
+ return nil
+ }
+
+ if item.hasExpired() {
+ os.Remove(c.filepath(key))
+ return nil
+ }
+ return item.Val
+}
+
+// Delete deletes cached value by given key.
+func (c *FileCacher) Delete(key string) error {
+ return os.Remove(c.filepath(key))
+}
+
+// Incr increases cached int-type value by given key as a counter.
+func (c *FileCacher) Incr(key string) error {
+ item, err := c.read(key)
+ if err != nil {
+ return err
+ }
+
+ item.Val, err = Incr(item.Val)
+ if err != nil {
+ return err
+ }
+
+ return c.Put(key, item.Val, item.Expire)
+}
+
+// Decrease cached int value.
+func (c *FileCacher) Decr(key string) error {
+ item, err := c.read(key)
+ if err != nil {
+ return err
+ }
+
+ item.Val, err = Decr(item.Val)
+ if err != nil {
+ return err
+ }
+
+ return c.Put(key, item.Val, item.Expire)
+}
+
+// IsExist returns true if cached value exists.
+func (c *FileCacher) IsExist(key string) bool {
+ return com.IsExist(c.filepath(key))
+}
+
+// Flush deletes all cached data.
+func (c *FileCacher) Flush() error {
+ return os.RemoveAll(c.rootPath)
+}
+
+func (c *FileCacher) startGC() {
+ c.lock.Lock()
+ defer c.lock.Unlock()
+
+ if c.interval < 1 {
+ return
+ }
+
+ if err := filepath.Walk(c.rootPath, func(path string, fi os.FileInfo, err error) error {
+ if err != nil {
+ return fmt.Errorf("Walk: %v", err)
+ }
+
+ if fi.IsDir() {
+ return nil
+ }
+
+ data, err := ioutil.ReadFile(path)
+ if err != nil && !os.IsNotExist(err) {
+ fmt.Errorf("ReadFile: %v", err)
+ }
+
+ item := new(Item)
+ if err = DecodeGob(data, item); err != nil {
+ return err
+ }
+ if item.hasExpired() {
+ if err = os.Remove(path); err != nil && !os.IsNotExist(err) {
+ return fmt.Errorf("Remove: %v", err)
+ }
+ }
+ return nil
+ }); err != nil {
+ log.Printf("error garbage collecting cache files: %v", err)
+ }
+
+ time.AfterFunc(time.Duration(c.interval)*time.Second, func() { c.startGC() })
+}
+
+// StartAndGC starts GC routine based on config string settings.
+func (c *FileCacher) StartAndGC(opt Options) error {
+ c.lock.Lock()
+ c.rootPath = opt.AdapterConfig
+ c.interval = opt.Interval
+
+ if !filepath.IsAbs(c.rootPath) {
+ panic("rootPath must be an absolute path")
+ }
+ c.lock.Unlock()
+
+ if err := os.MkdirAll(c.rootPath, os.ModePerm); err != nil {
+ return err
+ }
+
+ go c.startGC()
+ return nil
+}
+
+func init() {
+ Register("file", NewFileCacher())
+}
--- /dev/null
+module gitea.com/go-chi/cache
+
+go 1.11
+
+require (
+ github.com/BurntSushi/toml v0.3.1 // indirect
+ github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668
+ github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76 // indirect
+ github.com/edsrzf/mmap-go v1.0.0 // indirect
+ github.com/go-redis/redis v6.15.2+incompatible
+ github.com/go-sql-driver/mysql v1.4.1
+ github.com/golang/snappy v0.0.2 // indirect
+ github.com/lib/pq v1.2.0
+ github.com/lunny/log v0.0.0-20160921050905-7887c61bf0de // indirect
+ github.com/lunny/nodb v0.0.0-20160621015157-fc1ef06ad4af
+ github.com/mattn/go-sqlite3 v1.11.0 // indirect
+ github.com/onsi/ginkgo v1.8.0 // indirect
+ github.com/onsi/gomega v1.5.0 // indirect
+ github.com/pelletier/go-toml v1.8.1 // indirect
+ github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726 // indirect
+ github.com/siddontang/go-snappy v0.0.0-20140704025258-d8f7bb82a96d // indirect
+ github.com/siddontang/ledisdb v0.0.0-20190202134119-8ceb77e66a92
+ github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d // indirect
+ github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337
+ github.com/syndtr/goleveldb v1.0.0 // indirect
+ github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e
+ golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 // indirect
+ golang.org/x/sys v0.0.0-20190730183949-1393eb018365 // indirect
+ google.golang.org/appengine v1.6.1 // indirect
+ gopkg.in/ini.v1 v1.44.2
+ gopkg.in/yaml.v2 v2.2.2 // indirect
+)
--- /dev/null
+github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668 h1:U/lr3Dgy4WK+hNk4tyD+nuGjpVLPEHuJSFXMw11/HPA=
+github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
+github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76 h1:Lgdd/Qp96Qj8jqLpq2cI1I1X7BJnu06efS+XkhRoLUQ=
+github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw=
+github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
+github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/go-redis/redis v6.15.2+incompatible h1:9SpNVG76gr6InJGxoZ6IuuxaCOQwDAhzyXg+Bs+0Sb4=
+github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
+github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
+github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.2 h1:aeE13tS0IiQgFjYdoL8qN3K1N2bXXtI6Vi51/y7BpMw=
+github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
+github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
+github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
+github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lunny/log v0.0.0-20160921050905-7887c61bf0de h1:nyxwRdWHAVxpFcDThedEgQ07DbcRc5xgNObtbTp76fk=
+github.com/lunny/log v0.0.0-20160921050905-7887c61bf0de/go.mod h1:3q8WtuPQsoRbatJuy3nvq/hRSvuBJrHHr+ybPPiNvHQ=
+github.com/lunny/nodb v0.0.0-20160621015157-fc1ef06ad4af h1:UaWHNBdukWrSG3DRvHFR/hyfg681fceqQDYVTBncKfQ=
+github.com/lunny/nodb v0.0.0-20160621015157-fc1ef06ad4af/go.mod h1:Cqz6pqow14VObJ7peltM+2n3PWOz7yTrfUuGbVFkzN0=
+github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q=
+github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w=
+github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo=
+github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM=
+github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
+github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726 h1:xT+JlYxNGqyT+XcU8iUrN18JYed2TvG9yN5ULG2jATM=
+github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
+github.com/siddontang/go-snappy v0.0.0-20140704025258-d8f7bb82a96d h1:qQWKKOvHN7Q9c6GdmUteCef2F9ubxMpxY1IKwpIKz68=
+github.com/siddontang/go-snappy v0.0.0-20140704025258-d8f7bb82a96d/go.mod h1:vq0tzqLRu6TS7Id0wMo2N5QzJoKedVeovOpHjnykSzY=
+github.com/siddontang/ledisdb v0.0.0-20190202134119-8ceb77e66a92 h1:qvsJwGToa8rxb42cDRhkbKeX2H5N8BH+s2aUikGt8mI=
+github.com/siddontang/ledisdb v0.0.0-20190202134119-8ceb77e66a92/go.mod h1:mF1DpOSOUiJRMR+FDqaqu3EBqrybQtrDDszLUZ6oxPg=
+github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d h1:NVwnfyR3rENtlz62bcrkXME3INVUa4lcdGt+opvxExs=
+github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY=
+github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
+github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8=
+github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
+github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
+github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
+github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e h1:GSGeB9EAKY2spCABz6xOX5DbxZEXolK+nBSvmsQwRjM=
+github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk=
+golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190730183949-1393eb018365 h1:SaXEMXhWzMJThc05vu6uh61Q245r4KaWMrsTedk0FDc=
+golang.org/x/sys v0.0.0-20190730183949-1393eb018365/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I=
+google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/ini.v1 v1.44.2 h1:N6kNUPqiIyxP+s/aINPzRvNpcTVV30qLC0t6ZjZFlUU=
+gopkg.in/ini.v1 v1.44.2/go.mod h1:M3Cogqpuv0QCi3ExAY5V4uOt4qb/R3xZubo9m8lK5wg=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
--- /dev/null
+// Copyright 2013 Beego Authors
+// Copyright 2014 The Macaron Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package cache
+
+import (
+ "strings"
+
+ "github.com/bradfitz/gomemcache/memcache"
+ "github.com/unknwon/com"
+
+ "gitea.com/go-chi/cache"
+)
+
+// MemcacheCacher represents a memcache cache adapter implementation.
+type MemcacheCacher struct {
+ c *memcache.Client
+}
+
+func NewItem(key string, data []byte, expire int32) *memcache.Item {
+ return &memcache.Item{
+ Key: key,
+ Value: data,
+ Expiration: expire,
+ }
+}
+
+// Put puts value into cache with key and expire time.
+// If expired is 0, it lives forever.
+func (c *MemcacheCacher) Put(key string, val interface{}, expire int64) error {
+ return c.c.Set(NewItem(key, []byte(com.ToStr(val)), int32(expire)))
+}
+
+// Get gets cached value by given key.
+func (c *MemcacheCacher) Get(key string) interface{} {
+ item, err := c.c.Get(key)
+ if err != nil {
+ return nil
+ }
+ return string(item.Value)
+}
+
+// Delete deletes cached value by given key.
+func (c *MemcacheCacher) Delete(key string) error {
+ return c.c.Delete(key)
+}
+
+// Incr increases cached int-type value by given key as a counter.
+func (c *MemcacheCacher) Incr(key string) error {
+ _, err := c.c.Increment(key, 1)
+ return err
+}
+
+// Decr decreases cached int-type value by given key as a counter.
+func (c *MemcacheCacher) Decr(key string) error {
+ _, err := c.c.Decrement(key, 1)
+ return err
+}
+
+// IsExist returns true if cached value exists.
+func (c *MemcacheCacher) IsExist(key string) bool {
+ _, err := c.c.Get(key)
+ return err == nil
+}
+
+// Flush deletes all cached data.
+func (c *MemcacheCacher) Flush() error {
+ return c.c.FlushAll()
+}
+
+// StartAndGC starts GC routine based on config string settings.
+// AdapterConfig: 127.0.0.1:9090;127.0.0.1:9091
+func (c *MemcacheCacher) StartAndGC(opt cache.Options) error {
+ c.c = memcache.New(strings.Split(opt.AdapterConfig, ";")...)
+ return nil
+}
+
+func init() {
+ cache.Register("memcache", &MemcacheCacher{})
+}
--- /dev/null
+ignore
\ No newline at end of file
--- /dev/null
+// Copyright 2013 Beego Authors
+// Copyright 2014 The Macaron Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package cache
+
+import (
+ "errors"
+ "sync"
+ "time"
+)
+
+// MemoryItem represents a memory cache item.
+type MemoryItem struct {
+ val interface{}
+ created int64
+ expire int64
+}
+
+func (item *MemoryItem) hasExpired() bool {
+ return item.expire > 0 &&
+ (time.Now().Unix()-item.created) >= item.expire
+}
+
+// MemoryCacher represents a memory cache adapter implementation.
+type MemoryCacher struct {
+ lock sync.RWMutex
+ items map[string]*MemoryItem
+ interval int // GC interval.
+}
+
+// NewMemoryCacher creates and returns a new memory cacher.
+func NewMemoryCacher() *MemoryCacher {
+ return &MemoryCacher{items: make(map[string]*MemoryItem)}
+}
+
+// Put puts value into cache with key and expire time.
+// If expired is 0, it will be deleted by next GC operation.
+func (c *MemoryCacher) Put(key string, val interface{}, expire int64) error {
+ c.lock.Lock()
+ defer c.lock.Unlock()
+
+ c.items[key] = &MemoryItem{
+ val: val,
+ created: time.Now().Unix(),
+ expire: expire,
+ }
+ return nil
+}
+
+// Get gets cached value by given key.
+func (c *MemoryCacher) Get(key string) interface{} {
+ c.lock.RLock()
+ defer c.lock.RUnlock()
+
+ item, ok := c.items[key]
+ if !ok {
+ return nil
+ }
+ if item.hasExpired() {
+ go c.Delete(key)
+ return nil
+ }
+ return item.val
+}
+
+// Delete deletes cached value by given key.
+func (c *MemoryCacher) Delete(key string) error {
+ c.lock.Lock()
+ defer c.lock.Unlock()
+
+ delete(c.items, key)
+ return nil
+}
+
+// Incr increases cached int-type value by given key as a counter.
+func (c *MemoryCacher) Incr(key string) (err error) {
+ c.lock.RLock()
+ defer c.lock.RUnlock()
+
+ item, ok := c.items[key]
+ if !ok {
+ return errors.New("key not exist")
+ }
+ item.val, err = Incr(item.val)
+ return err
+}
+
+// Decr decreases cached int-type value by given key as a counter.
+func (c *MemoryCacher) Decr(key string) (err error) {
+ c.lock.RLock()
+ defer c.lock.RUnlock()
+
+ item, ok := c.items[key]
+ if !ok {
+ return errors.New("key not exist")
+ }
+
+ item.val, err = Decr(item.val)
+ return err
+}
+
+// IsExist returns true if cached value exists.
+func (c *MemoryCacher) IsExist(key string) bool {
+ c.lock.RLock()
+ defer c.lock.RUnlock()
+
+ _, ok := c.items[key]
+ return ok
+}
+
+// Flush deletes all cached data.
+func (c *MemoryCacher) Flush() error {
+ c.lock.Lock()
+ defer c.lock.Unlock()
+
+ c.items = make(map[string]*MemoryItem)
+ return nil
+}
+
+func (c *MemoryCacher) checkRawExpiration(key string) {
+ item, ok := c.items[key]
+ if !ok {
+ return
+ }
+
+ if item.hasExpired() {
+ delete(c.items, key)
+ }
+}
+
+func (c *MemoryCacher) checkExpiration(key string) {
+ c.lock.Lock()
+ defer c.lock.Unlock()
+
+ c.checkRawExpiration(key)
+}
+
+func (c *MemoryCacher) startGC() {
+ c.lock.Lock()
+ defer c.lock.Unlock()
+
+ if c.interval < 1 {
+ return
+ }
+
+ if c.items != nil {
+ for key, _ := range c.items {
+ c.checkRawExpiration(key)
+ }
+ }
+
+ time.AfterFunc(time.Duration(c.interval)*time.Second, func() { c.startGC() })
+}
+
+// StartAndGC starts GC routine based on config string settings.
+func (c *MemoryCacher) StartAndGC(opt Options) error {
+ c.lock.Lock()
+ c.interval = opt.Interval
+ c.lock.Unlock()
+
+ go c.startGC()
+ return nil
+}
+
+func init() {
+ Register("memory", NewMemoryCacher())
+}
--- /dev/null
+// Copyright 2014 The Macaron Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package cache
+
+import (
+ "bytes"
+ "encoding/gob"
+ "errors"
+)
+
+func EncodeGob(item *Item) ([]byte, error) {
+ buf := bytes.NewBuffer(nil)
+ err := gob.NewEncoder(buf).Encode(item)
+ return buf.Bytes(), err
+}
+
+func DecodeGob(data []byte, out *Item) error {
+ buf := bytes.NewBuffer(data)
+ return gob.NewDecoder(buf).Decode(&out)
+}
+
+func Incr(val interface{}) (interface{}, error) {
+ switch val.(type) {
+ case int:
+ val = val.(int) + 1
+ case int32:
+ val = val.(int32) + 1
+ case int64:
+ val = val.(int64) + 1
+ case uint:
+ val = val.(uint) + 1
+ case uint32:
+ val = val.(uint32) + 1
+ case uint64:
+ val = val.(uint64) + 1
+ default:
+ return val, errors.New("item value is not int-type")
+ }
+ return val, nil
+}
+
+func Decr(val interface{}) (interface{}, error) {
+ switch val.(type) {
+ case int:
+ val = val.(int) - 1
+ case int32:
+ val = val.(int32) - 1
+ case int64:
+ val = val.(int64) - 1
+ case uint:
+ if val.(uint) > 0 {
+ val = val.(uint) - 1
+ } else {
+ return val, errors.New("item value is less than 0")
+ }
+ case uint32:
+ if val.(uint32) > 0 {
+ val = val.(uint32) - 1
+ } else {
+ return val, errors.New("item value is less than 0")
+ }
+ case uint64:
+ if val.(uint64) > 0 {
+ val = val.(uint64) - 1
+ } else {
+ return val, errors.New("item value is less than 0")
+ }
+ default:
+ return val, errors.New("item value is not int-type")
+ }
+ return val, nil
+}
--- /dev/null
+kind: pipeline
+name: go1-1-1
+
+steps:
+- name: test
+ image: golang:1.11
+ environment:
+ GOPROXY: https://goproxy.cn
+ commands:
+ - go build -v
+ - go test -v -race -coverprofile=coverage.txt -covermode=atomic
+
+---
+kind: pipeline
+name: go1-1-2
+
+steps:
+- name: test
+ image: golang:1.12
+ environment:
+ GOPROXY: https://goproxy.cn
+ commands:
+ - go build -v
+ - go test -v -race -coverprofile=coverage.txt -covermode=atomic
\ No newline at end of file
--- /dev/null
+Apache License
+Version 2.0, January 2004
+http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+"License" shall mean the terms and conditions for use, reproduction, and
+distribution as defined by Sections 1 through 9 of this document.
+
+"Licensor" shall mean the copyright owner or entity authorized by the copyright
+owner that is granting the License.
+
+"Legal Entity" shall mean the union of the acting entity and all other entities
+that control, are controlled by, or are under common control with that entity.
+For the purposes of this definition, "control" means (i) the power, direct or
+indirect, to cause the direction or management of such entity, whether by
+contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
+outstanding shares, or (iii) beneficial ownership of such entity.
+
+"You" (or "Your") shall mean an individual or Legal Entity exercising
+permissions granted by this License.
+
+"Source" form shall mean the preferred form for making modifications, including
+but not limited to software source code, documentation source, and configuration
+files.
+
+"Object" form shall mean any form resulting from mechanical transformation or
+translation of a Source form, including but not limited to compiled object code,
+generated documentation, and conversions to other media types.
+
+"Work" shall mean the work of authorship, whether in Source or Object form, made
+available under the License, as indicated by a copyright notice that is included
+in or attached to the work (an example is provided in the Appendix below).
+
+"Derivative Works" shall mean any work, whether in Source or Object form, that
+is based on (or derived from) the Work and for which the editorial revisions,
+annotations, elaborations, or other modifications represent, as a whole, an
+original work of authorship. For the purposes of this License, Derivative Works
+shall not include works that remain separable from, or merely link (or bind by
+name) to the interfaces of, the Work and Derivative Works thereof.
+
+"Contribution" shall mean any work of authorship, including the original version
+of the Work and any modifications or additions to that Work or Derivative Works
+thereof, that is intentionally submitted to Licensor for inclusion in the Work
+by the copyright owner or by an individual or Legal Entity authorized to submit
+on behalf of the copyright owner. For the purposes of this definition,
+"submitted" means any form of electronic, verbal, or written communication sent
+to the Licensor or its representatives, including but not limited to
+communication on electronic mailing lists, source code control systems, and
+issue tracking systems that are managed by, or on behalf of, the Licensor for
+the purpose of discussing and improving the Work, but excluding communication
+that is conspicuously marked or otherwise designated in writing by the copyright
+owner as "Not a Contribution."
+
+"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
+of whom a Contribution has been received by Licensor and subsequently
+incorporated within the Work.
+
+2. Grant of Copyright License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable copyright license to reproduce, prepare Derivative Works of,
+publicly display, publicly perform, sublicense, and distribute the Work and such
+Derivative Works in Source or Object form.
+
+3. Grant of Patent License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable (except as stated in this section) patent license to make, have
+made, use, offer to sell, sell, import, and otherwise transfer the Work, where
+such license applies only to those patent claims licensable by such Contributor
+that are necessarily infringed by their Contribution(s) alone or by combination
+of their Contribution(s) with the Work to which such Contribution(s) was
+submitted. If You institute patent litigation against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that the Work or a
+Contribution incorporated within the Work constitutes direct or contributory
+patent infringement, then any patent licenses granted to You under this License
+for that Work shall terminate as of the date such litigation is filed.
+
+4. Redistribution.
+
+You may reproduce and distribute copies of the Work or Derivative Works thereof
+in any medium, with or without modifications, and in Source or Object form,
+provided that You meet the following conditions:
+
+You must give any other recipients of the Work or Derivative Works a copy of
+this License; and
+You must cause any modified files to carry prominent notices stating that You
+changed the files; and
+You must retain, in the Source form of any Derivative Works that You distribute,
+all copyright, patent, trademark, and attribution notices from the Source form
+of the Work, excluding those notices that do not pertain to any part of the
+Derivative Works; and
+If the Work includes a "NOTICE" text file as part of its distribution, then any
+Derivative Works that You distribute must include a readable copy of the
+attribution notices contained within such NOTICE file, excluding those notices
+that do not pertain to any part of the Derivative Works, in at least one of the
+following places: within a NOTICE text file distributed as part of the
+Derivative Works; within the Source form or documentation, if provided along
+with the Derivative Works; or, within a display generated by the Derivative
+Works, if and wherever such third-party notices normally appear. The contents of
+the NOTICE file are for informational purposes only and do not modify the
+License. You may add Your own attribution notices within Derivative Works that
+You distribute, alongside or as an addendum to the NOTICE text from the Work,
+provided that such additional attribution notices cannot be construed as
+modifying the License.
+You may add Your own copyright statement to Your modifications and may provide
+additional or different license terms and conditions for use, reproduction, or
+distribution of Your modifications, or for any such Derivative Works as a whole,
+provided Your use, reproduction, and distribution of the Work otherwise complies
+with the conditions stated in this License.
+
+5. Submission of Contributions.
+
+Unless You explicitly state otherwise, any Contribution intentionally submitted
+for inclusion in the Work by You to the Licensor shall be under the terms and
+conditions of this License, without any additional terms or conditions.
+Notwithstanding the above, nothing herein shall supersede or modify the terms of
+any separate license agreement you may have executed with Licensor regarding
+such Contributions.
+
+6. Trademarks.
+
+This License does not grant permission to use the trade names, trademarks,
+service marks, or product names of the Licensor, except as required for
+reasonable and customary use in describing the origin of the Work and
+reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty.
+
+Unless required by applicable law or agreed to in writing, Licensor provides the
+Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
+including, without limitation, any warranties or conditions of TITLE,
+NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
+solely responsible for determining the appropriateness of using or
+redistributing the Work and assume any risks associated with Your exercise of
+permissions under this License.
+
+8. Limitation of Liability.
+
+In no event and under no legal theory, whether in tort (including negligence),
+contract, or otherwise, unless required by applicable law (such as deliberate
+and grossly negligent acts) or agreed to in writing, shall any Contributor be
+liable to You for damages, including any direct, indirect, special, incidental,
+or consequential damages of any character arising as a result of this License or
+out of the use or inability to use the Work (including but not limited to
+damages for loss of goodwill, work stoppage, computer failure or malfunction, or
+any and all other commercial damages or losses), even if such Contributor has
+been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability.
+
+While redistributing the Work or Derivative Works thereof, You may choose to
+offer, and charge a fee for, acceptance of support, warranty, indemnity, or
+other liability obligations and/or rights consistent with this License. However,
+in accepting such obligations, You may act only on Your own behalf and on Your
+sole responsibility, not on behalf of any other Contributor, and only if You
+agree to indemnify, defend, and hold each Contributor harmless for any liability
+incurred by, or claims asserted against, such Contributor by reason of your
+accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work
+
+To apply the Apache License to your work, attach the following boilerplate
+notice, with the fields enclosed by brackets "[]" replaced with your own
+identifying information. (Don't include the brackets!) The text should be
+enclosed in the appropriate comment syntax for the file format. We also
+recommend that a file or class name and description of purpose be included on
+the same "printed page" as the copyright notice for easier identification within
+third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
\ No newline at end of file
--- /dev/null
+# captcha [![Build Status](https://drone.gitea.com/api/badges/macaron/captcha/status.svg)](https://drone.gitea.com/macaron/captcha)
+
+Middleware captcha provides captcha service for [Macaron](https://gitea.com/macaron/macaron).
+
+### Installation
+
+ go get gitea.com/macaron/captcha
+
+## Getting Help
+
+- [API Reference](https://gowalker.org/gitea.com/macaron/captcha)
+- [Documentation](http://go-macaron.com/docs/middlewares/captcha)
+
+## License
+
+This project is under the Apache License, Version 2.0. See the [LICENSE](LICENSE) file for the full license text.
\ No newline at end of file
--- /dev/null
+// Copyright 2013 Beego Authors
+// Copyright 2014 The Macaron Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+// Package captcha a middleware that provides captcha service for Macaron.
+package captcha
+
+import (
+ "fmt"
+ "html/template"
+ "image/color"
+ "net/http"
+ "path"
+ "strings"
+
+ "gitea.com/go-chi/cache"
+ "github.com/unknwon/com"
+)
+
+const _VERSION = "0.1.0"
+
+func Version() string {
+ return _VERSION
+}
+
+var (
+ defaultChars = []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
+)
+
+// Captcha represents a captcha service.
+type Captcha struct {
+ Store cache.Cache
+ SubURL string
+ URLPrefix string
+ FieldIdName string
+ FieldCaptchaName string
+ StdWidth int
+ StdHeight int
+ ChallengeNums int
+ Expiration int64
+ CachePrefix string
+ ColorPalette color.Palette
+}
+
+// generate key string
+func (c *Captcha) key(id string) string {
+ return c.CachePrefix + id
+}
+
+// generate rand chars with default chars
+func (c *Captcha) genRandChars() string {
+ return string(com.RandomCreateBytes(c.ChallengeNums, defaultChars...))
+}
+
+// CreateHTML outputs HTML for display and fetch new captcha images.
+func (c *Captcha) CreateHTML() template.HTML {
+ value, err := c.CreateCaptcha()
+ if err != nil {
+ panic(fmt.Errorf("fail to create captcha: %v", err))
+ }
+ return template.HTML(fmt.Sprintf(`<input type="hidden" name="%[1]s" value="%[2]s">
+ <a class="captcha" href="javascript:" tabindex="-1">
+ <img onclick="this.src=('%[3]s%[4]s%[2]s.png?reload='+(new Date()).getTime())" class="captcha-img" src="%[3]s%[4]s%[2]s.png">
+ </a>`, c.FieldIdName, value, c.SubURL, c.URLPrefix))
+}
+
+// create a new captcha id
+func (c *Captcha) CreateCaptcha() (string, error) {
+ id := string(com.RandomCreateBytes(15))
+ if err := c.Store.Put(c.key(id), c.genRandChars(), c.Expiration); err != nil {
+ return "", err
+ }
+ return id, nil
+}
+
+// verify from a request
+func (c *Captcha) VerifyReq(req *http.Request) bool {
+ req.ParseForm()
+ return c.Verify(req.Form.Get(c.FieldIdName), req.Form.Get(c.FieldCaptchaName))
+}
+
+// direct verify id and challenge string
+func (c *Captcha) Verify(id string, challenge string) bool {
+ if len(challenge) == 0 || len(id) == 0 {
+ return false
+ }
+
+ var chars string
+
+ key := c.key(id)
+
+ if v, ok := c.Store.Get(key).(string); ok {
+ chars = v
+ } else {
+ return false
+ }
+
+ defer c.Store.Delete(key)
+
+ if len(chars) != len(challenge) {
+ return false
+ }
+
+ // verify challenge
+ for i, c := range []byte(chars) {
+ if c != challenge[i]-48 {
+ return false
+ }
+ }
+
+ return true
+}
+
+type Options struct {
+ // Suburl path. Default is empty.
+ SubURL string
+ // URL prefix of getting captcha pictures. Default is "/captcha/".
+ URLPrefix string
+ // Hidden input element ID. Default is "captcha_id".
+ FieldIdName string
+ // User input value element name in request form. Default is "captcha".
+ FieldCaptchaName string
+ // Challenge number. Default is 6.
+ ChallengeNums int
+ // Captcha image width. Default is 240.
+ Width int
+ // Captcha image height. Default is 80.
+ Height int
+ // Captcha expiration time in seconds. Default is 600.
+ Expiration int64
+ // Cache key prefix captcha characters. Default is "captcha_".
+ CachePrefix string
+ // ColorPalette holds a collection of primary colors used for
+ // the captcha's text. If not defined, a random color will be generated.
+ ColorPalette color.Palette
+}
+
+func prepareOptions(options []Options) Options {
+ var opt Options
+ if len(options) > 0 {
+ opt = options[0]
+ }
+
+ opt.SubURL = strings.TrimSuffix(opt.SubURL, "/")
+
+ // Defaults.
+ if len(opt.URLPrefix) == 0 {
+ opt.URLPrefix = "/captcha/"
+ } else if opt.URLPrefix[len(opt.URLPrefix)-1] != '/' {
+ opt.URLPrefix += "/"
+ }
+ if len(opt.FieldIdName) == 0 {
+ opt.FieldIdName = "captcha_id"
+ }
+ if len(opt.FieldCaptchaName) == 0 {
+ opt.FieldCaptchaName = "captcha"
+ }
+ if opt.ChallengeNums == 0 {
+ opt.ChallengeNums = 6
+ }
+ if opt.Width == 0 {
+ opt.Width = stdWidth
+ }
+ if opt.Height == 0 {
+ opt.Height = stdHeight
+ }
+ if opt.Expiration == 0 {
+ opt.Expiration = 600
+ }
+ if len(opt.CachePrefix) == 0 {
+ opt.CachePrefix = "captcha_"
+ }
+
+ return opt
+}
+
+// NewCaptcha initializes and returns a captcha with given options.
+func NewCaptcha(opts ...Options) *Captcha {
+ opt := prepareOptions(opts)
+ return &Captcha{
+ SubURL: opt.SubURL,
+ URLPrefix: opt.URLPrefix,
+ FieldIdName: opt.FieldIdName,
+ FieldCaptchaName: opt.FieldCaptchaName,
+ StdWidth: opt.Width,
+ StdHeight: opt.Height,
+ ChallengeNums: opt.ChallengeNums,
+ Expiration: opt.Expiration,
+ CachePrefix: opt.CachePrefix,
+ ColorPalette: opt.ColorPalette,
+ }
+}
+
+// Captchaer is a middleware that maps a captcha.Captcha service into the Macaron handler chain.
+// An single variadic captcha.Options struct can be optionally provided to configure.
+// This should be register after cache.Cacher.
+func Captchaer(cpt *Captcha) func(http.Handler) http.Handler {
+ return func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ if strings.HasPrefix(req.URL.Path, cpt.URLPrefix) {
+ var chars string
+ id := path.Base(req.URL.Path)
+ if i := strings.Index(id, "."); i > -1 {
+ id = id[:i]
+ }
+ key := cpt.key(id)
+
+ reloads := req.URL.Query()["reload"]
+ // Reload captcha.
+ if len(reloads) > 0 && len(reloads[0]) > 0 {
+ chars = cpt.genRandChars()
+ if err := cpt.Store.Put(key, chars, cpt.Expiration); err != nil {
+ w.WriteHeader(500)
+ w.Write([]byte("captcha reload error"))
+ panic(fmt.Errorf("reload captcha: %v", err))
+ }
+ } else {
+ if v, ok := cpt.Store.Get(key).(string); ok {
+ chars = v
+ } else {
+ w.WriteHeader(404)
+ w.Write([]byte("captcha not found"))
+ return
+ }
+ }
+
+ w.WriteHeader(200)
+ if _, err := NewImage([]byte(chars), cpt.StdWidth, cpt.StdHeight, cpt.ColorPalette).WriteTo(w); err != nil {
+ panic(fmt.Errorf("write captcha: %v", err))
+ }
+ return
+ }
+
+ next.ServeHTTP(w, req)
+ })
+ }
+}
--- /dev/null
+module gitea.com/go-chi/captcha
+
+go 1.11
+
+require (
+ gitea.com/go-chi/cache v0.0.0-20210110083709-82c4c9ce2d5e
+ github.com/go-chi/chi v1.5.1
+ github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337
+ github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e
+)
--- /dev/null
+gitea.com/go-chi/cache v0.0.0-20210110083709-82c4c9ce2d5e h1:zgPGaf3kXP0cVm9J0l8ZA2+XDzILYATg0CXbihR6N+o=
+gitea.com/go-chi/cache v0.0.0-20210110083709-82c4c9ce2d5e/go.mod h1:k2V/gPDEtXGjjMGuBJiapffAXTv76H4snSmlJRLUhH0=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
+github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/go-chi/chi v1.5.1 h1:kfTK3Cxd/dkMu/rKs5ZceWYp+t5CtiE7vmaTv3LjC6w=
+github.com/go-chi/chi v1.5.1/go.mod h1:REp24E+25iKvxgeTfHmdUoL5x15kBiDBlnIl5bCwe2k=
+github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
+github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
+github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
+github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lunny/log v0.0.0-20160921050905-7887c61bf0de/go.mod h1:3q8WtuPQsoRbatJuy3nvq/hRSvuBJrHHr+ybPPiNvHQ=
+github.com/lunny/nodb v0.0.0-20160621015157-fc1ef06ad4af/go.mod h1:Cqz6pqow14VObJ7peltM+2n3PWOz7yTrfUuGbVFkzN0=
+github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
+github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
+github.com/siddontang/go-snappy v0.0.0-20140704025258-d8f7bb82a96d/go.mod h1:vq0tzqLRu6TS7Id0wMo2N5QzJoKedVeovOpHjnykSzY=
+github.com/siddontang/ledisdb v0.0.0-20190202134119-8ceb77e66a92/go.mod h1:mF1DpOSOUiJRMR+FDqaqu3EBqrybQtrDDszLUZ6oxPg=
+github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY=
+github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
+github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8=
+github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
+github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
+github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e h1:GSGeB9EAKY2spCABz6xOX5DbxZEXolK+nBSvmsQwRjM=
+github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190730183949-1393eb018365/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/ini.v1 v1.44.2 h1:N6kNUPqiIyxP+s/aINPzRvNpcTVV30qLC0t6ZjZFlUU=
+gopkg.in/ini.v1 v1.44.2/go.mod h1:M3Cogqpuv0QCi3ExAY5V4uOt4qb/R3xZubo9m8lK5wg=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
--- /dev/null
+// Copyright 2013 Beego Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package captcha
+
+import (
+ "bytes"
+ "image"
+ "image/color"
+ "image/png"
+ "io"
+ "math"
+)
+
+const (
+ fontWidth = 11
+ fontHeight = 18
+ blackChar = 1
+
+ // Standard width and height of a captcha image.
+ stdWidth = 240
+ stdHeight = 80
+
+ // Maximum absolute skew factor of a single digit.
+ maxSkew = 0.7
+ // Number of background circles.
+ circleCount = 20
+)
+
+var font = [][]byte{
+ { // 0
+ 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
+ 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
+ 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0,
+ 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
+ 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
+ 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1,
+ 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
+ 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0,
+ 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
+ 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
+ },
+ { // 1
+ 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0,
+ 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0,
+ 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ },
+ { // 2
+ 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0,
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
+ 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0,
+ 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0,
+ 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+ 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ },
+ { // 3
+ 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
+ 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
+ 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0,
+ 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0,
+ 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
+ 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
+ 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
+ },
+ { // 4
+ 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0,
+ 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0,
+ 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0,
+ 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0,
+ 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0,
+ 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0,
+ 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0,
+ 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0,
+ 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0,
+ 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0,
+ 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
+ },
+ { // 5
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
+ 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
+ 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
+ 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0,
+ },
+ { // 6
+ 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0,
+ 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0,
+ 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+ 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0,
+ 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0,
+ 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0,
+ 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1,
+ 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1,
+ 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0,
+ 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
+ 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
+ },
+ { // 7
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
+ 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+ },
+ { // 8
+ 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
+ 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0,
+ 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1,
+ 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
+ 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
+ 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
+ 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0,
+ 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
+ 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0,
+ 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0,
+ 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0,
+ 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1,
+ 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
+ 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
+ },
+ { // 9
+ 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
+ 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0,
+ 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0,
+ 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1,
+ 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1,
+ 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1,
+ 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
+ 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0,
+ 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
+ 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
+ },
+}
+
+type Image struct {
+ *image.Paletted
+ numWidth int
+ numHeight int
+ dotSize int
+}
+
+var prng = &siprng{}
+
+// randIntn returns a pseudorandom non-negative int in range [0, n).
+func randIntn(n int) int {
+ return prng.Intn(n)
+}
+
+// randInt returns a pseudorandom int in range [from, to].
+func randInt(from, to int) int {
+ return prng.Intn(to+1-from) + from
+}
+
+// randFloat returns a pseudorandom float64 in range [from, to].
+func randFloat(from, to float64) float64 {
+ return (to-from)*prng.Float64() + from
+}
+
+func randomPalette(primary color.Palette) color.Palette {
+ p := make([]color.Color, circleCount+1)
+ // Transparent color.
+ p[0] = color.RGBA{0xFF, 0xFF, 0xFF, 0x00}
+ // Primary color.
+ var prim color.RGBA
+ if len(primary) == 0 {
+ prim = color.RGBA{
+ uint8(randIntn(129)),
+ uint8(randIntn(129)),
+ uint8(randIntn(129)),
+ 0xFF,
+ }
+ } else {
+ r, g, b, a := primary[randIntn(len(primary)-1)].RGBA()
+ prim = color.RGBA{uint8(r), uint8(g), uint8(b), uint8(a)}
+ }
+ p[1] = prim
+ // Circle colors.
+ for i := 2; i <= circleCount; i++ {
+ p[i] = randomBrightness(prim, 255)
+ }
+ return p
+}
+
+// NewImage returns a new captcha image of the given width and height with the
+// given digits, where each digit must be in range 0-9. The digit's color is
+// chosen by random from the colorPalette.
+func NewImage(digits []byte, width, height int, colorPalette color.Palette) *Image {
+ m := new(Image)
+ m.Paletted = image.NewPaletted(image.Rect(0, 0, width, height), randomPalette(colorPalette))
+ m.calculateSizes(width, height, len(digits))
+ // Randomly position captcha inside the image.
+ maxx := width - (m.numWidth+m.dotSize)*len(digits) - m.dotSize
+ maxy := height - m.numHeight - m.dotSize*2
+ var border int
+ if width > height {
+ border = height / 5
+ } else {
+ border = width / 5
+ }
+ x := randInt(border, maxx-border)
+ y := randInt(border, maxy-border)
+ // Draw digits.
+ for _, n := range digits {
+ m.drawDigit(font[n], x, y)
+ x += m.numWidth + m.dotSize
+ }
+ // Draw strike-through line.
+ m.strikeThrough()
+ // Apply wave distortion.
+ m.distort(randFloat(5, 10), randFloat(100, 200))
+ // Fill image with random circles.
+ m.fillWithCircles(circleCount, m.dotSize)
+ return m
+}
+
+// encodedPNG encodes an image to PNG and returns
+// the result as a byte slice.
+func (m *Image) encodedPNG() []byte {
+ var buf bytes.Buffer
+ if err := png.Encode(&buf, m.Paletted); err != nil {
+ panic(err.Error())
+ }
+ return buf.Bytes()
+}
+
+// WriteTo writes captcha image in PNG format into the given writer.
+func (m *Image) WriteTo(w io.Writer) (int64, error) {
+ n, err := w.Write(m.encodedPNG())
+ return int64(n), err
+}
+
+func (m *Image) calculateSizes(width, height, ncount int) {
+ // Goal: fit all digits inside the image.
+ var border int
+ if width > height {
+ border = height / 4
+ } else {
+ border = width / 4
+ }
+ // Convert everything to floats for calculations.
+ w := float64(width - border*2)
+ h := float64(height - border*2)
+ // fw takes into account 1-dot spacing between digits.
+ fw := float64(fontWidth + 1)
+ fh := float64(fontHeight)
+ nc := float64(ncount)
+ // Calculate the width of a single digit taking into account only the
+ // width of the image.
+ nw := w / nc
+ // Calculate the height of a digit from this width.
+ nh := nw * fh / fw
+ // Digit too high?
+ if nh > h {
+ // Fit digits based on height.
+ nh = h
+ nw = fw / fh * nh
+ }
+ // Calculate dot size.
+ m.dotSize = int(nh / fh)
+ // Save everything, making the actual width smaller by 1 dot to account
+ // for spacing between digits.
+ m.numWidth = int(nw) - m.dotSize
+ m.numHeight = int(nh)
+}
+
+func (m *Image) drawHorizLine(fromX, toX, y int, colorIdx uint8) {
+ for x := fromX; x <= toX; x++ {
+ m.SetColorIndex(x, y, colorIdx)
+ }
+}
+
+func (m *Image) drawCircle(x, y, radius int, colorIdx uint8) {
+ f := 1 - radius
+ dfx := 1
+ dfy := -2 * radius
+ xo := 0
+ yo := radius
+
+ m.SetColorIndex(x, y+radius, colorIdx)
+ m.SetColorIndex(x, y-radius, colorIdx)
+ m.drawHorizLine(x-radius, x+radius, y, colorIdx)
+
+ for xo < yo {
+ if f >= 0 {
+ yo--
+ dfy += 2
+ f += dfy
+ }
+ xo++
+ dfx += 2
+ f += dfx
+ m.drawHorizLine(x-xo, x+xo, y+yo, colorIdx)
+ m.drawHorizLine(x-xo, x+xo, y-yo, colorIdx)
+ m.drawHorizLine(x-yo, x+yo, y+xo, colorIdx)
+ m.drawHorizLine(x-yo, x+yo, y-xo, colorIdx)
+ }
+}
+
+func (m *Image) fillWithCircles(n, maxradius int) {
+ maxx := m.Bounds().Max.X
+ maxy := m.Bounds().Max.Y
+ for i := 0; i < n; i++ {
+ colorIdx := uint8(randInt(1, circleCount-1))
+ r := randInt(1, maxradius)
+ m.drawCircle(randInt(r, maxx-r), randInt(r, maxy-r), r, colorIdx)
+ }
+}
+
+func (m *Image) strikeThrough() {
+ maxx := m.Bounds().Max.X
+ maxy := m.Bounds().Max.Y
+ y := randInt(maxy/3, maxy-maxy/3)
+ amplitude := randFloat(5, 20)
+ period := randFloat(80, 180)
+ dx := 2.0 * math.Pi / period
+ for x := 0; x < maxx; x++ {
+ xo := amplitude * math.Cos(float64(y)*dx)
+ yo := amplitude * math.Sin(float64(x)*dx)
+ for yn := 0; yn < m.dotSize; yn++ {
+ r := randInt(0, m.dotSize)
+ m.drawCircle(x+int(xo), y+int(yo)+(yn*m.dotSize), r/2, 1)
+ }
+ }
+}
+
+func (m *Image) drawDigit(digit []byte, x, y int) {
+ skf := randFloat(-maxSkew, maxSkew)
+ xs := float64(x)
+ r := m.dotSize / 2
+ y += randInt(-r, r)
+ for yo := 0; yo < fontHeight; yo++ {
+ for xo := 0; xo < fontWidth; xo++ {
+ if digit[yo*fontWidth+xo] != blackChar {
+ continue
+ }
+ m.drawCircle(x+xo*m.dotSize, y+yo*m.dotSize, r, 1)
+ }
+ xs += skf
+ x = int(xs)
+ }
+}
+
+func (m *Image) distort(amplude float64, period float64) {
+ w := m.Bounds().Max.X
+ h := m.Bounds().Max.Y
+
+ oldm := m.Paletted
+ newm := image.NewPaletted(image.Rect(0, 0, w, h), oldm.Palette)
+
+ dx := 2.0 * math.Pi / period
+ for x := 0; x < w; x++ {
+ for y := 0; y < h; y++ {
+ xo := amplude * math.Sin(float64(y)*dx)
+ yo := amplude * math.Cos(float64(x)*dx)
+ newm.SetColorIndex(x, y, oldm.ColorIndexAt(x+int(xo), y+int(yo)))
+ }
+ }
+ m.Paletted = newm
+}
+
+func randomBrightness(c color.RGBA, max uint8) color.RGBA {
+ minc := min3(c.R, c.G, c.B)
+ maxc := max3(c.R, c.G, c.B)
+ if maxc > max {
+ return c
+ }
+ n := randIntn(int(max-maxc)) - int(minc)
+ return color.RGBA{
+ uint8(int(c.R) + n),
+ uint8(int(c.G) + n),
+ uint8(int(c.B) + n),
+ uint8(c.A),
+ }
+}
+
+func min3(x, y, z uint8) (m uint8) {
+ m = x
+ if y < m {
+ m = y
+ }
+ if z < m {
+ m = z
+ }
+ return
+}
+
+func max3(x, y, z uint8) (m uint8) {
+ m = x
+ if y > m {
+ m = y
+ }
+ if z > m {
+ m = z
+ }
+ return
+}
--- /dev/null
+// Copyright 2013 Beego Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package captcha
+
+import (
+ "crypto/rand"
+ "encoding/binary"
+ "io"
+ "sync"
+)
+
+// siprng is PRNG based on SipHash-2-4.
+type siprng struct {
+ mu sync.Mutex
+ k0, k1, ctr uint64
+}
+
+// siphash implements SipHash-2-4, accepting a uint64 as a message.
+func siphash(k0, k1, m uint64) uint64 {
+ // Initialization.
+ v0 := k0 ^ 0x736f6d6570736575
+ v1 := k1 ^ 0x646f72616e646f6d
+ v2 := k0 ^ 0x6c7967656e657261
+ v3 := k1 ^ 0x7465646279746573
+ t := uint64(8) << 56
+
+ // Compression.
+ v3 ^= m
+
+ // Round 1.
+ v0 += v1
+ v1 = v1<<13 | v1>>(64-13)
+ v1 ^= v0
+ v0 = v0<<32 | v0>>(64-32)
+
+ v2 += v3
+ v3 = v3<<16 | v3>>(64-16)
+ v3 ^= v2
+
+ v0 += v3
+ v3 = v3<<21 | v3>>(64-21)
+ v3 ^= v0
+
+ v2 += v1
+ v1 = v1<<17 | v1>>(64-17)
+ v1 ^= v2
+ v2 = v2<<32 | v2>>(64-32)
+
+ // Round 2.
+ v0 += v1
+ v1 = v1<<13 | v1>>(64-13)
+ v1 ^= v0
+ v0 = v0<<32 | v0>>(64-32)
+
+ v2 += v3
+ v3 = v3<<16 | v3>>(64-16)
+ v3 ^= v2
+
+ v0 += v3
+ v3 = v3<<21 | v3>>(64-21)
+ v3 ^= v0
+
+ v2 += v1
+ v1 = v1<<17 | v1>>(64-17)
+ v1 ^= v2
+ v2 = v2<<32 | v2>>(64-32)
+
+ v0 ^= m
+
+ // Compress last block.
+ v3 ^= t
+
+ // Round 1.
+ v0 += v1
+ v1 = v1<<13 | v1>>(64-13)
+ v1 ^= v0
+ v0 = v0<<32 | v0>>(64-32)
+
+ v2 += v3
+ v3 = v3<<16 | v3>>(64-16)
+ v3 ^= v2
+
+ v0 += v3
+ v3 = v3<<21 | v3>>(64-21)
+ v3 ^= v0
+
+ v2 += v1
+ v1 = v1<<17 | v1>>(64-17)
+ v1 ^= v2
+ v2 = v2<<32 | v2>>(64-32)
+
+ // Round 2.
+ v0 += v1
+ v1 = v1<<13 | v1>>(64-13)
+ v1 ^= v0
+ v0 = v0<<32 | v0>>(64-32)
+
+ v2 += v3
+ v3 = v3<<16 | v3>>(64-16)
+ v3 ^= v2
+
+ v0 += v3
+ v3 = v3<<21 | v3>>(64-21)
+ v3 ^= v0
+
+ v2 += v1
+ v1 = v1<<17 | v1>>(64-17)
+ v1 ^= v2
+ v2 = v2<<32 | v2>>(64-32)
+
+ v0 ^= t
+
+ // Finalization.
+ v2 ^= 0xff
+
+ // Round 1.
+ v0 += v1
+ v1 = v1<<13 | v1>>(64-13)
+ v1 ^= v0
+ v0 = v0<<32 | v0>>(64-32)
+
+ v2 += v3
+ v3 = v3<<16 | v3>>(64-16)
+ v3 ^= v2
+
+ v0 += v3
+ v3 = v3<<21 | v3>>(64-21)
+ v3 ^= v0
+
+ v2 += v1
+ v1 = v1<<17 | v1>>(64-17)
+ v1 ^= v2
+ v2 = v2<<32 | v2>>(64-32)
+
+ // Round 2.
+ v0 += v1
+ v1 = v1<<13 | v1>>(64-13)
+ v1 ^= v0
+ v0 = v0<<32 | v0>>(64-32)
+
+ v2 += v3
+ v3 = v3<<16 | v3>>(64-16)
+ v3 ^= v2
+
+ v0 += v3
+ v3 = v3<<21 | v3>>(64-21)
+ v3 ^= v0
+
+ v2 += v1
+ v1 = v1<<17 | v1>>(64-17)
+ v1 ^= v2
+ v2 = v2<<32 | v2>>(64-32)
+
+ // Round 3.
+ v0 += v1
+ v1 = v1<<13 | v1>>(64-13)
+ v1 ^= v0
+ v0 = v0<<32 | v0>>(64-32)
+
+ v2 += v3
+ v3 = v3<<16 | v3>>(64-16)
+ v3 ^= v2
+
+ v0 += v3
+ v3 = v3<<21 | v3>>(64-21)
+ v3 ^= v0
+
+ v2 += v1
+ v1 = v1<<17 | v1>>(64-17)
+ v1 ^= v2
+ v2 = v2<<32 | v2>>(64-32)
+
+ // Round 4.
+ v0 += v1
+ v1 = v1<<13 | v1>>(64-13)
+ v1 ^= v0
+ v0 = v0<<32 | v0>>(64-32)
+
+ v2 += v3
+ v3 = v3<<16 | v3>>(64-16)
+ v3 ^= v2
+
+ v0 += v3
+ v3 = v3<<21 | v3>>(64-21)
+ v3 ^= v0
+
+ v2 += v1
+ v1 = v1<<17 | v1>>(64-17)
+ v1 ^= v2
+ v2 = v2<<32 | v2>>(64-32)
+
+ return v0 ^ v1 ^ v2 ^ v3
+}
+
+// rekey sets a new PRNG key, which is read from crypto/rand.
+func (p *siprng) rekey() {
+ var k [16]byte
+ if _, err := io.ReadFull(rand.Reader, k[:]); err != nil {
+ panic(err.Error())
+ }
+ p.k0 = binary.LittleEndian.Uint64(k[0:8])
+ p.k1 = binary.LittleEndian.Uint64(k[8:16])
+ p.ctr = 1
+}
+
+// Uint64 returns a new pseudorandom uint64.
+// It rekeys PRNG on the first call and every 64 MB of generated data.
+func (p *siprng) Uint64() uint64 {
+ p.mu.Lock()
+ if p.ctr == 0 || p.ctr > 8*1024*1024 {
+ p.rekey()
+ }
+ v := siphash(p.k0, p.k1, p.ctr)
+ p.ctr++
+ p.mu.Unlock()
+ return v
+}
+
+func (p *siprng) Int63() int64 {
+ return int64(p.Uint64() & 0x7fffffffffffffff)
+}
+
+func (p *siprng) Uint32() uint32 {
+ return uint32(p.Uint64())
+}
+
+func (p *siprng) Int31() int32 {
+ return int32(p.Uint32() & 0x7fffffff)
+}
+
+func (p *siprng) Intn(n int) int {
+ if n <= 0 {
+ panic("invalid argument to Intn")
+ }
+ if n <= 1<<31-1 {
+ return int(p.Int31n(int32(n)))
+ }
+ return int(p.Int63n(int64(n)))
+}
+
+func (p *siprng) Int63n(n int64) int64 {
+ if n <= 0 {
+ panic("invalid argument to Int63n")
+ }
+ max := int64((1 << 63) - 1 - (1<<63)%uint64(n))
+ v := p.Int63()
+ for v > max {
+ v = p.Int63()
+ }
+ return v % n
+}
+
+func (p *siprng) Int31n(n int32) int32 {
+ if n <= 0 {
+ panic("invalid argument to Int31n")
+ }
+ max := int32((1 << 31) - 1 - (1<<31)%uint32(n))
+ v := p.Int31()
+ for v > max {
+ v = p.Int31()
+ }
+ return v % n
+}
+
+func (p *siprng) Float64() float64 { return float64(p.Int63()) / (1 << 63) }
+++ /dev/null
-# Compiled Object files, Static and Dynamic libs (Shared Objects)
-*.o
-*.a
-*.so
-
-# Folders
-_obj
-_test
-
-# Architecture specific extensions/prefixes
-*.[568vq]
-[568vq].out
-
-*.cgo1.go
-*.cgo2.c
-_cgo_defun.c
-_cgo_gotypes.go
-_cgo_export.*
-
-_testmain.go
-
-*.exe
-log.db
-*.log
-logs
-.vscode
\ No newline at end of file
+++ /dev/null
-Copyright (c) 2014 - 2016 lunny
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
-* Redistributions of source code must retain the above copyright notice, this
- list of conditions and the following disclaimer.
-
-* Redistributions in binary form must reproduce the above copyright notice,
- this list of conditions and the following disclaimer in the documentation
- and/or other materials provided with the distribution.
-
-* Neither the name of the {organization} nor the names of its
- contributors may be used to endorse or promote products derived from
- this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+++ /dev/null
-## log
-
-[![](https://goreportcard.com/badge/gitea.com/lunny/log)](https://goreportcard.com/report/gitea.com/lunny/log)
-[![GoDoc](https://godoc.org/gitea.com/lunny/log?status.png)](https://godoc.org/gitea.com/lunny/log)
-
-[简体中文](https://gitea.com/lunny/log/blob/master/README_CN.md)
-
-# Installation
-
-```
-go get gitea.com/lunny/log
-```
-
-# Features
-
-* Add color support for unix console
-* Implemented dbwriter to save log to database
-* Implemented FileWriter to save log to file by date or time.
-* Location configuration
-
-# Example
-
-For Single File:
-```Go
-f, _ := os.Create("my.log")
-log.Std.SetOutput(f)
-```
-
-For Multiple Writer:
-```Go
-f, _ := os.Create("my.log")
-log.Std.SetOutput(io.MultiWriter(f, os.Stdout))
-```
-
-For log files by date or time:
-```Go
-w := log.NewFileWriter(log.FileOptions{
- ByType:log.ByDay,
- Dir:"./logs",
-})
-log.Std.SetOutput(w)
-```
-
-# About
-
-This repo is an extension of Golang log.
-
-# LICENSE
-
- BSD License
- [http://creativecommons.org/licenses/BSD/](http://creativecommons.org/licenses/BSD/)
+++ /dev/null
-## log
-
-[![](https://goreportcard.com/badge/gitea.com/lunny/log)](https://goreportcard.com/report/gitea.com/lunny/log)
-[![GoDoc](https://godoc.org/gitea.com/lunny/log?status.png)](https://godoc.org/gitea.com/lunny/log)
-
-[English](https://gitea.com/lunny/log/blob/master/README.md)
-
-# 安装
-
-```
-go get gitea.com/lunny/log
-```
-
-# 特性
-
-* 对unix增加控制台颜色支持
-* 实现了保存log到数据库支持
-* 实现了保存log到按日期的文件支持
-* 实现了设置日期的地区
-
-# 例子
-
-保存到单个文件:
-
-```Go
-f, _ := os.Create("my.log")
-log.Std.SetOutput(f)
-```
-
-保存到数据库:
-
-```Go
-f, _ := os.Create("my.log")
-log.Std.SetOutput(io.MultiWriter(f, os.Stdout))
-```
-
-保存到按时间分隔的文件:
-
-```Go
-w := log.NewFileWriter(log.FileOptions{
- ByType:log.ByDay,
- Dir:"./logs",
-})
-log.Std.SetOutput(w)
-```
-
-# 关于
-
-本 Log 是在 golang 的 log 之上的扩展
-
-# LICENSE
-
- BSD License
- [http://creativecommons.org/licenses/BSD/](http://creativecommons.org/licenses/BSD/)
+++ /dev/null
-package log
-
-import (
- "database/sql"
- "time"
-)
-
-type DBWriter struct {
- db *sql.DB
- stmt *sql.Stmt
- content chan []byte
-}
-
-func NewDBWriter(db *sql.DB) (*DBWriter, error) {
- _, err := db.Exec("CREATE TABLE IF NOT EXISTS log (id int, content text, created datetime)")
- if err != nil {
- return nil, err
- }
- stmt, err := db.Prepare("INSERT INTO log (content, created) values (?, ?)")
- if err != nil {
- return nil, err
- }
- return &DBWriter{db, stmt, make(chan []byte, 1000)}, nil
-}
-
-func (w *DBWriter) Write(p []byte) (n int, err error) {
- _, err = w.stmt.Exec(string(p), time.Now())
- if err == nil {
- n = len(p)
- }
- return
-}
-
-func (w *DBWriter) Close() {
- w.stmt.Close()
-}
+++ /dev/null
-package log
-
-import (
- "io"
- "os"
- "path/filepath"
- "sync"
- "time"
-)
-
-var _ io.Writer = &Files{}
-
-type ByType int
-
-const (
- ByDay ByType = iota
- ByHour
- ByMonth
-)
-
-var (
- formats = map[ByType]string{
- ByDay: "2006-01-02",
- ByHour: "2006-01-02-15",
- ByMonth: "2006-01",
- }
-)
-
-func SetFileFormat(t ByType, format string) {
- formats[t] = format
-}
-
-func (b ByType) Format() string {
- return formats[b]
-}
-
-type Files struct {
- FileOptions
- f *os.File
- lastFormat string
- lock sync.Mutex
-}
-
-type FileOptions struct {
- Dir string
- ByType ByType
- Loc *time.Location
-}
-
-func prepareFileOption(opts []FileOptions) FileOptions {
- var opt FileOptions
- if len(opts) > 0 {
- opt = opts[0]
- }
- if opt.Dir == "" {
- opt.Dir = "./"
- }
- err := os.MkdirAll(opt.Dir, os.ModePerm)
- if err != nil {
- panic(err.Error())
- }
-
- if opt.Loc == nil {
- opt.Loc = time.Local
- }
- return opt
-}
-
-func NewFileWriter(opts ...FileOptions) *Files {
- opt := prepareFileOption(opts)
- return &Files{
- FileOptions: opt,
- }
-}
-
-func (f *Files) getFile() (*os.File, error) {
- var err error
- t := time.Now().In(f.Loc)
- if f.f == nil {
- f.lastFormat = t.Format(f.ByType.Format())
- f.f, err = os.OpenFile(filepath.Join(f.Dir, f.lastFormat+".log"),
- os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
- return f.f, err
- }
- if f.lastFormat != t.Format(f.ByType.Format()) {
- f.f.Close()
- f.lastFormat = t.Format(f.ByType.Format())
- f.f, err = os.OpenFile(filepath.Join(f.Dir, f.lastFormat+".log"),
- os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
- return f.f, err
- }
- return f.f, nil
-}
-
-func (f *Files) Write(bs []byte) (int, error) {
- f.lock.Lock()
- defer f.lock.Unlock()
-
- w, err := f.getFile()
- if err != nil {
- return 0, err
- }
- return w.Write(bs)
-}
-
-func (f *Files) Close() {
- if f.f != nil {
- f.f.Close()
- f.f = nil
- }
- f.lastFormat = ""
-}
+++ /dev/null
-module gitea.com/lunny/log
-
-go 1.12
-
-require github.com/mattn/go-sqlite3 v1.10.0
+++ /dev/null
-github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
-github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
+++ /dev/null
-package log
-
-import (
- "bytes"
- "fmt"
- "io"
- "os"
- "runtime"
- "strings"
- "sync"
- "time"
-)
-
-// These flags define which text to prefix to each log entry generated by the Logger.
-const (
- // Bits or'ed together to control what's printed. There is no control over the
- // order they appear (the order listed here) or the format they present (as
- // described in the comments). A colon appears after these items:
- // 2009/0123 01:23:23.123123 /a/b/c/d.go:23: message
- Ldate = 1 << iota // the date: 2009/0123
- Ltime // the time: 01:23:23
- Lmicroseconds // microsecond resolution: 01:23:23.123123. assumes Ltime.
- Llongfile // full file name and line number: /a/b/c/d.go:23
- Lshortfile // final file name element and line number: d.go:23. overrides Llongfile
- Lmodule // module name
- Llevel // level: 0(Debug), 1(Info), 2(Warn), 3(Error), 4(Panic), 5(Fatal)
- Llongcolor // color will start [info] end of line
- Lshortcolor // color only include [info]
- LstdFlags = Ldate | Ltime // initial values for the standard logger
- //Ldefault = Llevel | LstdFlags | Lshortfile | Llongcolor
-) // [prefix][time][level][module][shortfile|longfile]
-
-func Ldefault() int {
- if runtime.GOOS == "windows" {
- return Llevel | LstdFlags | Lshortfile
- }
- return Llevel | LstdFlags | Lshortfile | Llongcolor
-}
-
-func Version() string {
- return "0.2.0.1121"
-}
-
-const (
- Lall = iota
-)
-const (
- Ldebug = iota
- Linfo
- Lwarn
- Lerror
- Lpanic
- Lfatal
- Lnone
-)
-
-const (
- ForeBlack = iota + 30 //30
- ForeRed //31
- ForeGreen //32
- ForeYellow //33
- ForeBlue //34
- ForePurple //35
- ForeCyan //36
- ForeWhite //37
-)
-
-const (
- BackBlack = iota + 40 //40
- BackRed //41
- BackGreen //42
- BackYellow //43
- BackBlue //44
- BackPurple //45
- BackCyan //46
- BackWhite //47
-)
-
-var levels = []string{
- "[Debug]",
- "[Info]",
- "[Warn]",
- "[Error]",
- "[Panic]",
- "[Fatal]",
-}
-
-// MUST called before all logs
-func SetLevels(lvs []string) {
- levels = lvs
-}
-
-var colors = []int{
- ForeCyan,
- ForeGreen,
- ForeYellow,
- ForeRed,
- ForePurple,
- ForeBlue,
-}
-
-// MUST called before all logs
-func SetColors(cls []int) {
- colors = cls
-}
-
-// A Logger represents an active logging object that generates lines of
-// output to an io.Writer. Each logging operation makes a single call to
-// the Writer's Write method. A Logger can be used simultaneously from
-// multiple goroutines; it guarantees to serialize access to the Writer.
-type Logger struct {
- mu sync.Mutex // ensures atomic writes; protects the following fields
- prefix string // prefix to write at beginning of each line
- flag int // properties
- Level int
- out io.Writer // destination for output
- buf bytes.Buffer // for accumulating text to write
- levelStats [6]int64
- loc *time.Location
-}
-
-// New creates a new Logger. The out variable sets the
-// destination to which log data will be written.
-// The prefix appears at the beginning of each generated log line.
-// The flag argument defines the logging properties.
-func New(out io.Writer, prefix string, flag int) *Logger {
- l := &Logger{out: out, prefix: prefix, Level: 1, flag: flag, loc: time.Local}
- if out != os.Stdout {
- l.flag = RmColorFlags(l.flag)
- }
- return l
-}
-
-var Std = New(os.Stderr, "", Ldefault())
-
-// Cheap integer to fixed-width decimal ASCII. Give a negative width to avoid zero-padding.
-// Knows the buffer has capacity.
-func itoa(buf *bytes.Buffer, i int, wid int) {
- var u uint = uint(i)
- if u == 0 && wid <= 1 {
- buf.WriteByte('0')
- return
- }
-
- // Assemble decimal in reverse order.
- var b [32]byte
- bp := len(b)
- for ; u > 0 || wid > 0; u /= 10 {
- bp--
- wid--
- b[bp] = byte(u%10) + '0'
- }
-
- // avoid slicing b to avoid an allocation.
- for bp < len(b) {
- buf.WriteByte(b[bp])
- bp++
- }
-}
-
-func moduleOf(file string) string {
- pos := strings.LastIndex(file, "/")
- if pos != -1 {
- pos1 := strings.LastIndex(file[:pos], "/src/")
- if pos1 != -1 {
- return file[pos1+5 : pos]
- }
- }
- return "UNKNOWN"
-}
-
-func (l *Logger) formatHeader(buf *bytes.Buffer, t time.Time,
- file string, line int, lvl int, reqId string) {
- if l.prefix != "" {
- buf.WriteString(l.prefix)
- }
- if l.flag&(Ldate|Ltime|Lmicroseconds) != 0 {
- if l.flag&Ldate != 0 {
- year, month, day := t.Date()
- itoa(buf, year, 4)
- buf.WriteByte('/')
- itoa(buf, int(month), 2)
- buf.WriteByte('/')
- itoa(buf, day, 2)
- buf.WriteByte(' ')
- }
- if l.flag&(Ltime|Lmicroseconds) != 0 {
- hour, min, sec := t.Clock()
- itoa(buf, hour, 2)
- buf.WriteByte(':')
- itoa(buf, min, 2)
- buf.WriteByte(':')
- itoa(buf, sec, 2)
- if l.flag&Lmicroseconds != 0 {
- buf.WriteByte('.')
- itoa(buf, t.Nanosecond()/1e3, 6)
- }
- buf.WriteByte(' ')
- }
- }
- if reqId != "" {
- buf.WriteByte('[')
- buf.WriteString(reqId)
- buf.WriteByte(']')
- buf.WriteByte(' ')
- }
-
- if l.flag&(Lshortcolor|Llongcolor) != 0 {
- buf.WriteString(fmt.Sprintf("\033[1;%dm", colors[lvl]))
- }
- if l.flag&Llevel != 0 {
- buf.WriteString(levels[lvl])
- buf.WriteByte(' ')
- }
- if l.flag&Lshortcolor != 0 {
- buf.WriteString("\033[0m")
- }
-
- if l.flag&Lmodule != 0 {
- buf.WriteByte('[')
- buf.WriteString(moduleOf(file))
- buf.WriteByte(']')
- buf.WriteByte(' ')
- }
- if l.flag&(Lshortfile|Llongfile) != 0 {
- if l.flag&Lshortfile != 0 {
- short := file
- for i := len(file) - 1; i > 0; i-- {
- if file[i] == '/' {
- short = file[i+1:]
- break
- }
- }
- file = short
- }
- buf.WriteString(file)
- buf.WriteByte(':')
- itoa(buf, line, -1)
- buf.WriteByte(' ')
- }
-}
-
-// Output writes the output for a logging event. The string s contains
-// the text to print after the prefix specified by the flags of the
-// Logger. A newline is appended if the last character of s is not
-// already a newline. Calldepth is used to recover the PC and is
-// provided for generality, although at the moment on all pre-defined
-// paths it will be 2.
-func (l *Logger) Output(reqId string, lvl int, calldepth int, s string) error {
- if lvl < l.Level {
- return nil
- }
- now := time.Now().In(l.loc) // get this early.
- var file string
- var line int
- l.mu.Lock()
- defer l.mu.Unlock()
- if l.flag&(Lshortfile|Llongfile|Lmodule) != 0 {
- // release lock while getting caller info - it's expensive.
- l.mu.Unlock()
- var ok bool
- _, file, line, ok = runtime.Caller(calldepth)
- if !ok {
- file = "???"
- line = 0
- }
- l.mu.Lock()
- }
- l.levelStats[lvl]++
- l.buf.Reset()
- l.formatHeader(&l.buf, now, file, line, lvl, reqId)
- l.buf.WriteString(s)
- if l.flag&Llongcolor != 0 {
- l.buf.WriteString("\033[0m")
- }
- if len(s) > 0 && s[len(s)-1] != '\n' {
- l.buf.WriteByte('\n')
- }
- _, err := l.out.Write(l.buf.Bytes())
- return err
-}
-
-// -----------------------------------------
-
-// Printf calls l.Output to print to the logger.
-// Arguments are handled in the manner of fmt.Printf.
-func (l *Logger) Printf(format string, v ...interface{}) {
- l.Output("", Linfo, 2, fmt.Sprintf(format, v...))
-}
-
-// Print calls l.Output to print to the logger.
-// Arguments are handled in the manner of fmt.Print.
-func (l *Logger) Print(v ...interface{}) {
- l.Output("", Linfo, 2, fmt.Sprint(v...))
-}
-
-// Println calls l.Output to print to the logger.
-// Arguments are handled in the manner of fmt.Println.
-func (l *Logger) Println(v ...interface{}) {
- l.Output("", Linfo, 2, fmt.Sprintln(v...))
-}
-
-// -----------------------------------------
-
-func (l *Logger) Debugf(format string, v ...interface{}) {
- l.Output("", Ldebug, 2, fmt.Sprintf(format, v...))
-}
-
-func (l *Logger) Debug(v ...interface{}) {
- l.Output("", Ldebug, 2, fmt.Sprintln(v...))
-}
-
-// -----------------------------------------
-func (l *Logger) Infof(format string, v ...interface{}) {
- l.Output("", Linfo, 2, fmt.Sprintf(format, v...))
-}
-
-func (l *Logger) Info(v ...interface{}) {
- l.Output("", Linfo, 2, fmt.Sprintln(v...))
-}
-
-// -----------------------------------------
-func (l *Logger) Warnf(format string, v ...interface{}) {
- l.Output("", Lwarn, 2, fmt.Sprintf(format, v...))
-}
-
-func (l *Logger) Warn(v ...interface{}) {
- l.Output("", Lwarn, 2, fmt.Sprintln(v...))
-}
-
-// -----------------------------------------
-
-func (l *Logger) Errorf(format string, v ...interface{}) {
- l.Output("", Lerror, 2, fmt.Sprintf(format, v...))
-}
-
-func (l *Logger) Error(v ...interface{}) {
- l.Output("", Lerror, 2, fmt.Sprintln(v...))
-}
-
-// -----------------------------------------
-
-func (l *Logger) Fatal(v ...interface{}) {
- l.Output("", Lfatal, 2, fmt.Sprintln(v...))
- os.Exit(1)
-}
-
-// Fatalf is equivalent to l.Printf() followed by a call to os.Exit(1).
-func (l *Logger) Fatalf(format string, v ...interface{}) {
- l.Output("", Lfatal, 2, fmt.Sprintf(format, v...))
- os.Exit(1)
-}
-
-// -----------------------------------------
-// Panic is equivalent to l.Print() followed by a call to panic().
-func (l *Logger) Panic(v ...interface{}) {
- s := fmt.Sprintln(v...)
- l.Output("", Lpanic, 2, s)
- panic(s)
-}
-
-// Panicf is equivalent to l.Printf() followed by a call to panic().
-func (l *Logger) Panicf(format string, v ...interface{}) {
- s := fmt.Sprintf(format, v...)
- l.Output("", Lpanic, 2, s)
- panic(s)
-}
-
-// -----------------------------------------
-func (l *Logger) Stack(v ...interface{}) {
- s := fmt.Sprint(v...)
- s += "\n"
- buf := make([]byte, 1024*1024)
- n := runtime.Stack(buf, true)
- s += string(buf[:n])
- s += "\n"
- l.Output("", Lerror, 2, s)
-}
-
-// -----------------------------------------
-func (l *Logger) Stat() (stats []int64) {
- l.mu.Lock()
- v := l.levelStats
- l.mu.Unlock()
- return v[:]
-}
-
-// Flags returns the output flags for the logger.
-func (l *Logger) Flags() int {
- l.mu.Lock()
- defer l.mu.Unlock()
- return l.flag
-}
-
-func RmColorFlags(flag int) int {
- // for un std out, it should not show color since almost them don't support
- if flag&Llongcolor != 0 {
- flag = flag ^ Llongcolor
- }
- if flag&Lshortcolor != 0 {
- flag = flag ^ Lshortcolor
- }
- return flag
-}
-
-func (l *Logger) Location() *time.Location {
- return l.loc
-}
-
-func (l *Logger) SetLocation(loc *time.Location) {
- l.loc = loc
-}
-
-// SetFlags sets the output flags for the logger.
-func (l *Logger) SetFlags(flag int) {
- l.mu.Lock()
- defer l.mu.Unlock()
- if l.out != os.Stdout {
- flag = RmColorFlags(flag)
- }
- l.flag = flag
-}
-
-// Prefix returns the output prefix for the logger.
-func (l *Logger) Prefix() string {
- l.mu.Lock()
- defer l.mu.Unlock()
- return l.prefix
-}
-
-// SetPrefix sets the output prefix for the logger.
-func (l *Logger) SetPrefix(prefix string) {
- l.mu.Lock()
- defer l.mu.Unlock()
- l.prefix = prefix
-}
-
-// SetOutputLevel sets the output level for the logger.
-func (l *Logger) SetOutputLevel(lvl int) {
- l.mu.Lock()
- defer l.mu.Unlock()
- l.Level = lvl
-}
-
-func (l *Logger) OutputLevel() int {
- return l.Level
-}
-
-func (l *Logger) SetOutput(w io.Writer) {
- l.mu.Lock()
- defer l.mu.Unlock()
- l.out = w
- if w != os.Stdout {
- l.flag = RmColorFlags(l.flag)
- }
-}
-
-// SetOutput sets the output destination for the standard logger.
-func SetOutput(w io.Writer) {
- Std.SetOutput(w)
-}
-
-func SetLocation(loc *time.Location) {
- Std.SetLocation(loc)
-}
-
-func Location() *time.Location {
- return Std.Location()
-}
-
-// Flags returns the output flags for the standard logger.
-func Flags() int {
- return Std.Flags()
-}
-
-// SetFlags sets the output flags for the standard logger.
-func SetFlags(flag int) {
- Std.SetFlags(flag)
-}
-
-// Prefix returns the output prefix for the standard logger.
-func Prefix() string {
- return Std.Prefix()
-}
-
-// SetPrefix sets the output prefix for the standard logger.
-func SetPrefix(prefix string) {
- Std.SetPrefix(prefix)
-}
-
-func SetOutputLevel(lvl int) {
- Std.SetOutputLevel(lvl)
-}
-
-func OutputLevel() int {
- return Std.OutputLevel()
-}
-
-// -----------------------------------------
-
-// Print calls Output to print to the standard logger.
-// Arguments are handled in the manner of fmt.Print.
-func Print(v ...interface{}) {
- Std.Output("", Linfo, 2, fmt.Sprintln(v...))
-}
-
-// Printf calls Output to print to the standard logger.
-// Arguments are handled in the manner of fmt.Printf.
-func Printf(format string, v ...interface{}) {
- Std.Output("", Linfo, 2, fmt.Sprintf(format, v...))
-}
-
-// Println calls Output to print to the standard logger.
-// Arguments are handled in the manner of fmt.Println.
-func Println(v ...interface{}) {
- Std.Output("", Linfo, 2, fmt.Sprintln(v...))
-}
-
-// -----------------------------------------
-
-func Debugf(format string, v ...interface{}) {
- Std.Output("", Ldebug, 2, fmt.Sprintf(format, v...))
-}
-
-func Debug(v ...interface{}) {
- Std.Output("", Ldebug, 2, fmt.Sprintln(v...))
-}
-
-// -----------------------------------------
-
-func Infof(format string, v ...interface{}) {
- Std.Output("", Linfo, 2, fmt.Sprintf(format, v...))
-}
-
-func Info(v ...interface{}) {
- Std.Output("", Linfo, 2, fmt.Sprintln(v...))
-}
-
-// -----------------------------------------
-
-func Warnf(format string, v ...interface{}) {
- Std.Output("", Lwarn, 2, fmt.Sprintf(format, v...))
-}
-
-func Warn(v ...interface{}) {
- Std.Output("", Lwarn, 2, fmt.Sprintln(v...))
-}
-
-// -----------------------------------------
-
-func Errorf(format string, v ...interface{}) {
- Std.Output("", Lerror, 2, fmt.Sprintf(format, v...))
-}
-
-func Error(v ...interface{}) {
- Std.Output("", Lerror, 2, fmt.Sprintln(v...))
-}
-
-// -----------------------------------------
-
-// Fatal is equivalent to Print() followed by a call to os.Exit(1).
-func Fatal(v ...interface{}) {
- Std.Output("", Lfatal, 2, fmt.Sprintln(v...))
-}
-
-// Fatalf is equivalent to Printf() followed by a call to os.Exit(1).
-func Fatalf(format string, v ...interface{}) {
- Std.Output("", Lfatal, 2, fmt.Sprintf(format, v...))
-}
-
-// -----------------------------------------
-
-// Panic is equivalent to Print() followed by a call to panic().
-func Panic(v ...interface{}) {
- Std.Output("", Lpanic, 2, fmt.Sprintln(v...))
-}
-
-// Panicf is equivalent to Printf() followed by a call to panic().
-func Panicf(format string, v ...interface{}) {
- Std.Output("", Lpanic, 2, fmt.Sprintf(format, v...))
-}
-
-// -----------------------------------------
-
-func Stack(v ...interface{}) {
- s := fmt.Sprint(v...)
- s += "\n"
- buf := make([]byte, 1024*1024)
- n := runtime.Stack(buf, true)
- s += string(buf[:n])
- s += "\n"
- Std.Output("", Lerror, 2, s)
-}
-
-// -----------------------------------------
+++ /dev/null
-build
-*.pyc
-.DS_Store
-nohup.out
-build_config.mk
-var
-.vscode
+++ /dev/null
-The MIT License (MIT)
-
-Copyright (c) 2014 siddontang
-
-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.
\ No newline at end of file
+++ /dev/null
-# NoDB
-
-[中文](https://gitea.com/lunny/nodb/blob/master/README_CN.md)
-
-Nodb is a fork of [ledisdb](https://github.com/siddontang/ledisdb) and shrink version. It's get rid of all C or other language codes and only keep Go's. It aims to provide a nosql database library rather than a redis like server. So if you want a redis like server, ledisdb is the best choose.
-
-Nodb is a pure Go and high performance NoSQL database library. It supports some data structure like kv, list, hash, zset, bitmap, set.
-
-Nodb now use [goleveldb](https://github.com/syndtr/goleveldb) as backend to store data.
-
-## Features
-
-+ Rich data structure: KV, List, Hash, ZSet, Bitmap, Set.
-+ Stores lots of data, over the memory limit.
-+ Supports expiration and ttl.
-+ Easy to embed in your own Go application.
-
-## Install
-
- go get gitea.com/lunny/nodb
-
-## Package Example
-
-### Open And Select database
-```go
-import(
- "gitea.com/lunny/nodb"
- "gitea.com/lunny/nodb/config"
-)
-
-cfg := new(config.Config)
-cfg.DataDir = "./"
-dbs, err := nodb.Open(cfg)
-if err != nil {
- fmt.Printf("nodb: error opening db: %v", err)
-}
-
-db, _ := dbs.Select(0)
-```
-### KV
-
-KV is the most basic nodb type like any other key-value database.
-```go
-err := db.Set(key, value)
-value, err := db.Get(key)
-```
-### List
-
-List is simply lists of values, sorted by insertion order.
-You can push or pop value on the list head (left) or tail (right).
-```go
-err := db.LPush(key, value1)
-err := db.RPush(key, value2)
-value1, err := db.LPop(key)
-value2, err := db.RPop(key)
-```
-### Hash
-
-Hash is a map between fields and values.
-```go
-n, err := db.HSet(key, field1, value1)
-n, err := db.HSet(key, field2, value2)
-value1, err := db.HGet(key, field1)
-value2, err := db.HGet(key, field2)
-```
-### ZSet
-
-ZSet is a sorted collections of values.
-Every member of zset is associated with score, a int64 value which used to sort, from smallest to greatest score.
-Members are unique, but score may be same.
-```go
-n, err := db.ZAdd(key, ScorePair{score1, member1}, ScorePair{score2, member2})
-ay, err := db.ZRangeByScore(key, minScore, maxScore, 0, -1)
-```
-## Links
-
-+ [Ledisdb Official Website](http://ledisdb.com)
-+ [GoDoc](https://godoc.org/gitea.com/lunny/nodb)
-
-
-## Thanks
-
-Gmail: siddontang@gmail.com
+++ /dev/null
-# NoDB
-
-[English](https://gitea.com/lunny/nodb/blob/master/README.md)
-
-Nodb 是 [ledisdb](https://github.com/siddontang/ledisdb) 的克隆和缩减版本。该版本去掉了所有C和其它语言的依赖,只保留Go语言的。目标是提供一个Nosql数据库的开发库而不是提供一个像Redis那样的服务器。因此如果你想要的是一个独立服务器,你可以直接选择ledisdb。
-
-Nodb 是一个纯Go的高性能 NoSQL 数据库。他支持 kv, list, hash, zset, bitmap, set 等数据结构。
-
-Nodb 当前底层使用 (goleveldb)[https://github.com/syndtr/goleveldb] 来存储数据。
-
-## 特性
-
-+ 丰富的数据结构支持: KV, List, Hash, ZSet, Bitmap, Set。
-+ 永久存储并且不受内存的限制。
-+ 高性能那个。
-+ 可以方便的嵌入到你的应用程序中。
-
-## 安装
-
- go get gitea.com/lunny/nodb
-
-## 例子
-
-### 打开和选择数据库
-```go
-import(
- "gitea.com/lunny/nodb"
- "gitea.com/lunny/nodb/config"
-)
-
-cfg := new(config.Config)
-cfg.DataDir = "./"
-dbs, err := nodb.Open(cfg)
-if err != nil {
- fmt.Printf("nodb: error opening db: %v", err)
-}
-db, _ := dbs.Select(0)
-```
-### KV
-
-KV 是最基础的功能,和其它Nosql一样。
-```go
-err := db.Set(key, value)
-value, err := db.Get(key)
-```
-### List
-
-List 是一些值的简单列表,按照插入的顺序排列。你可以从左或右push和pop值。
-```go
-err := db.LPush(key, value1)
-err := db.RPush(key, value2)
-value1, err := db.LPop(key)
-value2, err := db.RPop(key)
-```
-### Hash
-
-Hash 是一个field和value对应的map。
-```go
-n, err := db.HSet(key, field1, value1)
-n, err := db.HSet(key, field2, value2)
-value1, err := db.HGet(key, field1)
-value2, err := db.HGet(key, field2)
-```
-### ZSet
-
-ZSet 是一个排序的值集合。zset的每个成员对应一个score,这是一个int64的值用于从小到大排序。成员不可重复,但是score可以相同。
-```go
-n, err := db.ZAdd(key, ScorePair{score1, member1}, ScorePair{score2, member2})
-ay, err := db.ZRangeByScore(key, minScore, maxScore, 0, -1)
-```
-
-## 链接
-
-+ [Ledisdb Official Website](http://ledisdb.com)
-+ [GoDoc](https://godoc.org/gitea.com/lunny/nodb)
-
-
-## 感谢
-
-Gmail: siddontang@gmail.com
+++ /dev/null
-package nodb
-
-import (
- "sync"
-
- "gitea.com/lunny/nodb/store"
-)
-
-type batch struct {
- l *Nodb
-
- store.WriteBatch
-
- sync.Locker
-
- logs [][]byte
-
- tx *Tx
-}
-
-func (b *batch) Commit() error {
- b.l.commitLock.Lock()
- defer b.l.commitLock.Unlock()
-
- err := b.WriteBatch.Commit()
-
- if b.l.binlog != nil {
- if err == nil {
- if b.tx == nil {
- b.l.binlog.Log(b.logs...)
- } else {
- b.tx.logs = append(b.tx.logs, b.logs...)
- }
- }
- b.logs = [][]byte{}
- }
-
- return err
-}
-
-func (b *batch) Lock() {
- b.Locker.Lock()
-}
-
-func (b *batch) Unlock() {
- if b.l.binlog != nil {
- b.logs = [][]byte{}
- }
- b.WriteBatch.Rollback()
- b.Locker.Unlock()
-}
-
-func (b *batch) Put(key []byte, value []byte) {
- if b.l.binlog != nil {
- buf := encodeBinLogPut(key, value)
- b.logs = append(b.logs, buf)
- }
- b.WriteBatch.Put(key, value)
-}
-
-func (b *batch) Delete(key []byte) {
- if b.l.binlog != nil {
- buf := encodeBinLogDelete(key)
- b.logs = append(b.logs, buf)
- }
- b.WriteBatch.Delete(key)
-}
-
-type dbBatchLocker struct {
- l *sync.Mutex
- wrLock *sync.RWMutex
-}
-
-func (l *dbBatchLocker) Lock() {
- l.wrLock.RLock()
- l.l.Lock()
-}
-
-func (l *dbBatchLocker) Unlock() {
- l.l.Unlock()
- l.wrLock.RUnlock()
-}
-
-type txBatchLocker struct {
-}
-
-func (l *txBatchLocker) Lock() {}
-func (l *txBatchLocker) Unlock() {}
-
-type multiBatchLocker struct {
-}
-
-func (l *multiBatchLocker) Lock() {}
-func (l *multiBatchLocker) Unlock() {}
-
-func (l *Nodb) newBatch(wb store.WriteBatch, locker sync.Locker, tx *Tx) *batch {
- b := new(batch)
- b.l = l
- b.WriteBatch = wb
-
- b.tx = tx
- b.Locker = locker
-
- b.logs = [][]byte{}
- return b
-}
+++ /dev/null
-package nodb
-
-import (
- "bufio"
- "encoding/binary"
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "path"
- "strconv"
- "strings"
- "sync"
- "time"
-
- "gitea.com/lunny/log"
- "gitea.com/lunny/nodb/config"
-)
-
-type BinLogHead struct {
- CreateTime uint32
- BatchId uint32
- PayloadLen uint32
-}
-
-func (h *BinLogHead) Len() int {
- return 12
-}
-
-func (h *BinLogHead) Write(w io.Writer) error {
- if err := binary.Write(w, binary.BigEndian, h.CreateTime); err != nil {
- return err
- }
-
- if err := binary.Write(w, binary.BigEndian, h.BatchId); err != nil {
- return err
- }
-
- if err := binary.Write(w, binary.BigEndian, h.PayloadLen); err != nil {
- return err
- }
-
- return nil
-}
-
-func (h *BinLogHead) handleReadError(err error) error {
- if err == io.EOF {
- return io.ErrUnexpectedEOF
- } else {
- return err
- }
-}
-
-func (h *BinLogHead) Read(r io.Reader) error {
- var err error
- if err = binary.Read(r, binary.BigEndian, &h.CreateTime); err != nil {
- return err
- }
-
- if err = binary.Read(r, binary.BigEndian, &h.BatchId); err != nil {
- return h.handleReadError(err)
- }
-
- if err = binary.Read(r, binary.BigEndian, &h.PayloadLen); err != nil {
- return h.handleReadError(err)
- }
-
- return nil
-}
-
-func (h *BinLogHead) InSameBatch(ho *BinLogHead) bool {
- if h.CreateTime == ho.CreateTime && h.BatchId == ho.BatchId {
- return true
- } else {
- return false
- }
-}
-
-/*
-index file format:
-ledis-bin.00001
-ledis-bin.00002
-ledis-bin.00003
-
-log file format
-
-Log: Head|PayloadData
-
-Head: createTime|batchId|payloadData
-
-*/
-
-type BinLog struct {
- sync.Mutex
-
- path string
-
- cfg *config.BinLogConfig
-
- logFile *os.File
-
- logWb *bufio.Writer
-
- indexName string
- logNames []string
- lastLogIndex int64
-
- batchId uint32
-
- ch chan struct{}
-}
-
-func NewBinLog(cfg *config.Config) (*BinLog, error) {
- l := new(BinLog)
-
- l.cfg = &cfg.BinLog
- l.cfg.Adjust()
-
- l.path = path.Join(cfg.DataDir, "binlog")
-
- if err := os.MkdirAll(l.path, os.ModePerm); err != nil {
- return nil, err
- }
-
- l.logNames = make([]string, 0, 16)
-
- l.ch = make(chan struct{})
-
- if err := l.loadIndex(); err != nil {
- return nil, err
- }
-
- return l, nil
-}
-
-func (l *BinLog) flushIndex() error {
- data := strings.Join(l.logNames, "\n")
-
- bakName := fmt.Sprintf("%s.bak", l.indexName)
- f, err := os.OpenFile(bakName, os.O_WRONLY|os.O_CREATE, 0666)
- if err != nil {
- log.Errorf("create binlog bak index error %s", err.Error())
- return err
- }
-
- if _, err := f.WriteString(data); err != nil {
- log.Errorf("write binlog index error %s", err.Error())
- f.Close()
- return err
- }
-
- f.Close()
-
- if err := os.Rename(bakName, l.indexName); err != nil {
- log.Errorf("rename binlog bak index error %s", err.Error())
- return err
- }
-
- return nil
-}
-
-func (l *BinLog) loadIndex() error {
- l.indexName = path.Join(l.path, fmt.Sprintf("ledis-bin.index"))
- if _, err := os.Stat(l.indexName); os.IsNotExist(err) {
- //no index file, nothing to do
- } else {
- indexData, err := ioutil.ReadFile(l.indexName)
- if err != nil {
- return err
- }
-
- lines := strings.Split(string(indexData), "\n")
- for _, line := range lines {
- line = strings.Trim(line, "\r\n ")
- if len(line) == 0 {
- continue
- }
-
- if _, err := os.Stat(path.Join(l.path, line)); err != nil {
- log.Errorf("load index line %s error %s", line, err.Error())
- return err
- } else {
- l.logNames = append(l.logNames, line)
- }
- }
- }
- if l.cfg.MaxFileNum > 0 && len(l.logNames) > l.cfg.MaxFileNum {
- //remove oldest logfile
- if err := l.Purge(len(l.logNames) - l.cfg.MaxFileNum); err != nil {
- return err
- }
- }
-
- var err error
- if len(l.logNames) == 0 {
- l.lastLogIndex = 1
- } else {
- lastName := l.logNames[len(l.logNames)-1]
-
- if l.lastLogIndex, err = strconv.ParseInt(path.Ext(lastName)[1:], 10, 64); err != nil {
- log.Errorf("invalid logfile name %s", err.Error())
- return err
- }
-
- //like mysql, if server restart, a new binlog will create
- l.lastLogIndex++
- }
-
- return nil
-}
-
-func (l *BinLog) getLogFile() string {
- return l.FormatLogFileName(l.lastLogIndex)
-}
-
-func (l *BinLog) openNewLogFile() error {
- var err error
- lastName := l.getLogFile()
-
- logPath := path.Join(l.path, lastName)
- if l.logFile, err = os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY, 0666); err != nil {
- log.Errorf("open new logfile error %s", err.Error())
- return err
- }
-
- if l.cfg.MaxFileNum > 0 && len(l.logNames) == l.cfg.MaxFileNum {
- l.purge(1)
- }
-
- l.logNames = append(l.logNames, lastName)
-
- if l.logWb == nil {
- l.logWb = bufio.NewWriterSize(l.logFile, 1024)
- } else {
- l.logWb.Reset(l.logFile)
- }
-
- if err = l.flushIndex(); err != nil {
- return err
- }
-
- return nil
-}
-
-func (l *BinLog) checkLogFileSize() bool {
- if l.logFile == nil {
- return false
- }
-
- st, _ := l.logFile.Stat()
- if st.Size() >= int64(l.cfg.MaxFileSize) {
- l.closeLog()
- return true
- }
-
- return false
-}
-
-func (l *BinLog) closeLog() {
- l.lastLogIndex++
-
- l.logFile.Close()
- l.logFile = nil
-}
-
-func (l *BinLog) purge(n int) {
- for i := 0; i < n; i++ {
- logPath := path.Join(l.path, l.logNames[i])
- os.Remove(logPath)
- }
-
- copy(l.logNames[0:], l.logNames[n:])
- l.logNames = l.logNames[0 : len(l.logNames)-n]
-}
-
-func (l *BinLog) Close() {
- if l.logFile != nil {
- l.logFile.Close()
- l.logFile = nil
- }
-}
-
-func (l *BinLog) LogNames() []string {
- return l.logNames
-}
-
-func (l *BinLog) LogFileName() string {
- return l.getLogFile()
-}
-
-func (l *BinLog) LogFilePos() int64 {
- if l.logFile == nil {
- return 0
- } else {
- st, _ := l.logFile.Stat()
- return st.Size()
- }
-}
-
-func (l *BinLog) LogFileIndex() int64 {
- return l.lastLogIndex
-}
-
-func (l *BinLog) FormatLogFileName(index int64) string {
- return fmt.Sprintf("ledis-bin.%07d", index)
-}
-
-func (l *BinLog) FormatLogFilePath(index int64) string {
- return path.Join(l.path, l.FormatLogFileName(index))
-}
-
-func (l *BinLog) LogPath() string {
- return l.path
-}
-
-func (l *BinLog) Purge(n int) error {
- l.Lock()
- defer l.Unlock()
-
- if len(l.logNames) == 0 {
- return nil
- }
-
- if n >= len(l.logNames) {
- n = len(l.logNames)
- //can not purge current log file
- if l.logNames[n-1] == l.getLogFile() {
- n = n - 1
- }
- }
-
- l.purge(n)
-
- return l.flushIndex()
-}
-
-func (l *BinLog) PurgeAll() error {
- l.Lock()
- defer l.Unlock()
-
- l.closeLog()
- return l.openNewLogFile()
-}
-
-func (l *BinLog) Log(args ...[]byte) error {
- l.Lock()
- defer l.Unlock()
-
- var err error
-
- if l.logFile == nil {
- if err = l.openNewLogFile(); err != nil {
- return err
- }
- }
-
- head := &BinLogHead{}
-
- head.CreateTime = uint32(time.Now().Unix())
- head.BatchId = l.batchId
-
- l.batchId++
-
- for _, data := range args {
- head.PayloadLen = uint32(len(data))
-
- if err := head.Write(l.logWb); err != nil {
- return err
- }
-
- if _, err := l.logWb.Write(data); err != nil {
- return err
- }
- }
-
- if err = l.logWb.Flush(); err != nil {
- log.Errorf("write log error %s", err.Error())
- return err
- }
-
- l.checkLogFileSize()
-
- close(l.ch)
- l.ch = make(chan struct{})
-
- return nil
-}
-
-func (l *BinLog) Wait() <-chan struct{} {
- return l.ch
-}
+++ /dev/null
-package nodb
-
-import (
- "encoding/binary"
- "errors"
- "fmt"
- "strconv"
-)
-
-var (
- errBinLogDeleteType = errors.New("invalid bin log delete type")
- errBinLogPutType = errors.New("invalid bin log put type")
- errBinLogCommandType = errors.New("invalid bin log command type")
-)
-
-func encodeBinLogDelete(key []byte) []byte {
- buf := make([]byte, 1+len(key))
- buf[0] = BinLogTypeDeletion
- copy(buf[1:], key)
- return buf
-}
-
-func decodeBinLogDelete(sz []byte) ([]byte, error) {
- if len(sz) < 1 || sz[0] != BinLogTypeDeletion {
- return nil, errBinLogDeleteType
- }
-
- return sz[1:], nil
-}
-
-func encodeBinLogPut(key []byte, value []byte) []byte {
- buf := make([]byte, 3+len(key)+len(value))
- buf[0] = BinLogTypePut
- pos := 1
- binary.BigEndian.PutUint16(buf[pos:], uint16(len(key)))
- pos += 2
- copy(buf[pos:], key)
- pos += len(key)
- copy(buf[pos:], value)
-
- return buf
-}
-
-func decodeBinLogPut(sz []byte) ([]byte, []byte, error) {
- if len(sz) < 3 || sz[0] != BinLogTypePut {
- return nil, nil, errBinLogPutType
- }
-
- keyLen := int(binary.BigEndian.Uint16(sz[1:]))
- if 3+keyLen > len(sz) {
- return nil, nil, errBinLogPutType
- }
-
- return sz[3 : 3+keyLen], sz[3+keyLen:], nil
-}
-
-func FormatBinLogEvent(event []byte) (string, error) {
- logType := uint8(event[0])
-
- var err error
- var k []byte
- var v []byte
-
- var buf []byte = make([]byte, 0, 1024)
-
- switch logType {
- case BinLogTypePut:
- k, v, err = decodeBinLogPut(event)
- buf = append(buf, "PUT "...)
- case BinLogTypeDeletion:
- k, err = decodeBinLogDelete(event)
- buf = append(buf, "DELETE "...)
- default:
- err = errInvalidBinLogEvent
- }
-
- if err != nil {
- return "", err
- }
-
- if buf, err = formatDataKey(buf, k); err != nil {
- return "", err
- }
-
- if v != nil && len(v) != 0 {
- buf = append(buf, fmt.Sprintf(" %q", v)...)
- }
-
- return String(buf), nil
-}
-
-func formatDataKey(buf []byte, k []byte) ([]byte, error) {
- if len(k) < 2 {
- return nil, errInvalidBinLogEvent
- }
-
- buf = append(buf, fmt.Sprintf("DB:%2d ", k[0])...)
- buf = append(buf, fmt.Sprintf("%s ", TypeName[k[1]])...)
-
- db := new(DB)
- db.index = k[0]
-
- //to do format at respective place
-
- switch k[1] {
- case KVType:
- if key, err := db.decodeKVKey(k); err != nil {
- return nil, err
- } else {
- buf = strconv.AppendQuote(buf, String(key))
- }
- case HashType:
- if key, field, err := db.hDecodeHashKey(k); err != nil {
- return nil, err
- } else {
- buf = strconv.AppendQuote(buf, String(key))
- buf = append(buf, ' ')
- buf = strconv.AppendQuote(buf, String(field))
- }
- case HSizeType:
- if key, err := db.hDecodeSizeKey(k); err != nil {
- return nil, err
- } else {
- buf = strconv.AppendQuote(buf, String(key))
- }
- case ListType:
- if key, seq, err := db.lDecodeListKey(k); err != nil {
- return nil, err
- } else {
- buf = strconv.AppendQuote(buf, String(key))
- buf = append(buf, ' ')
- buf = strconv.AppendInt(buf, int64(seq), 10)
- }
- case LMetaType:
- if key, err := db.lDecodeMetaKey(k); err != nil {
- return nil, err
- } else {
- buf = strconv.AppendQuote(buf, String(key))
- }
- case ZSetType:
- if key, m, err := db.zDecodeSetKey(k); err != nil {
- return nil, err
- } else {
- buf = strconv.AppendQuote(buf, String(key))
- buf = append(buf, ' ')
- buf = strconv.AppendQuote(buf, String(m))
- }
- case ZSizeType:
- if key, err := db.zDecodeSizeKey(k); err != nil {
- return nil, err
- } else {
- buf = strconv.AppendQuote(buf, String(key))
- }
- case ZScoreType:
- if key, m, score, err := db.zDecodeScoreKey(k); err != nil {
- return nil, err
- } else {
- buf = strconv.AppendQuote(buf, String(key))
- buf = append(buf, ' ')
- buf = strconv.AppendQuote(buf, String(m))
- buf = append(buf, ' ')
- buf = strconv.AppendInt(buf, score, 10)
- }
- case BitType:
- if key, seq, err := db.bDecodeBinKey(k); err != nil {
- return nil, err
- } else {
- buf = strconv.AppendQuote(buf, String(key))
- buf = append(buf, ' ')
- buf = strconv.AppendUint(buf, uint64(seq), 10)
- }
- case BitMetaType:
- if key, err := db.bDecodeMetaKey(k); err != nil {
- return nil, err
- } else {
- buf = strconv.AppendQuote(buf, String(key))
- }
- case SetType:
- if key, member, err := db.sDecodeSetKey(k); err != nil {
- return nil, err
- } else {
- buf = strconv.AppendQuote(buf, String(key))
- buf = append(buf, ' ')
- buf = strconv.AppendQuote(buf, String(member))
- }
- case SSizeType:
- if key, err := db.sDecodeSizeKey(k); err != nil {
- return nil, err
- } else {
- buf = strconv.AppendQuote(buf, String(key))
- }
- case ExpTimeType:
- if tp, key, t, err := db.expDecodeTimeKey(k); err != nil {
- return nil, err
- } else {
- buf = append(buf, TypeName[tp]...)
- buf = append(buf, ' ')
- buf = strconv.AppendQuote(buf, String(key))
- buf = append(buf, ' ')
- buf = strconv.AppendInt(buf, t, 10)
- }
- case ExpMetaType:
- if tp, key, err := db.expDecodeMetaKey(k); err != nil {
- return nil, err
- } else {
- buf = append(buf, TypeName[tp]...)
- buf = append(buf, ' ')
- buf = strconv.AppendQuote(buf, String(key))
- }
- default:
- return nil, errInvalidBinLogEvent
- }
-
- return buf, nil
-}
+++ /dev/null
-package config
-
-import (
- "io/ioutil"
-
- "github.com/pelletier/go-toml"
-)
-
-type Size int
-
-const (
- DefaultAddr string = "127.0.0.1:6380"
- DefaultHttpAddr string = "127.0.0.1:11181"
-
- DefaultDBName string = "goleveldb"
-
- DefaultDataDir string = "./data"
-)
-
-const (
- MaxBinLogFileSize int = 1024 * 1024 * 1024
- MaxBinLogFileNum int = 10000
-
- DefaultBinLogFileSize int = MaxBinLogFileSize
- DefaultBinLogFileNum int = 10
-)
-
-type LevelDBConfig struct {
- Compression bool `toml:"compression"`
- BlockSize int `toml:"block_size"`
- WriteBufferSize int `toml:"write_buffer_size"`
- CacheSize int `toml:"cache_size"`
- MaxOpenFiles int `toml:"max_open_files"`
-}
-
-type LMDBConfig struct {
- MapSize int `toml:"map_size"`
- NoSync bool `toml:"nosync"`
-}
-
-type BinLogConfig struct {
- MaxFileSize int `toml:"max_file_size"`
- MaxFileNum int `toml:"max_file_num"`
-}
-
-type Config struct {
- DataDir string `toml:"data_dir"`
-
- DBName string `toml:"db_name"`
-
- LevelDB LevelDBConfig `toml:"leveldb"`
-
- LMDB LMDBConfig `toml:"lmdb"`
-
- BinLog BinLogConfig `toml:"binlog"`
-
- SlaveOf string `toml:"slaveof"`
-
- AccessLog string `toml:"access_log"`
-}
-
-func NewConfigWithFile(fileName string) (*Config, error) {
- data, err := ioutil.ReadFile(fileName)
- if err != nil {
- return nil, err
- }
-
- return NewConfigWithData(data)
-}
-
-func NewConfigWithData(data []byte) (*Config, error) {
- cfg := NewConfigDefault()
-
- err := toml.Unmarshal(data, cfg)
- if err != nil {
- return nil, err
- }
-
- return cfg, nil
-}
-
-func NewConfigDefault() *Config {
- cfg := new(Config)
-
- cfg.DataDir = DefaultDataDir
-
- cfg.DBName = DefaultDBName
-
- // disable binlog
- cfg.BinLog.MaxFileNum = 0
- cfg.BinLog.MaxFileSize = 0
-
- // disable replication
- cfg.SlaveOf = ""
-
- // disable access log
- cfg.AccessLog = ""
-
- cfg.LMDB.MapSize = 20 * 1024 * 1024
- cfg.LMDB.NoSync = true
-
- return cfg
-}
-
-func (cfg *LevelDBConfig) Adjust() {
- if cfg.CacheSize <= 0 {
- cfg.CacheSize = 4 * 1024 * 1024
- }
-
- if cfg.BlockSize <= 0 {
- cfg.BlockSize = 4 * 1024
- }
-
- if cfg.WriteBufferSize <= 0 {
- cfg.WriteBufferSize = 4 * 1024 * 1024
- }
-
- if cfg.MaxOpenFiles < 1024 {
- cfg.MaxOpenFiles = 1024
- }
-}
-
-func (cfg *BinLogConfig) Adjust() {
- if cfg.MaxFileSize <= 0 {
- cfg.MaxFileSize = DefaultBinLogFileSize
- } else if cfg.MaxFileSize > MaxBinLogFileSize {
- cfg.MaxFileSize = MaxBinLogFileSize
- }
-
- if cfg.MaxFileNum <= 0 {
- cfg.MaxFileNum = DefaultBinLogFileNum
- } else if cfg.MaxFileNum > MaxBinLogFileNum {
- cfg.MaxFileNum = MaxBinLogFileNum
- }
-}
+++ /dev/null
-# LedisDB configuration
-
-# Server listen address
-addr = "127.0.0.1:6380"
-
-# Server http listen address, set empty to disable
-http_addr = "127.0.0.1:11181"
-
-# Data store path, all ledisdb's data will be saved here
-data_dir = "/tmp/ledis_server"
-
-# Log server command, set empty to disable
-access_log = ""
-
-# Set slaveof to enable replication from master, empty, no replication
-slaveof = ""
-
-# Choose which backend storage to use, now support:
-#
-# leveldb
-# rocksdb
-# goleveldb
-# lmdb
-# boltdb
-# hyperleveldb
-# memory
-#
-db_name = "leveldb"
-
-[leveldb]
-compression = false
-block_size = 32768
-write_buffer_size = 67108864
-cache_size = 524288000
-max_open_files = 1024
-
-[lmdb]
-map_size = 524288000
-nosync = true
-
-[binlog]
-max_file_size = 0
-max_file_num = 0
-
-
+++ /dev/null
-package nodb
-
-import (
- "errors"
-)
-
-const (
- NoneType byte = 0
- KVType byte = 1
- HashType byte = 2
- HSizeType byte = 3
- ListType byte = 4
- LMetaType byte = 5
- ZSetType byte = 6
- ZSizeType byte = 7
- ZScoreType byte = 8
- BitType byte = 9
- BitMetaType byte = 10
- SetType byte = 11
- SSizeType byte = 12
-
- maxDataType byte = 100
-
- ExpTimeType byte = 101
- ExpMetaType byte = 102
-)
-
-var (
- TypeName = map[byte]string{
- KVType: "kv",
- HashType: "hash",
- HSizeType: "hsize",
- ListType: "list",
- LMetaType: "lmeta",
- ZSetType: "zset",
- ZSizeType: "zsize",
- ZScoreType: "zscore",
- BitType: "bit",
- BitMetaType: "bitmeta",
- SetType: "set",
- SSizeType: "ssize",
- ExpTimeType: "exptime",
- ExpMetaType: "expmeta",
- }
-)
-
-const (
- defaultScanCount int = 10
-)
-
-var (
- errKeySize = errors.New("invalid key size")
- errValueSize = errors.New("invalid value size")
- errHashFieldSize = errors.New("invalid hash field size")
- errSetMemberSize = errors.New("invalid set member size")
- errZSetMemberSize = errors.New("invalid zset member size")
- errExpireValue = errors.New("invalid expire value")
-)
-
-const (
- //we don't support too many databases
- MaxDBNumber uint8 = 16
-
- //max key size
- MaxKeySize int = 1024
-
- //max hash field size
- MaxHashFieldSize int = 1024
-
- //max zset member size
- MaxZSetMemberSize int = 1024
-
- //max set member size
- MaxSetMemberSize int = 1024
-
- //max value size
- MaxValueSize int = 10 * 1024 * 1024
-)
-
-var (
- ErrScoreMiss = errors.New("zset score miss")
-)
-
-const (
- BinLogTypeDeletion uint8 = 0x0
- BinLogTypePut uint8 = 0x1
- BinLogTypeCommand uint8 = 0x2
-)
-
-const (
- DBAutoCommit uint8 = 0x0
- DBInTransaction uint8 = 0x1
- DBInMulti uint8 = 0x2
-)
-
-var (
- Version = "0.1"
-)
+++ /dev/null
-// package nodb is a high performance embedded NoSQL.
-//
-// nodb supports various data structure like kv, list, hash and zset like redis.
-//
-// Other features include binlog replication, data with a limited time-to-live.
-//
-// Usage
-//
-// First create a nodb instance before use:
-//
-// l := nodb.Open(cfg)
-//
-// cfg is a Config instance which contains configuration for nodb use,
-// like DataDir (root directory for nodb working to store data).
-//
-// After you create a nodb instance, you can select a DB to store you data:
-//
-// db, _ := l.Select(0)
-//
-// DB must be selected by a index, nodb supports only 16 databases, so the index range is [0-15].
-//
-// KV
-//
-// KV is the most basic nodb type like any other key-value database.
-//
-// err := db.Set(key, value)
-// value, err := db.Get(key)
-//
-// List
-//
-// List is simply lists of values, sorted by insertion order.
-// You can push or pop value on the list head (left) or tail (right).
-//
-// err := db.LPush(key, value1)
-// err := db.RPush(key, value2)
-// value1, err := db.LPop(key)
-// value2, err := db.RPop(key)
-//
-// Hash
-//
-// Hash is a map between fields and values.
-//
-// n, err := db.HSet(key, field1, value1)
-// n, err := db.HSet(key, field2, value2)
-// value1, err := db.HGet(key, field1)
-// value2, err := db.HGet(key, field2)
-//
-// ZSet
-//
-// ZSet is a sorted collections of values.
-// Every member of zset is associated with score, a int64 value which used to sort, from smallest to greatest score.
-// Members are unique, but score may be same.
-//
-// n, err := db.ZAdd(key, ScorePair{score1, member1}, ScorePair{score2, member2})
-// ay, err := db.ZRangeByScore(key, minScore, maxScore, 0, -1)
-//
-// Binlog
-//
-// nodb supports binlog, so you can sync binlog to another server for replication. If you want to open binlog support, set UseBinLog to true in config.
-//
-package nodb
+++ /dev/null
-package nodb
-
-import (
- "bufio"
- "bytes"
- "encoding/binary"
- "io"
- "os"
-
- "github.com/siddontang/go-snappy/snappy"
-)
-
-//dump format
-// fileIndex(bigendian int64)|filePos(bigendian int64)
-// |keylen(bigendian int32)|key|valuelen(bigendian int32)|value......
-//
-//key and value are both compressed for fast transfer dump on network using snappy
-
-type BinLogAnchor struct {
- LogFileIndex int64
- LogPos int64
-}
-
-func (m *BinLogAnchor) WriteTo(w io.Writer) error {
- if err := binary.Write(w, binary.BigEndian, m.LogFileIndex); err != nil {
- return err
- }
-
- if err := binary.Write(w, binary.BigEndian, m.LogPos); err != nil {
- return err
- }
- return nil
-}
-
-func (m *BinLogAnchor) ReadFrom(r io.Reader) error {
- err := binary.Read(r, binary.BigEndian, &m.LogFileIndex)
- if err != nil {
- return err
- }
-
- err = binary.Read(r, binary.BigEndian, &m.LogPos)
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func (l *Nodb) DumpFile(path string) error {
- f, err := os.Create(path)
- if err != nil {
- return err
- }
- defer f.Close()
-
- return l.Dump(f)
-}
-
-func (l *Nodb) Dump(w io.Writer) error {
- m := new(BinLogAnchor)
-
- var err error
-
- l.wLock.Lock()
- defer l.wLock.Unlock()
-
- if l.binlog != nil {
- m.LogFileIndex = l.binlog.LogFileIndex()
- m.LogPos = l.binlog.LogFilePos()
- }
-
- wb := bufio.NewWriterSize(w, 4096)
- if err = m.WriteTo(wb); err != nil {
- return err
- }
-
- it := l.ldb.NewIterator()
- it.SeekToFirst()
-
- compressBuf := make([]byte, 4096)
-
- var key []byte
- var value []byte
- for ; it.Valid(); it.Next() {
- key = it.RawKey()
- value = it.RawValue()
-
- if key, err = snappy.Encode(compressBuf, key); err != nil {
- return err
- }
-
- if err = binary.Write(wb, binary.BigEndian, uint16(len(key))); err != nil {
- return err
- }
-
- if _, err = wb.Write(key); err != nil {
- return err
- }
-
- if value, err = snappy.Encode(compressBuf, value); err != nil {
- return err
- }
-
- if err = binary.Write(wb, binary.BigEndian, uint32(len(value))); err != nil {
- return err
- }
-
- if _, err = wb.Write(value); err != nil {
- return err
- }
- }
-
- if err = wb.Flush(); err != nil {
- return err
- }
-
- compressBuf = nil
-
- return nil
-}
-
-func (l *Nodb) LoadDumpFile(path string) (*BinLogAnchor, error) {
- f, err := os.Open(path)
- if err != nil {
- return nil, err
- }
- defer f.Close()
-
- return l.LoadDump(f)
-}
-
-func (l *Nodb) LoadDump(r io.Reader) (*BinLogAnchor, error) {
- l.wLock.Lock()
- defer l.wLock.Unlock()
-
- info := new(BinLogAnchor)
-
- rb := bufio.NewReaderSize(r, 4096)
-
- err := info.ReadFrom(rb)
- if err != nil {
- return nil, err
- }
-
- var keyLen uint16
- var valueLen uint32
-
- var keyBuf bytes.Buffer
- var valueBuf bytes.Buffer
-
- deKeyBuf := make([]byte, 4096)
- deValueBuf := make([]byte, 4096)
-
- var key, value []byte
-
- for {
- if err = binary.Read(rb, binary.BigEndian, &keyLen); err != nil && err != io.EOF {
- return nil, err
- } else if err == io.EOF {
- break
- }
-
- if _, err = io.CopyN(&keyBuf, rb, int64(keyLen)); err != nil {
- return nil, err
- }
-
- if key, err = snappy.Decode(deKeyBuf, keyBuf.Bytes()); err != nil {
- return nil, err
- }
-
- if err = binary.Read(rb, binary.BigEndian, &valueLen); err != nil {
- return nil, err
- }
-
- if _, err = io.CopyN(&valueBuf, rb, int64(valueLen)); err != nil {
- return nil, err
- }
-
- if value, err = snappy.Decode(deValueBuf, valueBuf.Bytes()); err != nil {
- return nil, err
- }
-
- if err = l.ldb.Put(key, value); err != nil {
- return nil, err
- }
-
- keyBuf.Reset()
- valueBuf.Reset()
- }
-
- deKeyBuf = nil
- deValueBuf = nil
-
- //if binlog enable, we will delete all binlogs and open a new one for handling simply
- if l.binlog != nil {
- l.binlog.PurgeAll()
- }
-
- return info, nil
-}
+++ /dev/null
-module gitea.com/lunny/nodb
-
-go 1.12
-
-require (
- gitea.com/lunny/log v0.0.0-20190322053110-01b5df579c4e
- github.com/mattn/go-sqlite3 v1.11.0 // indirect
- github.com/pelletier/go-toml v1.8.1
- github.com/siddontang/go-snappy v0.0.0-20140704025258-d8f7bb82a96d
- github.com/syndtr/goleveldb v1.0.0
-)
+++ /dev/null
-gitea.com/lunny/log v0.0.0-20190322053110-01b5df579c4e h1:r1en/D7xJmcY24VkHkjkcJFa+7ZWubVWPBrvsHkmHxk=
-gitea.com/lunny/log v0.0.0-20190322053110-01b5df579c4e/go.mod h1:uJEsN4LQpeGYRCjuPXPZBClU7N5pWzGuyF4uqLpE/e0=
-github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
-github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
-github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
-github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
-github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
-github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
-github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
-github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
-github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
-github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q=
-github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
-github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
-github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
-github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
-github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM=
-github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
-github.com/siddontang/go-snappy v0.0.0-20140704025258-d8f7bb82a96d h1:qQWKKOvHN7Q9c6GdmUteCef2F9ubxMpxY1IKwpIKz68=
-github.com/siddontang/go-snappy v0.0.0-20140704025258-d8f7bb82a96d/go.mod h1:vq0tzqLRu6TS7Id0wMo2N5QzJoKedVeovOpHjnykSzY=
-github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
-github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
-golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA=
-golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
-golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs=
-golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
-gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
-gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
-gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
-gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
-gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+++ /dev/null
-package nodb
-
-// todo, add info
-
-// type Keyspace struct {
-// Kvs int `json:"kvs"`
-// KvExpires int `json:"kv_expires"`
-
-// Lists int `json:"lists"`
-// ListExpires int `json:"list_expires"`
-
-// Bitmaps int `json:"bitmaps"`
-// BitmapExpires int `json:"bitmap_expires"`
-
-// ZSets int `json:"zsets"`
-// ZSetExpires int `json:"zset_expires"`
-
-// Hashes int `json:"hashes"`
-// HashExpires int `json:"hahsh_expires"`
-// }
-
-// type Info struct {
-// KeySpaces [MaxDBNumber]Keyspace
-// }
+++ /dev/null
-package nodb
-
-import (
- "errors"
- "fmt"
-)
-
-var (
- ErrNestMulti = errors.New("nest multi not supported")
- ErrMultiDone = errors.New("multi has been closed")
-)
-
-type Multi struct {
- *DB
-}
-
-func (db *DB) IsInMulti() bool {
- return db.status == DBInMulti
-}
-
-// begin a mutli to execute commands,
-// it will block any other write operations before you close the multi, unlike transaction, mutli can not rollback
-func (db *DB) Multi() (*Multi, error) {
- if db.IsInMulti() {
- return nil, ErrNestMulti
- }
-
- m := new(Multi)
-
- m.DB = new(DB)
- m.DB.status = DBInMulti
-
- m.DB.l = db.l
-
- m.l.wLock.Lock()
-
- m.DB.sdb = db.sdb
-
- m.DB.bucket = db.sdb
-
- m.DB.index = db.index
-
- m.DB.kvBatch = m.newBatch()
- m.DB.listBatch = m.newBatch()
- m.DB.hashBatch = m.newBatch()
- m.DB.zsetBatch = m.newBatch()
- m.DB.binBatch = m.newBatch()
- m.DB.setBatch = m.newBatch()
-
- return m, nil
-}
-
-func (m *Multi) newBatch() *batch {
- return m.l.newBatch(m.bucket.NewWriteBatch(), &multiBatchLocker{}, nil)
-}
-
-func (m *Multi) Close() error {
- if m.bucket == nil {
- return ErrMultiDone
- }
- m.l.wLock.Unlock()
- m.bucket = nil
- return nil
-}
-
-func (m *Multi) Select(index int) error {
- if index < 0 || index >= int(MaxDBNumber) {
- return fmt.Errorf("invalid db index %d", index)
- }
-
- m.DB.index = uint8(index)
- return nil
-}
+++ /dev/null
-package nodb
-
-import (
- "fmt"
- "sync"
- "time"
-
- "gitea.com/lunny/nodb/config"
- "gitea.com/lunny/nodb/store"
- "gitea.com/lunny/log"
-)
-
-type Nodb struct {
- cfg *config.Config
-
- ldb *store.DB
- dbs [MaxDBNumber]*DB
-
- quit chan struct{}
- jobs *sync.WaitGroup
-
- binlog *BinLog
-
- wLock sync.RWMutex //allow one write at same time
- commitLock sync.Mutex //allow one write commit at same time
-}
-
-func Open(cfg *config.Config) (*Nodb, error) {
- if len(cfg.DataDir) == 0 {
- cfg.DataDir = config.DefaultDataDir
- }
-
- ldb, err := store.Open(cfg)
- if err != nil {
- return nil, err
- }
-
- l := new(Nodb)
-
- l.quit = make(chan struct{})
- l.jobs = new(sync.WaitGroup)
-
- l.ldb = ldb
-
- if cfg.BinLog.MaxFileNum > 0 && cfg.BinLog.MaxFileSize > 0 {
- l.binlog, err = NewBinLog(cfg)
- if err != nil {
- return nil, err
- }
- } else {
- l.binlog = nil
- }
-
- for i := uint8(0); i < MaxDBNumber; i++ {
- l.dbs[i] = l.newDB(i)
- }
-
- l.activeExpireCycle()
-
- return l, nil
-}
-
-func (l *Nodb) Close() {
- close(l.quit)
- l.jobs.Wait()
-
- l.ldb.Close()
-
- if l.binlog != nil {
- l.binlog.Close()
- l.binlog = nil
- }
-}
-
-func (l *Nodb) Select(index int) (*DB, error) {
- if index < 0 || index >= int(MaxDBNumber) {
- return nil, fmt.Errorf("invalid db index %d", index)
- }
-
- return l.dbs[index], nil
-}
-
-func (l *Nodb) FlushAll() error {
- for index, db := range l.dbs {
- if _, err := db.FlushAll(); err != nil {
- log.Errorf("flush db %d error %s", index, err.Error())
- }
- }
-
- return nil
-}
-
-// very dangerous to use
-func (l *Nodb) DataDB() *store.DB {
- return l.ldb
-}
-
-func (l *Nodb) activeExpireCycle() {
- var executors []*elimination = make([]*elimination, len(l.dbs))
- for i, db := range l.dbs {
- executors[i] = db.newEliminator()
- }
-
- l.jobs.Add(1)
- go func() {
- tick := time.NewTicker(1 * time.Second)
- end := false
- done := make(chan struct{})
- for !end {
- select {
- case <-tick.C:
- go func() {
- for _, eli := range executors {
- eli.active()
- }
- done <- struct{}{}
- }()
- <-done
- case <-l.quit:
- end = true
- break
- }
- }
-
- tick.Stop()
- l.jobs.Done()
- }()
-}
+++ /dev/null
-package nodb
-
-import (
- "fmt"
- "sync"
-
- "gitea.com/lunny/nodb/store"
-)
-
-type ibucket interface {
- Get(key []byte) ([]byte, error)
-
- Put(key []byte, value []byte) error
- Delete(key []byte) error
-
- NewIterator() *store.Iterator
-
- NewWriteBatch() store.WriteBatch
-
- RangeIterator(min []byte, max []byte, rangeType uint8) *store.RangeLimitIterator
- RevRangeIterator(min []byte, max []byte, rangeType uint8) *store.RangeLimitIterator
- RangeLimitIterator(min []byte, max []byte, rangeType uint8, offset int, count int) *store.RangeLimitIterator
- RevRangeLimitIterator(min []byte, max []byte, rangeType uint8, offset int, count int) *store.RangeLimitIterator
-}
-
-type DB struct {
- l *Nodb
-
- sdb *store.DB
-
- bucket ibucket
-
- index uint8
-
- kvBatch *batch
- listBatch *batch
- hashBatch *batch
- zsetBatch *batch
- binBatch *batch
- setBatch *batch
-
- status uint8
-}
-
-func (l *Nodb) newDB(index uint8) *DB {
- d := new(DB)
-
- d.l = l
-
- d.sdb = l.ldb
-
- d.bucket = d.sdb
-
- d.status = DBAutoCommit
- d.index = index
-
- d.kvBatch = d.newBatch()
- d.listBatch = d.newBatch()
- d.hashBatch = d.newBatch()
- d.zsetBatch = d.newBatch()
- d.binBatch = d.newBatch()
- d.setBatch = d.newBatch()
-
- return d
-}
-
-func (db *DB) newBatch() *batch {
- return db.l.newBatch(db.bucket.NewWriteBatch(), &dbBatchLocker{l: &sync.Mutex{}, wrLock: &db.l.wLock}, nil)
-}
-
-func (db *DB) Index() int {
- return int(db.index)
-}
-
-func (db *DB) IsAutoCommit() bool {
- return db.status == DBAutoCommit
-}
-
-func (db *DB) FlushAll() (drop int64, err error) {
- all := [...](func() (int64, error)){
- db.flush,
- db.lFlush,
- db.hFlush,
- db.zFlush,
- db.bFlush,
- db.sFlush}
-
- for _, flush := range all {
- if n, e := flush(); e != nil {
- err = e
- return
- } else {
- drop += n
- }
- }
-
- return
-}
-
-func (db *DB) newEliminator() *elimination {
- eliminator := newEliminator(db)
-
- eliminator.regRetireContext(KVType, db.kvBatch, db.delete)
- eliminator.regRetireContext(ListType, db.listBatch, db.lDelete)
- eliminator.regRetireContext(HashType, db.hashBatch, db.hDelete)
- eliminator.regRetireContext(ZSetType, db.zsetBatch, db.zDelete)
- eliminator.regRetireContext(BitType, db.binBatch, db.bDelete)
- eliminator.regRetireContext(SetType, db.setBatch, db.sDelete)
-
- return eliminator
-}
-
-func (db *DB) flushRegion(t *batch, minKey []byte, maxKey []byte) (drop int64, err error) {
- it := db.bucket.RangeIterator(minKey, maxKey, store.RangeROpen)
- for ; it.Valid(); it.Next() {
- t.Delete(it.RawKey())
- drop++
- if drop&1023 == 0 {
- if err = t.Commit(); err != nil {
- return
- }
- }
- }
- it.Close()
- return
-}
-
-func (db *DB) flushType(t *batch, dataType byte) (drop int64, err error) {
- var deleteFunc func(t *batch, key []byte) int64
- var metaDataType byte
- switch dataType {
- case KVType:
- deleteFunc = db.delete
- metaDataType = KVType
- case ListType:
- deleteFunc = db.lDelete
- metaDataType = LMetaType
- case HashType:
- deleteFunc = db.hDelete
- metaDataType = HSizeType
- case ZSetType:
- deleteFunc = db.zDelete
- metaDataType = ZSizeType
- case BitType:
- deleteFunc = db.bDelete
- metaDataType = BitMetaType
- case SetType:
- deleteFunc = db.sDelete
- metaDataType = SSizeType
- default:
- return 0, fmt.Errorf("invalid data type: %s", TypeName[dataType])
- }
-
- var keys [][]byte
- keys, err = db.scan(metaDataType, nil, 1024, false, "")
- for len(keys) != 0 || err != nil {
- for _, key := range keys {
- deleteFunc(t, key)
- db.rmExpire(t, dataType, key)
-
- }
-
- if err = t.Commit(); err != nil {
- return
- } else {
- drop += int64(len(keys))
- }
- keys, err = db.scan(metaDataType, nil, 1024, false, "")
- }
- return
-}
+++ /dev/null
-package nodb
-
-import (
- "bufio"
- "bytes"
- "errors"
- "io"
- "os"
- "time"
-
- "gitea.com/lunny/log"
- "gitea.com/lunny/nodb/store/driver"
-)
-
-const (
- maxReplBatchNum = 100
- maxReplLogSize = 1 * 1024 * 1024
-)
-
-var (
- ErrSkipEvent = errors.New("skip to next event")
-)
-
-var (
- errInvalidBinLogEvent = errors.New("invalid binglog event")
- errInvalidBinLogFile = errors.New("invalid binlog file")
-)
-
-type replBatch struct {
- wb driver.IWriteBatch
- events [][]byte
- l *Nodb
-
- lastHead *BinLogHead
-}
-
-func (b *replBatch) Commit() error {
- b.l.commitLock.Lock()
- defer b.l.commitLock.Unlock()
-
- err := b.wb.Commit()
- if err != nil {
- b.Rollback()
- return err
- }
-
- if b.l.binlog != nil {
- if err = b.l.binlog.Log(b.events...); err != nil {
- b.Rollback()
- return err
- }
- }
-
- b.events = [][]byte{}
- b.lastHead = nil
-
- return nil
-}
-
-func (b *replBatch) Rollback() error {
- b.wb.Rollback()
- b.events = [][]byte{}
- b.lastHead = nil
- return nil
-}
-
-func (l *Nodb) replicateEvent(b *replBatch, event []byte) error {
- if len(event) == 0 {
- return errInvalidBinLogEvent
- }
-
- b.events = append(b.events, event)
-
- logType := uint8(event[0])
- switch logType {
- case BinLogTypePut:
- return l.replicatePutEvent(b, event)
- case BinLogTypeDeletion:
- return l.replicateDeleteEvent(b, event)
- default:
- return errInvalidBinLogEvent
- }
-}
-
-func (l *Nodb) replicatePutEvent(b *replBatch, event []byte) error {
- key, value, err := decodeBinLogPut(event)
- if err != nil {
- return err
- }
-
- b.wb.Put(key, value)
-
- return nil
-}
-
-func (l *Nodb) replicateDeleteEvent(b *replBatch, event []byte) error {
- key, err := decodeBinLogDelete(event)
- if err != nil {
- return err
- }
-
- b.wb.Delete(key)
-
- return nil
-}
-
-func ReadEventFromReader(rb io.Reader, f func(head *BinLogHead, event []byte) error) error {
- head := &BinLogHead{}
- var err error
-
- for {
- if err = head.Read(rb); err != nil {
- if err == io.EOF {
- break
- } else {
- return err
- }
- }
-
- var dataBuf bytes.Buffer
-
- if _, err = io.CopyN(&dataBuf, rb, int64(head.PayloadLen)); err != nil {
- return err
- }
-
- err = f(head, dataBuf.Bytes())
- if err != nil && err != ErrSkipEvent {
- return err
- }
- }
-
- return nil
-}
-
-func (l *Nodb) ReplicateFromReader(rb io.Reader) error {
- b := new(replBatch)
-
- b.wb = l.ldb.NewWriteBatch()
- b.l = l
-
- f := func(head *BinLogHead, event []byte) error {
- if b.lastHead == nil {
- b.lastHead = head
- } else if !b.lastHead.InSameBatch(head) {
- if err := b.Commit(); err != nil {
- log.Fatalf("replication error %s, skip to next", err.Error())
- return ErrSkipEvent
- }
- b.lastHead = head
- }
-
- err := l.replicateEvent(b, event)
- if err != nil {
- log.Fatalf("replication error %s, skip to next", err.Error())
- return ErrSkipEvent
- }
- return nil
- }
-
- err := ReadEventFromReader(rb, f)
- if err != nil {
- b.Rollback()
- return err
- }
- return b.Commit()
-}
-
-func (l *Nodb) ReplicateFromData(data []byte) error {
- rb := bytes.NewReader(data)
-
- err := l.ReplicateFromReader(rb)
-
- return err
-}
-
-func (l *Nodb) ReplicateFromBinLog(filePath string) error {
- f, err := os.Open(filePath)
- if err != nil {
- return err
- }
-
- rb := bufio.NewReaderSize(f, 4096)
-
- err = l.ReplicateFromReader(rb)
-
- f.Close()
-
- return err
-}
-
-// try to read events, if no events read, try to wait the new event singal until timeout seconds
-func (l *Nodb) ReadEventsToTimeout(info *BinLogAnchor, w io.Writer, timeout int) (n int, err error) {
- lastIndex := info.LogFileIndex
- lastPos := info.LogPos
-
- n = 0
- if l.binlog == nil {
- //binlog not supported
- info.LogFileIndex = 0
- info.LogPos = 0
- return
- }
-
- n, err = l.ReadEventsTo(info, w)
- if err == nil && info.LogFileIndex == lastIndex && info.LogPos == lastPos {
- //no events read
- select {
- case <-l.binlog.Wait():
- case <-time.After(time.Duration(timeout) * time.Second):
- }
- return l.ReadEventsTo(info, w)
- }
- return
-}
-
-func (l *Nodb) ReadEventsTo(info *BinLogAnchor, w io.Writer) (n int, err error) {
- n = 0
- if l.binlog == nil {
- //binlog not supported
- info.LogFileIndex = 0
- info.LogPos = 0
- return
- }
-
- index := info.LogFileIndex
- offset := info.LogPos
-
- filePath := l.binlog.FormatLogFilePath(index)
-
- var f *os.File
- f, err = os.Open(filePath)
- if os.IsNotExist(err) {
- lastIndex := l.binlog.LogFileIndex()
-
- if index == lastIndex {
- //no binlog at all
- info.LogPos = 0
- } else {
- //slave binlog info had lost
- info.LogFileIndex = -1
- }
- }
-
- if err != nil {
- if os.IsNotExist(err) {
- err = nil
- }
- return
- }
-
- defer f.Close()
-
- var fileSize int64
- st, _ := f.Stat()
- fileSize = st.Size()
-
- if fileSize == info.LogPos {
- return
- }
-
- if _, err = f.Seek(offset, os.SEEK_SET); err != nil {
- //may be invliad seek offset
- return
- }
-
- var lastHead *BinLogHead = nil
-
- head := &BinLogHead{}
-
- batchNum := 0
-
- for {
- if err = head.Read(f); err != nil {
- if err == io.EOF {
- //we will try to use next binlog
- if index < l.binlog.LogFileIndex() {
- info.LogFileIndex += 1
- info.LogPos = 0
- }
- err = nil
- return
- } else {
- return
- }
-
- }
-
- if lastHead == nil {
- lastHead = head
- batchNum++
- } else if !lastHead.InSameBatch(head) {
- lastHead = head
- batchNum++
- if batchNum > maxReplBatchNum || n > maxReplLogSize {
- return
- }
- }
-
- if err = head.Write(w); err != nil {
- return
- }
-
- if _, err = io.CopyN(w, f, int64(head.PayloadLen)); err != nil {
- return
- }
-
- n += (head.Len() + int(head.PayloadLen))
- info.LogPos = info.LogPos + int64(head.Len()) + int64(head.PayloadLen)
- }
-
- return
-}
+++ /dev/null
-package nodb
-
-import (
- "bytes"
- "errors"
- "regexp"
-
- "gitea.com/lunny/nodb/store"
-)
-
-var errDataType = errors.New("error data type")
-var errMetaKey = errors.New("error meta key")
-
-// Seek search the prefix key
-func (db *DB) Seek(key []byte) (*store.Iterator, error) {
- return db.seek(KVType, key)
-}
-
-func (db *DB) seek(dataType byte, key []byte) (*store.Iterator, error) {
- var minKey []byte
- var err error
-
- if len(key) > 0 {
- if err = checkKeySize(key); err != nil {
- return nil, err
- }
- if minKey, err = db.encodeMetaKey(dataType, key); err != nil {
- return nil, err
- }
-
- } else {
- if minKey, err = db.encodeMinKey(dataType); err != nil {
- return nil, err
- }
- }
-
- it := db.bucket.NewIterator()
- it.Seek(minKey)
- return it, nil
-}
-
-func (db *DB) MaxKey() ([]byte, error) {
- return db.encodeMaxKey(KVType)
-}
-
-func (db *DB) Key(it *store.Iterator) ([]byte, error) {
- return db.decodeMetaKey(KVType, it.Key())
-}
-
-func (db *DB) scan(dataType byte, key []byte, count int, inclusive bool, match string) ([][]byte, error) {
- var minKey, maxKey []byte
- var err error
- var r *regexp.Regexp
-
- if len(match) > 0 {
- if r, err = regexp.Compile(match); err != nil {
- return nil, err
- }
- }
-
- if len(key) > 0 {
- if err = checkKeySize(key); err != nil {
- return nil, err
- }
- if minKey, err = db.encodeMetaKey(dataType, key); err != nil {
- return nil, err
- }
-
- } else {
- if minKey, err = db.encodeMinKey(dataType); err != nil {
- return nil, err
- }
- }
-
- if maxKey, err = db.encodeMaxKey(dataType); err != nil {
- return nil, err
- }
-
- if count <= 0 {
- count = defaultScanCount
- }
-
- v := make([][]byte, 0, count)
-
- it := db.bucket.NewIterator()
- it.Seek(minKey)
-
- if !inclusive {
- if it.Valid() && bytes.Equal(it.RawKey(), minKey) {
- it.Next()
- }
- }
-
- for i := 0; it.Valid() && i < count && bytes.Compare(it.RawKey(), maxKey) < 0; it.Next() {
- if k, err := db.decodeMetaKey(dataType, it.Key()); err != nil {
- continue
- } else if r != nil && !r.Match(k) {
- continue
- } else {
- v = append(v, k)
- i++
- }
- }
- it.Close()
- return v, nil
-}
-
-func (db *DB) encodeMinKey(dataType byte) ([]byte, error) {
- return db.encodeMetaKey(dataType, nil)
-}
-
-func (db *DB) encodeMaxKey(dataType byte) ([]byte, error) {
- k, err := db.encodeMetaKey(dataType, nil)
- if err != nil {
- return nil, err
- }
- k[len(k)-1] = dataType + 1
- return k, nil
-}
-
-func (db *DB) encodeMetaKey(dataType byte, key []byte) ([]byte, error) {
- switch dataType {
- case KVType:
- return db.encodeKVKey(key), nil
- case LMetaType:
- return db.lEncodeMetaKey(key), nil
- case HSizeType:
- return db.hEncodeSizeKey(key), nil
- case ZSizeType:
- return db.zEncodeSizeKey(key), nil
- case BitMetaType:
- return db.bEncodeMetaKey(key), nil
- case SSizeType:
- return db.sEncodeSizeKey(key), nil
- default:
- return nil, errDataType
- }
-}
-func (db *DB) decodeMetaKey(dataType byte, ek []byte) ([]byte, error) {
- if len(ek) < 2 || ek[0] != db.index || ek[1] != dataType {
- return nil, errMetaKey
- }
- return ek[2:], nil
-}
+++ /dev/null
-package store
-
-import (
- "gitea.com/lunny/nodb/store/driver"
-)
-
-type DB struct {
- driver.IDB
-}
-
-func (db *DB) NewIterator() *Iterator {
- it := new(Iterator)
- it.it = db.IDB.NewIterator()
-
- return it
-}
-
-func (db *DB) NewWriteBatch() WriteBatch {
- return db.IDB.NewWriteBatch()
-}
-
-func (db *DB) NewSnapshot() (*Snapshot, error) {
- var err error
- s := &Snapshot{}
- if s.ISnapshot, err = db.IDB.NewSnapshot(); err != nil {
- return nil, err
- }
-
- return s, nil
-}
-
-func (db *DB) RangeIterator(min []byte, max []byte, rangeType uint8) *RangeLimitIterator {
- return NewRangeLimitIterator(db.NewIterator(), &Range{min, max, rangeType}, &Limit{0, -1})
-}
-
-func (db *DB) RevRangeIterator(min []byte, max []byte, rangeType uint8) *RangeLimitIterator {
- return NewRevRangeLimitIterator(db.NewIterator(), &Range{min, max, rangeType}, &Limit{0, -1})
-}
-
-//count < 0, unlimit.
-//
-//offset must >= 0, if < 0, will get nothing.
-func (db *DB) RangeLimitIterator(min []byte, max []byte, rangeType uint8, offset int, count int) *RangeLimitIterator {
- return NewRangeLimitIterator(db.NewIterator(), &Range{min, max, rangeType}, &Limit{offset, count})
-}
-
-//count < 0, unlimit.
-//
-//offset must >= 0, if < 0, will get nothing.
-func (db *DB) RevRangeLimitIterator(min []byte, max []byte, rangeType uint8, offset int, count int) *RangeLimitIterator {
- return NewRevRangeLimitIterator(db.NewIterator(), &Range{min, max, rangeType}, &Limit{offset, count})
-}
-
-func (db *DB) Begin() (*Tx, error) {
- tx, err := db.IDB.Begin()
- if err != nil {
- return nil, err
- }
-
- return &Tx{tx}, nil
-}
+++ /dev/null
-package driver
-
-type BatchPuter interface {
- BatchPut([]Write) error
-}
-
-type Write struct {
- Key []byte
- Value []byte
-}
-
-type WriteBatch struct {
- batch BatchPuter
- wb []Write
-}
-
-func (w *WriteBatch) Put(key, value []byte) {
- if value == nil {
- value = []byte{}
- }
- w.wb = append(w.wb, Write{key, value})
-}
-
-func (w *WriteBatch) Delete(key []byte) {
- w.wb = append(w.wb, Write{key, nil})
-}
-
-func (w *WriteBatch) Commit() error {
- return w.batch.BatchPut(w.wb)
-}
-
-func (w *WriteBatch) Rollback() error {
- w.wb = w.wb[0:0]
- return nil
-}
-
-func NewWriteBatch(puter BatchPuter) IWriteBatch {
- return &WriteBatch{puter, []Write{}}
-}
+++ /dev/null
-package driver
-
-import (
- "errors"
-)
-
-var (
- ErrTxSupport = errors.New("transaction is not supported")
-)
-
-type IDB interface {
- Close() error
-
- Get(key []byte) ([]byte, error)
-
- Put(key []byte, value []byte) error
- Delete(key []byte) error
-
- NewIterator() IIterator
-
- NewWriteBatch() IWriteBatch
-
- NewSnapshot() (ISnapshot, error)
-
- Begin() (Tx, error)
-}
-
-type ISnapshot interface {
- Get(key []byte) ([]byte, error)
- NewIterator() IIterator
- Close()
-}
-
-type IIterator interface {
- Close() error
-
- First()
- Last()
- Seek(key []byte)
-
- Next()
- Prev()
-
- Valid() bool
-
- Key() []byte
- Value() []byte
-}
-
-type IWriteBatch interface {
- Put(key []byte, value []byte)
- Delete(key []byte)
- Commit() error
- Rollback() error
-}
-
-type Tx interface {
- Get(key []byte) ([]byte, error)
- Put(key []byte, value []byte) error
- Delete(key []byte) error
-
- NewIterator() IIterator
- NewWriteBatch() IWriteBatch
-
- Commit() error
- Rollback() error
-}
+++ /dev/null
-package driver
-
-import (
- "fmt"
-
- "gitea.com/lunny/nodb/config"
-)
-
-type Store interface {
- String() string
- Open(path string, cfg *config.Config) (IDB, error)
- Repair(path string, cfg *config.Config) error
-}
-
-var dbs = map[string]Store{}
-
-func Register(s Store) {
- name := s.String()
- if _, ok := dbs[name]; ok {
- panic(fmt.Errorf("store %s is registered", s))
- }
-
- dbs[name] = s
-}
-
-func ListStores() []string {
- s := []string{}
- for k, _ := range dbs {
- s = append(s, k)
- }
-
- return s
-}
-
-func GetStore(cfg *config.Config) (Store, error) {
- if len(cfg.DBName) == 0 {
- cfg.DBName = config.DefaultDBName
- }
-
- s, ok := dbs[cfg.DBName]
- if !ok {
- return nil, fmt.Errorf("store %s is not registered", cfg.DBName)
- }
-
- return s, nil
-}
+++ /dev/null
-package goleveldb
-
-import (
- "github.com/syndtr/goleveldb/leveldb"
-)
-
-type WriteBatch struct {
- db *DB
- wbatch *leveldb.Batch
-}
-
-func (w *WriteBatch) Put(key, value []byte) {
- w.wbatch.Put(key, value)
-}
-
-func (w *WriteBatch) Delete(key []byte) {
- w.wbatch.Delete(key)
-}
-
-func (w *WriteBatch) Commit() error {
- return w.db.db.Write(w.wbatch, nil)
-}
-
-func (w *WriteBatch) Rollback() error {
- w.wbatch.Reset()
- return nil
-}
+++ /dev/null
-package goleveldb
-
-const DBName = "goleveldb"
-const MemDBName = "memory"
+++ /dev/null
-package goleveldb
-
-import (
- "github.com/syndtr/goleveldb/leveldb"
- "github.com/syndtr/goleveldb/leveldb/cache"
- "github.com/syndtr/goleveldb/leveldb/filter"
- "github.com/syndtr/goleveldb/leveldb/opt"
- "github.com/syndtr/goleveldb/leveldb/storage"
-
- "gitea.com/lunny/nodb/config"
- "gitea.com/lunny/nodb/store/driver"
-
- "os"
-)
-
-const defaultFilterBits int = 10
-
-type Store struct {
-}
-
-func (s Store) String() string {
- return DBName
-}
-
-type MemStore struct {
-}
-
-func (s MemStore) String() string {
- return MemDBName
-}
-
-type DB struct {
- path string
-
- cfg *config.LevelDBConfig
-
- db *leveldb.DB
-
- opts *opt.Options
-
- iteratorOpts *opt.ReadOptions
-
- cache cache.Cache
-
- filter filter.Filter
-}
-
-func (s Store) Open(path string, cfg *config.Config) (driver.IDB, error) {
- if err := os.MkdirAll(path, os.ModePerm); err != nil {
- return nil, err
- }
-
- db := new(DB)
- db.path = path
- db.cfg = &cfg.LevelDB
-
- db.initOpts()
-
- var err error
- db.db, err = leveldb.OpenFile(db.path, db.opts)
-
- if err != nil {
- return nil, err
- }
-
- return db, nil
-}
-
-func (s Store) Repair(path string, cfg *config.Config) error {
- db, err := leveldb.RecoverFile(path, newOptions(&cfg.LevelDB))
- if err != nil {
- return err
- }
-
- db.Close()
- return nil
-}
-
-func (s MemStore) Open(path string, cfg *config.Config) (driver.IDB, error) {
- db := new(DB)
- db.path = path
- db.cfg = &cfg.LevelDB
-
- db.initOpts()
-
- var err error
- db.db, err = leveldb.Open(storage.NewMemStorage(), db.opts)
- if err != nil {
- return nil, err
- }
-
- return db, nil
-}
-
-func (s MemStore) Repair(path string, cfg *config.Config) error {
- return nil
-}
-
-func (db *DB) initOpts() {
- db.opts = newOptions(db.cfg)
-
- db.iteratorOpts = &opt.ReadOptions{}
- db.iteratorOpts.DontFillCache = true
-}
-
-func newOptions(cfg *config.LevelDBConfig) *opt.Options {
- opts := &opt.Options{}
- opts.ErrorIfMissing = false
-
- cfg.Adjust()
-
- //opts.BlockCacher = cache.NewLRU(cfg.CacheSize)
- opts.BlockCacheCapacity = cfg.CacheSize
-
- //we must use bloomfilter
- opts.Filter = filter.NewBloomFilter(defaultFilterBits)
-
- if !cfg.Compression {
- opts.Compression = opt.NoCompression
- } else {
- opts.Compression = opt.SnappyCompression
- }
-
- opts.BlockSize = cfg.BlockSize
- opts.WriteBuffer = cfg.WriteBufferSize
-
- return opts
-}
-
-func (db *DB) Close() error {
- return db.db.Close()
-}
-
-func (db *DB) Put(key, value []byte) error {
- return db.db.Put(key, value, nil)
-}
-
-func (db *DB) Get(key []byte) ([]byte, error) {
- v, err := db.db.Get(key, nil)
- if err == leveldb.ErrNotFound {
- return nil, nil
- }
- return v, nil
-}
-
-func (db *DB) Delete(key []byte) error {
- return db.db.Delete(key, nil)
-}
-
-func (db *DB) NewWriteBatch() driver.IWriteBatch {
- wb := &WriteBatch{
- db: db,
- wbatch: new(leveldb.Batch),
- }
- return wb
-}
-
-func (db *DB) NewIterator() driver.IIterator {
- it := &Iterator{
- db.db.NewIterator(nil, db.iteratorOpts),
- }
-
- return it
-}
-
-func (db *DB) Begin() (driver.Tx, error) {
- return nil, driver.ErrTxSupport
-}
-
-func (db *DB) NewSnapshot() (driver.ISnapshot, error) {
- snapshot, err := db.db.GetSnapshot()
- if err != nil {
- return nil, err
- }
-
- s := &Snapshot{
- db: db,
- snp: snapshot,
- }
-
- return s, nil
-}
-
-func init() {
- driver.Register(Store{})
- driver.Register(MemStore{})
-}
+++ /dev/null
-package goleveldb
-
-import (
- "github.com/syndtr/goleveldb/leveldb/iterator"
-)
-
-type Iterator struct {
- it iterator.Iterator
-}
-
-func (it *Iterator) Key() []byte {
- return it.it.Key()
-}
-
-func (it *Iterator) Value() []byte {
- return it.it.Value()
-}
-
-func (it *Iterator) Close() error {
- if it.it != nil {
- it.it.Release()
- it.it = nil
- }
- return nil
-}
-
-func (it *Iterator) Valid() bool {
- return it.it.Valid()
-}
-
-func (it *Iterator) Next() {
- it.it.Next()
-}
-
-func (it *Iterator) Prev() {
- it.it.Prev()
-}
-
-func (it *Iterator) First() {
- it.it.First()
-}
-
-func (it *Iterator) Last() {
- it.it.Last()
-}
-
-func (it *Iterator) Seek(key []byte) {
- it.it.Seek(key)
-}
+++ /dev/null
-package goleveldb
-
-import (
- "gitea.com/lunny/nodb/store/driver"
- "github.com/syndtr/goleveldb/leveldb"
-)
-
-type Snapshot struct {
- db *DB
- snp *leveldb.Snapshot
-}
-
-func (s *Snapshot) Get(key []byte) ([]byte, error) {
- return s.snp.Get(key, s.db.iteratorOpts)
-}
-
-func (s *Snapshot) NewIterator() driver.IIterator {
- it := &Iterator{
- s.snp.NewIterator(nil, s.db.iteratorOpts),
- }
- return it
-}
-
-func (s *Snapshot) Close() {
- s.snp.Release()
-}
+++ /dev/null
-package store
-
-import (
- "bytes"
-
- "gitea.com/lunny/nodb/store/driver"
-)
-
-const (
- IteratorForward uint8 = 0
- IteratorBackward uint8 = 1
-)
-
-const (
- RangeClose uint8 = 0x00
- RangeLOpen uint8 = 0x01
- RangeROpen uint8 = 0x10
- RangeOpen uint8 = 0x11
-)
-
-// min must less or equal than max
-//
-// range type:
-//
-// close: [min, max]
-// open: (min, max)
-// lopen: (min, max]
-// ropen: [min, max)
-//
-type Range struct {
- Min []byte
- Max []byte
-
- Type uint8
-}
-
-type Limit struct {
- Offset int
- Count int
-}
-
-type Iterator struct {
- it driver.IIterator
-}
-
-// Returns a copy of key.
-func (it *Iterator) Key() []byte {
- k := it.it.Key()
- if k == nil {
- return nil
- }
-
- return append([]byte{}, k...)
-}
-
-// Returns a copy of value.
-func (it *Iterator) Value() []byte {
- v := it.it.Value()
- if v == nil {
- return nil
- }
-
- return append([]byte{}, v...)
-}
-
-// Returns a reference of key.
-// you must be careful that it will be changed after next iterate.
-func (it *Iterator) RawKey() []byte {
- return it.it.Key()
-}
-
-// Returns a reference of value.
-// you must be careful that it will be changed after next iterate.
-func (it *Iterator) RawValue() []byte {
- return it.it.Value()
-}
-
-// Copy key to b, if b len is small or nil, returns a new one.
-func (it *Iterator) BufKey(b []byte) []byte {
- k := it.RawKey()
- if k == nil {
- return nil
- }
- if b == nil {
- b = []byte{}
- }
-
- b = b[0:0]
- return append(b, k...)
-}
-
-// Copy value to b, if b len is small or nil, returns a new one.
-func (it *Iterator) BufValue(b []byte) []byte {
- v := it.RawValue()
- if v == nil {
- return nil
- }
-
- if b == nil {
- b = []byte{}
- }
-
- b = b[0:0]
- return append(b, v...)
-}
-
-func (it *Iterator) Close() {
- if it.it != nil {
- it.it.Close()
- it.it = nil
- }
-}
-
-func (it *Iterator) Valid() bool {
- return it.it.Valid()
-}
-
-func (it *Iterator) Next() {
- it.it.Next()
-}
-
-func (it *Iterator) Prev() {
- it.it.Prev()
-}
-
-func (it *Iterator) SeekToFirst() {
- it.it.First()
-}
-
-func (it *Iterator) SeekToLast() {
- it.it.Last()
-}
-
-func (it *Iterator) Seek(key []byte) {
- it.it.Seek(key)
-}
-
-// Finds by key, if not found, nil returns.
-func (it *Iterator) Find(key []byte) []byte {
- it.Seek(key)
- if it.Valid() {
- k := it.RawKey()
- if k == nil {
- return nil
- } else if bytes.Equal(k, key) {
- return it.Value()
- }
- }
-
- return nil
-}
-
-// Finds by key, if not found, nil returns, else a reference of value returns.
-// you must be careful that it will be changed after next iterate.
-func (it *Iterator) RawFind(key []byte) []byte {
- it.Seek(key)
- if it.Valid() {
- k := it.RawKey()
- if k == nil {
- return nil
- } else if bytes.Equal(k, key) {
- return it.RawValue()
- }
- }
-
- return nil
-}
-
-type RangeLimitIterator struct {
- it *Iterator
-
- r *Range
- l *Limit
-
- step int
-
- //0 for IteratorForward, 1 for IteratorBackward
- direction uint8
-}
-
-func (it *RangeLimitIterator) Key() []byte {
- return it.it.Key()
-}
-
-func (it *RangeLimitIterator) Value() []byte {
- return it.it.Value()
-}
-
-func (it *RangeLimitIterator) RawKey() []byte {
- return it.it.RawKey()
-}
-
-func (it *RangeLimitIterator) RawValue() []byte {
- return it.it.RawValue()
-}
-
-func (it *RangeLimitIterator) BufKey(b []byte) []byte {
- return it.it.BufKey(b)
-}
-
-func (it *RangeLimitIterator) BufValue(b []byte) []byte {
- return it.it.BufValue(b)
-}
-
-func (it *RangeLimitIterator) Valid() bool {
- if it.l.Offset < 0 {
- return false
- } else if !it.it.Valid() {
- return false
- } else if it.l.Count >= 0 && it.step >= it.l.Count {
- return false
- }
-
- if it.direction == IteratorForward {
- if it.r.Max != nil {
- r := bytes.Compare(it.it.RawKey(), it.r.Max)
- if it.r.Type&RangeROpen > 0 {
- return !(r >= 0)
- } else {
- return !(r > 0)
- }
- }
- } else {
- if it.r.Min != nil {
- r := bytes.Compare(it.it.RawKey(), it.r.Min)
- if it.r.Type&RangeLOpen > 0 {
- return !(r <= 0)
- } else {
- return !(r < 0)
- }
- }
- }
-
- return true
-}
-
-func (it *RangeLimitIterator) Next() {
- it.step++
-
- if it.direction == IteratorForward {
- it.it.Next()
- } else {
- it.it.Prev()
- }
-}
-
-func (it *RangeLimitIterator) Close() {
- it.it.Close()
-}
-
-func NewRangeLimitIterator(i *Iterator, r *Range, l *Limit) *RangeLimitIterator {
- return rangeLimitIterator(i, r, l, IteratorForward)
-}
-
-func NewRevRangeLimitIterator(i *Iterator, r *Range, l *Limit) *RangeLimitIterator {
- return rangeLimitIterator(i, r, l, IteratorBackward)
-}
-
-func NewRangeIterator(i *Iterator, r *Range) *RangeLimitIterator {
- return rangeLimitIterator(i, r, &Limit{0, -1}, IteratorForward)
-}
-
-func NewRevRangeIterator(i *Iterator, r *Range) *RangeLimitIterator {
- return rangeLimitIterator(i, r, &Limit{0, -1}, IteratorBackward)
-}
-
-func rangeLimitIterator(i *Iterator, r *Range, l *Limit, direction uint8) *RangeLimitIterator {
- it := new(RangeLimitIterator)
-
- it.it = i
-
- it.r = r
- it.l = l
- it.direction = direction
-
- it.step = 0
-
- if l.Offset < 0 {
- return it
- }
-
- if direction == IteratorForward {
- if r.Min == nil {
- it.it.SeekToFirst()
- } else {
- it.it.Seek(r.Min)
-
- if r.Type&RangeLOpen > 0 {
- if it.it.Valid() && bytes.Equal(it.it.RawKey(), r.Min) {
- it.it.Next()
- }
- }
- }
- } else {
- if r.Max == nil {
- it.it.SeekToLast()
- } else {
- it.it.Seek(r.Max)
-
- if !it.it.Valid() {
- it.it.SeekToLast()
- } else {
- if !bytes.Equal(it.it.RawKey(), r.Max) {
- it.it.Prev()
- }
- }
-
- if r.Type&RangeROpen > 0 {
- if it.it.Valid() && bytes.Equal(it.it.RawKey(), r.Max) {
- it.it.Prev()
- }
- }
- }
- }
-
- for i := 0; i < l.Offset; i++ {
- if it.it.Valid() {
- if it.direction == IteratorForward {
- it.it.Next()
- } else {
- it.it.Prev()
- }
- }
- }
-
- return it
-}
+++ /dev/null
-package store
-
-import (
- "gitea.com/lunny/nodb/store/driver"
-)
-
-type Snapshot struct {
- driver.ISnapshot
-}
-
-func (s *Snapshot) NewIterator() *Iterator {
- it := new(Iterator)
- it.it = s.ISnapshot.NewIterator()
-
- return it
-}
+++ /dev/null
-package store
-
-import (
- "fmt"
- "os"
- "path"
- "gitea.com/lunny/nodb/config"
- "gitea.com/lunny/nodb/store/driver"
-
- _ "gitea.com/lunny/nodb/store/goleveldb"
-)
-
-func getStorePath(cfg *config.Config) string {
- return path.Join(cfg.DataDir, fmt.Sprintf("%s_data", cfg.DBName))
-}
-
-func Open(cfg *config.Config) (*DB, error) {
- s, err := driver.GetStore(cfg)
- if err != nil {
- return nil, err
- }
-
- path := getStorePath(cfg)
-
- if err := os.MkdirAll(path, os.ModePerm); err != nil {
- return nil, err
- }
-
- idb, err := s.Open(path, cfg)
- if err != nil {
- return nil, err
- }
-
- db := &DB{idb}
-
- return db, nil
-}
-
-func Repair(cfg *config.Config) error {
- s, err := driver.GetStore(cfg)
- if err != nil {
- return err
- }
-
- path := getStorePath(cfg)
-
- return s.Repair(path, cfg)
-}
-
-func init() {
-}
+++ /dev/null
-package store
-
-import (
- "gitea.com/lunny/nodb/store/driver"
-)
-
-type Tx struct {
- driver.Tx
-}
-
-func (tx *Tx) NewIterator() *Iterator {
- it := new(Iterator)
- it.it = tx.Tx.NewIterator()
-
- return it
-}
-
-func (tx *Tx) NewWriteBatch() WriteBatch {
- return tx.Tx.NewWriteBatch()
-}
-
-func (tx *Tx) RangeIterator(min []byte, max []byte, rangeType uint8) *RangeLimitIterator {
- return NewRangeLimitIterator(tx.NewIterator(), &Range{min, max, rangeType}, &Limit{0, -1})
-}
-
-func (tx *Tx) RevRangeIterator(min []byte, max []byte, rangeType uint8) *RangeLimitIterator {
- return NewRevRangeLimitIterator(tx.NewIterator(), &Range{min, max, rangeType}, &Limit{0, -1})
-}
-
-//count < 0, unlimit.
-//
-//offset must >= 0, if < 0, will get nothing.
-func (tx *Tx) RangeLimitIterator(min []byte, max []byte, rangeType uint8, offset int, count int) *RangeLimitIterator {
- return NewRangeLimitIterator(tx.NewIterator(), &Range{min, max, rangeType}, &Limit{offset, count})
-}
-
-//count < 0, unlimit.
-//
-//offset must >= 0, if < 0, will get nothing.
-func (tx *Tx) RevRangeLimitIterator(min []byte, max []byte, rangeType uint8, offset int, count int) *RangeLimitIterator {
- return NewRevRangeLimitIterator(tx.NewIterator(), &Range{min, max, rangeType}, &Limit{offset, count})
-}
+++ /dev/null
-package store
-
-import (
- "gitea.com/lunny/nodb/store/driver"
-)
-
-type WriteBatch interface {
- driver.IWriteBatch
-}
+++ /dev/null
-package nodb
-
-import (
- "encoding/binary"
- "errors"
- "sort"
- "time"
-
- "gitea.com/lunny/nodb/store"
-)
-
-const (
- OPand uint8 = iota + 1
- OPor
- OPxor
- OPnot
-)
-
-type BitPair struct {
- Pos int32
- Val uint8
-}
-
-type segBitInfo struct {
- Seq uint32
- Off uint32
- Val uint8
-}
-
-type segBitInfoArray []segBitInfo
-
-const (
- // byte
- segByteWidth uint32 = 9
- segByteSize uint32 = 1 << segByteWidth
-
- // bit
- segBitWidth uint32 = segByteWidth + 3
- segBitSize uint32 = segByteSize << 3
-
- maxByteSize uint32 = 8 << 20
- maxSegCount uint32 = maxByteSize / segByteSize
-
- minSeq uint32 = 0
- maxSeq uint32 = uint32((maxByteSize << 3) - 1)
-)
-
-var bitsInByte = [256]int32{0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3,
- 4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 1, 2, 2, 3, 2, 3,
- 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4,
- 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4,
- 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4,
- 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 1, 2,
- 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3,
- 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
- 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4,
- 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6,
- 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5,
- 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8}
-
-var fillBits = [...]uint8{1, 3, 7, 15, 31, 63, 127, 255}
-
-var emptySegment []byte = make([]byte, segByteSize, segByteSize)
-
-var fillSegment []byte = func() []byte {
- data := make([]byte, segByteSize, segByteSize)
- for i := uint32(0); i < segByteSize; i++ {
- data[i] = 0xff
- }
- return data
-}()
-
-var errBinKey = errors.New("invalid bin key")
-var errOffset = errors.New("invalid offset")
-var errDuplicatePos = errors.New("duplicate bit pos")
-
-func getBit(sz []byte, offset uint32) uint8 {
- index := offset >> 3
- if index >= uint32(len(sz)) {
- return 0 // error("overflow")
- }
-
- offset -= index << 3
- return sz[index] >> offset & 1
-}
-
-func setBit(sz []byte, offset uint32, val uint8) bool {
- if val != 1 && val != 0 {
- return false // error("invalid val")
- }
-
- index := offset >> 3
- if index >= uint32(len(sz)) {
- return false // error("overflow")
- }
-
- offset -= index << 3
- if sz[index]>>offset&1 != val {
- sz[index] ^= (1 << offset)
- }
- return true
-}
-
-func (datas segBitInfoArray) Len() int {
- return len(datas)
-}
-
-func (datas segBitInfoArray) Less(i, j int) bool {
- res := (datas)[i].Seq < (datas)[j].Seq
- if !res && (datas)[i].Seq == (datas)[j].Seq {
- res = (datas)[i].Off < (datas)[j].Off
- }
- return res
-}
-
-func (datas segBitInfoArray) Swap(i, j int) {
- datas[i], datas[j] = datas[j], datas[i]
-}
-
-func (db *DB) bEncodeMetaKey(key []byte) []byte {
- mk := make([]byte, len(key)+2)
- mk[0] = db.index
- mk[1] = BitMetaType
-
- copy(mk[2:], key)
- return mk
-}
-
-func (db *DB) bDecodeMetaKey(bkey []byte) ([]byte, error) {
- if len(bkey) < 2 || bkey[0] != db.index || bkey[1] != BitMetaType {
- return nil, errBinKey
- }
-
- return bkey[2:], nil
-}
-
-func (db *DB) bEncodeBinKey(key []byte, seq uint32) []byte {
- bk := make([]byte, len(key)+8)
-
- pos := 0
- bk[pos] = db.index
- pos++
- bk[pos] = BitType
- pos++
-
- binary.BigEndian.PutUint16(bk[pos:], uint16(len(key)))
- pos += 2
-
- copy(bk[pos:], key)
- pos += len(key)
-
- binary.BigEndian.PutUint32(bk[pos:], seq)
-
- return bk
-}
-
-func (db *DB) bDecodeBinKey(bkey []byte) (key []byte, seq uint32, err error) {
- if len(bkey) < 8 || bkey[0] != db.index {
- err = errBinKey
- return
- }
-
- keyLen := binary.BigEndian.Uint16(bkey[2:4])
- if int(keyLen+8) != len(bkey) {
- err = errBinKey
- return
- }
-
- key = bkey[4 : 4+keyLen]
- seq = uint32(binary.BigEndian.Uint32(bkey[4+keyLen:]))
- return
-}
-
-func (db *DB) bCapByteSize(seq uint32, off uint32) uint32 {
- var offByteSize uint32 = (off >> 3) + 1
- if offByteSize > segByteSize {
- offByteSize = segByteSize
- }
-
- return seq<<segByteWidth + offByteSize
-}
-
-func (db *DB) bParseOffset(key []byte, offset int32) (seq uint32, off uint32, err error) {
- if offset < 0 {
- if tailSeq, tailOff, e := db.bGetMeta(key); e != nil {
- err = e
- return
- } else if tailSeq >= 0 {
- offset += int32((uint32(tailSeq)<<segBitWidth | uint32(tailOff)) + 1)
- if offset < 0 {
- err = errOffset
- return
- }
- }
- }
-
- off = uint32(offset)
-
- seq = off >> segBitWidth
- off &= (segBitSize - 1)
- return
-}
-
-func (db *DB) bGetMeta(key []byte) (tailSeq int32, tailOff int32, err error) {
- var v []byte
-
- mk := db.bEncodeMetaKey(key)
- v, err = db.bucket.Get(mk)
- if err != nil {
- return
- }
-
- if v != nil {
- tailSeq = int32(binary.LittleEndian.Uint32(v[0:4]))
- tailOff = int32(binary.LittleEndian.Uint32(v[4:8]))
- } else {
- tailSeq = -1
- tailOff = -1
- }
- return
-}
-
-func (db *DB) bSetMeta(t *batch, key []byte, tailSeq uint32, tailOff uint32) {
- ek := db.bEncodeMetaKey(key)
-
- buf := make([]byte, 8)
- binary.LittleEndian.PutUint32(buf[0:4], tailSeq)
- binary.LittleEndian.PutUint32(buf[4:8], tailOff)
-
- t.Put(ek, buf)
- return
-}
-
-func (db *DB) bUpdateMeta(t *batch, key []byte, seq uint32, off uint32) (tailSeq uint32, tailOff uint32, err error) {
- var tseq, toff int32
- var update bool = false
-
- if tseq, toff, err = db.bGetMeta(key); err != nil {
- return
- } else if tseq < 0 {
- update = true
- } else {
- tailSeq = uint32(MaxInt32(tseq, 0))
- tailOff = uint32(MaxInt32(toff, 0))
- update = (seq > tailSeq || (seq == tailSeq && off > tailOff))
- }
-
- if update {
- db.bSetMeta(t, key, seq, off)
- tailSeq = seq
- tailOff = off
- }
- return
-}
-
-func (db *DB) bDelete(t *batch, key []byte) (drop int64) {
- mk := db.bEncodeMetaKey(key)
- t.Delete(mk)
-
- minKey := db.bEncodeBinKey(key, minSeq)
- maxKey := db.bEncodeBinKey(key, maxSeq)
- it := db.bucket.RangeIterator(minKey, maxKey, store.RangeClose)
- for ; it.Valid(); it.Next() {
- t.Delete(it.RawKey())
- drop++
- }
- it.Close()
-
- return drop
-}
-
-func (db *DB) bGetSegment(key []byte, seq uint32) ([]byte, []byte, error) {
- bk := db.bEncodeBinKey(key, seq)
- segment, err := db.bucket.Get(bk)
- if err != nil {
- return bk, nil, err
- }
- return bk, segment, nil
-}
-
-func (db *DB) bAllocateSegment(key []byte, seq uint32) ([]byte, []byte, error) {
- bk, segment, err := db.bGetSegment(key, seq)
- if err == nil && segment == nil {
- segment = make([]byte, segByteSize, segByteSize)
- }
- return bk, segment, err
-}
-
-func (db *DB) bIterator(key []byte) *store.RangeLimitIterator {
- sk := db.bEncodeBinKey(key, minSeq)
- ek := db.bEncodeBinKey(key, maxSeq)
- return db.bucket.RangeIterator(sk, ek, store.RangeClose)
-}
-
-func (db *DB) bSegAnd(a []byte, b []byte, res *[]byte) {
- if a == nil || b == nil {
- *res = nil
- return
- }
-
- data := *res
- if data == nil {
- data = make([]byte, segByteSize, segByteSize)
- *res = data
- }
-
- for i := uint32(0); i < segByteSize; i++ {
- data[i] = a[i] & b[i]
- }
- return
-}
-
-func (db *DB) bSegOr(a []byte, b []byte, res *[]byte) {
- if a == nil || b == nil {
- if a == nil && b == nil {
- *res = nil
- } else if a == nil {
- *res = b
- } else {
- *res = a
- }
- return
- }
-
- data := *res
- if data == nil {
- data = make([]byte, segByteSize, segByteSize)
- *res = data
- }
-
- for i := uint32(0); i < segByteSize; i++ {
- data[i] = a[i] | b[i]
- }
- return
-}
-
-func (db *DB) bSegXor(a []byte, b []byte, res *[]byte) {
- if a == nil && b == nil {
- *res = fillSegment
- return
- }
-
- if a == nil {
- a = emptySegment
- }
-
- if b == nil {
- b = emptySegment
- }
-
- data := *res
- if data == nil {
- data = make([]byte, segByteSize, segByteSize)
- *res = data
- }
-
- for i := uint32(0); i < segByteSize; i++ {
- data[i] = a[i] ^ b[i]
- }
-
- return
-}
-
-func (db *DB) bExpireAt(key []byte, when int64) (int64, error) {
- t := db.binBatch
- t.Lock()
- defer t.Unlock()
-
- if seq, _, err := db.bGetMeta(key); err != nil || seq < 0 {
- return 0, err
- } else {
- db.expireAt(t, BitType, key, when)
- if err := t.Commit(); err != nil {
- return 0, err
- }
- }
- return 1, nil
-}
-
-func (db *DB) bCountByte(val byte, soff uint32, eoff uint32) int32 {
- if soff > eoff {
- soff, eoff = eoff, soff
- }
-
- mask := uint8(0)
- if soff > 0 {
- mask |= fillBits[soff-1]
- }
- if eoff < 7 {
- mask |= (fillBits[7] ^ fillBits[eoff])
- }
- mask = fillBits[7] ^ mask
-
- return bitsInByte[val&mask]
-}
-
-func (db *DB) bCountSeg(key []byte, seq uint32, soff uint32, eoff uint32) (cnt int32, err error) {
- if soff >= segBitSize || soff < 0 ||
- eoff >= segBitSize || eoff < 0 {
- return
- }
-
- var segment []byte
- if _, segment, err = db.bGetSegment(key, seq); err != nil {
- return
- }
-
- if segment == nil {
- return
- }
-
- if soff > eoff {
- soff, eoff = eoff, soff
- }
-
- headIdx := int(soff >> 3)
- endIdx := int(eoff >> 3)
- sByteOff := soff - ((soff >> 3) << 3)
- eByteOff := eoff - ((eoff >> 3) << 3)
-
- if headIdx == endIdx {
- cnt = db.bCountByte(segment[headIdx], sByteOff, eByteOff)
- } else {
- cnt = db.bCountByte(segment[headIdx], sByteOff, 7) +
- db.bCountByte(segment[endIdx], 0, eByteOff)
- }
-
- // sum up following bytes
- for idx, end := headIdx+1, endIdx-1; idx <= end; idx += 1 {
- cnt += bitsInByte[segment[idx]]
- if idx == end {
- break
- }
- }
-
- return
-}
-
-func (db *DB) BGet(key []byte) (data []byte, err error) {
- if err = checkKeySize(key); err != nil {
- return
- }
-
- var ts, to int32
- if ts, to, err = db.bGetMeta(key); err != nil || ts < 0 {
- return
- }
-
- var tailSeq, tailOff = uint32(ts), uint32(to)
- var capByteSize uint32 = db.bCapByteSize(tailSeq, tailOff)
- data = make([]byte, capByteSize, capByteSize)
-
- minKey := db.bEncodeBinKey(key, minSeq)
- maxKey := db.bEncodeBinKey(key, tailSeq)
- it := db.bucket.RangeIterator(minKey, maxKey, store.RangeClose)
-
- var seq, s, e uint32
- for ; it.Valid(); it.Next() {
- if _, seq, err = db.bDecodeBinKey(it.RawKey()); err != nil {
- data = nil
- break
- }
-
- s = seq << segByteWidth
- e = MinUInt32(s+segByteSize, capByteSize)
- copy(data[s:e], it.RawValue())
- }
- it.Close()
-
- return
-}
-
-func (db *DB) BDelete(key []byte) (drop int64, err error) {
- if err = checkKeySize(key); err != nil {
- return
- }
-
- t := db.binBatch
- t.Lock()
- defer t.Unlock()
-
- drop = db.bDelete(t, key)
- db.rmExpire(t, BitType, key)
-
- err = t.Commit()
- return
-}
-
-func (db *DB) BSetBit(key []byte, offset int32, val uint8) (ori uint8, err error) {
- if err = checkKeySize(key); err != nil {
- return
- }
-
- // todo : check offset
- var seq, off uint32
- if seq, off, err = db.bParseOffset(key, offset); err != nil {
- return 0, err
- }
-
- var bk, segment []byte
- if bk, segment, err = db.bAllocateSegment(key, seq); err != nil {
- return 0, err
- }
-
- if segment != nil {
- ori = getBit(segment, off)
- if setBit(segment, off, val) {
- t := db.binBatch
- t.Lock()
- defer t.Unlock()
-
- t.Put(bk, segment)
- if _, _, e := db.bUpdateMeta(t, key, seq, off); e != nil {
- err = e
- return
- }
-
- err = t.Commit()
- }
- }
-
- return
-}
-
-func (db *DB) BMSetBit(key []byte, args ...BitPair) (place int64, err error) {
- if err = checkKeySize(key); err != nil {
- return
- }
-
- // (ps : so as to aviod wasting memory copy while calling db.Get() and batch.Put(),
- // here we sequence the params by pos, so that we can merge the execution of
- // diff pos setting which targets on the same segment respectively. )
-
- // #1 : sequence request data
- var argCnt = len(args)
- var bitInfos segBitInfoArray = make(segBitInfoArray, argCnt)
- var seq, off uint32
-
- for i, info := range args {
- if seq, off, err = db.bParseOffset(key, info.Pos); err != nil {
- return
- }
-
- bitInfos[i].Seq = seq
- bitInfos[i].Off = off
- bitInfos[i].Val = info.Val
- }
-
- sort.Sort(bitInfos)
-
- for i := 1; i < argCnt; i++ {
- if bitInfos[i].Seq == bitInfos[i-1].Seq && bitInfos[i].Off == bitInfos[i-1].Off {
- return 0, errDuplicatePos
- }
- }
-
- // #2 : execute bit set in order
- t := db.binBatch
- t.Lock()
- defer t.Unlock()
-
- var curBinKey, curSeg []byte
- var curSeq, maxSeq, maxOff uint32
-
- for _, info := range bitInfos {
- if curSeg != nil && info.Seq != curSeq {
- t.Put(curBinKey, curSeg)
- curSeg = nil
- }
-
- if curSeg == nil {
- curSeq = info.Seq
- if curBinKey, curSeg, err = db.bAllocateSegment(key, info.Seq); err != nil {
- return
- }
-
- if curSeg == nil {
- continue
- }
- }
-
- if setBit(curSeg, info.Off, info.Val) {
- maxSeq = info.Seq
- maxOff = info.Off
- place++
- }
- }
-
- if curSeg != nil {
- t.Put(curBinKey, curSeg)
- }
-
- // finally, update meta
- if place > 0 {
- if _, _, err = db.bUpdateMeta(t, key, maxSeq, maxOff); err != nil {
- return
- }
-
- err = t.Commit()
- }
-
- return
-}
-
-func (db *DB) BGetBit(key []byte, offset int32) (uint8, error) {
- if seq, off, err := db.bParseOffset(key, offset); err != nil {
- return 0, err
- } else {
- _, segment, err := db.bGetSegment(key, seq)
- if err != nil {
- return 0, err
- }
-
- if segment == nil {
- return 0, nil
- } else {
- return getBit(segment, off), nil
- }
- }
-}
-
-// func (db *DB) BGetRange(key []byte, start int32, end int32) ([]byte, error) {
-// section := make([]byte)
-
-// return
-// }
-
-func (db *DB) BCount(key []byte, start int32, end int32) (cnt int32, err error) {
- var sseq, soff uint32
- if sseq, soff, err = db.bParseOffset(key, start); err != nil {
- return
- }
-
- var eseq, eoff uint32
- if eseq, eoff, err = db.bParseOffset(key, end); err != nil {
- return
- }
-
- if sseq > eseq || (sseq == eseq && soff > eoff) {
- sseq, eseq = eseq, sseq
- soff, eoff = eoff, soff
- }
-
- var segCnt int32
- if eseq == sseq {
- if segCnt, err = db.bCountSeg(key, sseq, soff, eoff); err != nil {
- return 0, err
- }
-
- cnt = segCnt
-
- } else {
- if segCnt, err = db.bCountSeg(key, sseq, soff, segBitSize-1); err != nil {
- return 0, err
- } else {
- cnt += segCnt
- }
-
- if segCnt, err = db.bCountSeg(key, eseq, 0, eoff); err != nil {
- return 0, err
- } else {
- cnt += segCnt
- }
- }
-
- // middle segs
- var segment []byte
- skey := db.bEncodeBinKey(key, sseq)
- ekey := db.bEncodeBinKey(key, eseq)
-
- it := db.bucket.RangeIterator(skey, ekey, store.RangeOpen)
- for ; it.Valid(); it.Next() {
- segment = it.RawValue()
- for _, bt := range segment {
- cnt += bitsInByte[bt]
- }
- }
- it.Close()
-
- return
-}
-
-func (db *DB) BTail(key []byte) (int32, error) {
- // effective length of data, the highest bit-pos set in history
- tailSeq, tailOff, err := db.bGetMeta(key)
- if err != nil {
- return 0, err
- }
-
- tail := int32(-1)
- if tailSeq >= 0 {
- tail = int32(uint32(tailSeq)<<segBitWidth | uint32(tailOff))
- }
-
- return tail, nil
-}
-
-func (db *DB) BOperation(op uint8, dstkey []byte, srckeys ...[]byte) (blen int32, err error) {
- // blen -
- // the total bit size of data stored in destination key,
- // that is equal to the size of the longest input string.
-
- var exeOp func([]byte, []byte, *[]byte)
- switch op {
- case OPand:
- exeOp = db.bSegAnd
- case OPor:
- exeOp = db.bSegOr
- case OPxor, OPnot:
- exeOp = db.bSegXor
- default:
- return
- }
-
- if dstkey == nil || srckeys == nil {
- return
- }
-
- t := db.binBatch
- t.Lock()
- defer t.Unlock()
-
- var srcKseq, srcKoff int32
- var seq, off, maxDstSeq, maxDstOff uint32
-
- var keyNum int = len(srckeys)
- var validKeyNum int
- for i := 0; i < keyNum; i++ {
- if srcKseq, srcKoff, err = db.bGetMeta(srckeys[i]); err != nil {
- return
- } else if srcKseq < 0 {
- srckeys[i] = nil
- continue
- }
-
- validKeyNum++
-
- seq = uint32(srcKseq)
- off = uint32(srcKoff)
- if seq > maxDstSeq || (seq == maxDstSeq && off > maxDstOff) {
- maxDstSeq = seq
- maxDstOff = off
- }
- }
-
- if (op == OPnot && validKeyNum != 1) ||
- (op != OPnot && validKeyNum < 2) {
- return // with not enough existing source key
- }
-
- var srcIdx int
- for srcIdx = 0; srcIdx < keyNum; srcIdx++ {
- if srckeys[srcIdx] != nil {
- break
- }
- }
-
- // init - data
- var segments = make([][]byte, maxDstSeq+1)
-
- if op == OPnot {
- // ps :
- // ( ~num == num ^ 0x11111111 )
- // we init the result segments with all bit set,
- // then we can calculate through the way of 'xor'.
-
- // ahead segments bin format : 1111 ... 1111
- for i := uint32(0); i < maxDstSeq; i++ {
- segments[i] = fillSegment
- }
-
- // last segment bin format : 1111..1100..0000
- var tailSeg = make([]byte, segByteSize, segByteSize)
- var fillByte = fillBits[7]
- var tailSegLen = db.bCapByteSize(uint32(0), maxDstOff)
- for i := uint32(0); i < tailSegLen-1; i++ {
- tailSeg[i] = fillByte
- }
- tailSeg[tailSegLen-1] = fillBits[maxDstOff-(tailSegLen-1)<<3]
- segments[maxDstSeq] = tailSeg
-
- } else {
- // ps : init segments by data corresponding to the 1st valid source key
- it := db.bIterator(srckeys[srcIdx])
- for ; it.Valid(); it.Next() {
- if _, seq, err = db.bDecodeBinKey(it.RawKey()); err != nil {
- // to do ...
- it.Close()
- return
- }
- segments[seq] = it.Value()
- }
- it.Close()
- srcIdx++
- }
-
- // operation with following keys
- var res []byte
- for i := srcIdx; i < keyNum; i++ {
- if srckeys[i] == nil {
- continue
- }
-
- it := db.bIterator(srckeys[i])
- for idx, end := uint32(0), false; !end; it.Next() {
- end = !it.Valid()
- if !end {
- if _, seq, err = db.bDecodeBinKey(it.RawKey()); err != nil {
- // to do ...
- it.Close()
- return
- }
- } else {
- seq = maxDstSeq + 1
- }
-
- // todo :
- // operation 'and' can be optimize here :
- // if seq > max_segments_idx, this loop can be break,
- // which can avoid cost from Key() and bDecodeBinKey()
-
- for ; idx < seq; idx++ {
- res = nil
- exeOp(segments[idx], nil, &res)
- segments[idx] = res
- }
-
- if !end {
- res = it.Value()
- exeOp(segments[seq], res, &res)
- segments[seq] = res
- idx++
- }
- }
- it.Close()
- }
-
- // clear the old data in case
- db.bDelete(t, dstkey)
- db.rmExpire(t, BitType, dstkey)
-
- // set data
- db.bSetMeta(t, dstkey, maxDstSeq, maxDstOff)
-
- var bk []byte
- for seq, segt := range segments {
- if segt != nil {
- bk = db.bEncodeBinKey(dstkey, uint32(seq))
- t.Put(bk, segt)
- }
- }
-
- err = t.Commit()
- if err == nil {
- // blen = int32(db.bCapByteSize(maxDstOff, maxDstOff))
- blen = int32(maxDstSeq<<segBitWidth | maxDstOff + 1)
- }
-
- return
-}
-
-func (db *DB) BExpire(key []byte, duration int64) (int64, error) {
- if duration <= 0 {
- return 0, errExpireValue
- }
-
- if err := checkKeySize(key); err != nil {
- return -1, err
- }
-
- return db.bExpireAt(key, time.Now().Unix()+duration)
-}
-
-func (db *DB) BExpireAt(key []byte, when int64) (int64, error) {
- if when <= time.Now().Unix() {
- return 0, errExpireValue
- }
-
- if err := checkKeySize(key); err != nil {
- return -1, err
- }
-
- return db.bExpireAt(key, when)
-}
-
-func (db *DB) BTTL(key []byte) (int64, error) {
- if err := checkKeySize(key); err != nil {
- return -1, err
- }
-
- return db.ttl(BitType, key)
-}
-
-func (db *DB) BPersist(key []byte) (int64, error) {
- if err := checkKeySize(key); err != nil {
- return 0, err
- }
-
- t := db.binBatch
- t.Lock()
- defer t.Unlock()
-
- n, err := db.rmExpire(t, BitType, key)
- if err != nil {
- return 0, err
- }
-
- err = t.Commit()
- return n, err
-}
-
-func (db *DB) BScan(key []byte, count int, inclusive bool, match string) ([][]byte, error) {
- return db.scan(BitMetaType, key, count, inclusive, match)
-}
-
-func (db *DB) bFlush() (drop int64, err error) {
- t := db.binBatch
- t.Lock()
- defer t.Unlock()
-
- return db.flushType(t, BitType)
-}
+++ /dev/null
-package nodb
-
-import (
- "encoding/binary"
- "errors"
- "time"
-
- "gitea.com/lunny/nodb/store"
-)
-
-type FVPair struct {
- Field []byte
- Value []byte
-}
-
-var errHashKey = errors.New("invalid hash key")
-var errHSizeKey = errors.New("invalid hsize key")
-
-const (
- hashStartSep byte = ':'
- hashStopSep byte = hashStartSep + 1
-)
-
-func checkHashKFSize(key []byte, field []byte) error {
- if len(key) > MaxKeySize || len(key) == 0 {
- return errKeySize
- } else if len(field) > MaxHashFieldSize || len(field) == 0 {
- return errHashFieldSize
- }
- return nil
-}
-
-func (db *DB) hEncodeSizeKey(key []byte) []byte {
- buf := make([]byte, len(key)+2)
-
- buf[0] = db.index
- buf[1] = HSizeType
-
- copy(buf[2:], key)
- return buf
-}
-
-func (db *DB) hDecodeSizeKey(ek []byte) ([]byte, error) {
- if len(ek) < 2 || ek[0] != db.index || ek[1] != HSizeType {
- return nil, errHSizeKey
- }
-
- return ek[2:], nil
-}
-
-func (db *DB) hEncodeHashKey(key []byte, field []byte) []byte {
- buf := make([]byte, len(key)+len(field)+1+1+2+1)
-
- pos := 0
- buf[pos] = db.index
- pos++
- buf[pos] = HashType
- pos++
-
- binary.BigEndian.PutUint16(buf[pos:], uint16(len(key)))
- pos += 2
-
- copy(buf[pos:], key)
- pos += len(key)
-
- buf[pos] = hashStartSep
- pos++
- copy(buf[pos:], field)
-
- return buf
-}
-
-func (db *DB) hDecodeHashKey(ek []byte) ([]byte, []byte, error) {
- if len(ek) < 5 || ek[0] != db.index || ek[1] != HashType {
- return nil, nil, errHashKey
- }
-
- pos := 2
- keyLen := int(binary.BigEndian.Uint16(ek[pos:]))
- pos += 2
-
- if keyLen+5 > len(ek) {
- return nil, nil, errHashKey
- }
-
- key := ek[pos : pos+keyLen]
- pos += keyLen
-
- if ek[pos] != hashStartSep {
- return nil, nil, errHashKey
- }
-
- pos++
- field := ek[pos:]
- return key, field, nil
-}
-
-func (db *DB) hEncodeStartKey(key []byte) []byte {
- return db.hEncodeHashKey(key, nil)
-}
-
-func (db *DB) hEncodeStopKey(key []byte) []byte {
- k := db.hEncodeHashKey(key, nil)
-
- k[len(k)-1] = hashStopSep
-
- return k
-}
-
-func (db *DB) hSetItem(key []byte, field []byte, value []byte) (int64, error) {
- t := db.hashBatch
-
- ek := db.hEncodeHashKey(key, field)
-
- var n int64 = 1
- if v, _ := db.bucket.Get(ek); v != nil {
- n = 0
- } else {
- if _, err := db.hIncrSize(key, 1); err != nil {
- return 0, err
- }
- }
-
- t.Put(ek, value)
- return n, nil
-}
-
-// ps : here just focus on deleting the hash data,
-// any other likes expire is ignore.
-func (db *DB) hDelete(t *batch, key []byte) int64 {
- sk := db.hEncodeSizeKey(key)
- start := db.hEncodeStartKey(key)
- stop := db.hEncodeStopKey(key)
-
- var num int64 = 0
- it := db.bucket.RangeLimitIterator(start, stop, store.RangeROpen, 0, -1)
- for ; it.Valid(); it.Next() {
- t.Delete(it.Key())
- num++
- }
- it.Close()
-
- t.Delete(sk)
- return num
-}
-
-func (db *DB) hExpireAt(key []byte, when int64) (int64, error) {
- t := db.hashBatch
- t.Lock()
- defer t.Unlock()
-
- if hlen, err := db.HLen(key); err != nil || hlen == 0 {
- return 0, err
- } else {
- db.expireAt(t, HashType, key, when)
- if err := t.Commit(); err != nil {
- return 0, err
- }
- }
- return 1, nil
-}
-
-func (db *DB) HLen(key []byte) (int64, error) {
- if err := checkKeySize(key); err != nil {
- return 0, err
- }
-
- return Int64(db.bucket.Get(db.hEncodeSizeKey(key)))
-}
-
-func (db *DB) HSet(key []byte, field []byte, value []byte) (int64, error) {
- if err := checkHashKFSize(key, field); err != nil {
- return 0, err
- } else if err := checkValueSize(value); err != nil {
- return 0, err
- }
-
- t := db.hashBatch
- t.Lock()
- defer t.Unlock()
-
- n, err := db.hSetItem(key, field, value)
- if err != nil {
- return 0, err
- }
-
- //todo add binlog
-
- err = t.Commit()
- return n, err
-}
-
-func (db *DB) HGet(key []byte, field []byte) ([]byte, error) {
- if err := checkHashKFSize(key, field); err != nil {
- return nil, err
- }
-
- return db.bucket.Get(db.hEncodeHashKey(key, field))
-}
-
-func (db *DB) HMset(key []byte, args ...FVPair) error {
- t := db.hashBatch
- t.Lock()
- defer t.Unlock()
-
- var err error
- var ek []byte
- var num int64 = 0
- for i := 0; i < len(args); i++ {
- if err := checkHashKFSize(key, args[i].Field); err != nil {
- return err
- } else if err := checkValueSize(args[i].Value); err != nil {
- return err
- }
-
- ek = db.hEncodeHashKey(key, args[i].Field)
-
- if v, err := db.bucket.Get(ek); err != nil {
- return err
- } else if v == nil {
- num++
- }
-
- t.Put(ek, args[i].Value)
- }
-
- if _, err = db.hIncrSize(key, num); err != nil {
- return err
- }
-
- //todo add binglog
- err = t.Commit()
- return err
-}
-
-func (db *DB) HMget(key []byte, args ...[]byte) ([][]byte, error) {
- var ek []byte
-
- it := db.bucket.NewIterator()
- defer it.Close()
-
- r := make([][]byte, len(args))
- for i := 0; i < len(args); i++ {
- if err := checkHashKFSize(key, args[i]); err != nil {
- return nil, err
- }
-
- ek = db.hEncodeHashKey(key, args[i])
-
- r[i] = it.Find(ek)
- }
-
- return r, nil
-}
-
-func (db *DB) HDel(key []byte, args ...[]byte) (int64, error) {
- t := db.hashBatch
-
- var ek []byte
- var v []byte
- var err error
-
- t.Lock()
- defer t.Unlock()
-
- it := db.bucket.NewIterator()
- defer it.Close()
-
- var num int64 = 0
- for i := 0; i < len(args); i++ {
- if err := checkHashKFSize(key, args[i]); err != nil {
- return 0, err
- }
-
- ek = db.hEncodeHashKey(key, args[i])
-
- v = it.RawFind(ek)
- if v == nil {
- continue
- } else {
- num++
- t.Delete(ek)
- }
- }
-
- if _, err = db.hIncrSize(key, -num); err != nil {
- return 0, err
- }
-
- err = t.Commit()
-
- return num, err
-}
-
-func (db *DB) hIncrSize(key []byte, delta int64) (int64, error) {
- t := db.hashBatch
- sk := db.hEncodeSizeKey(key)
-
- var err error
- var size int64 = 0
- if size, err = Int64(db.bucket.Get(sk)); err != nil {
- return 0, err
- } else {
- size += delta
- if size <= 0 {
- size = 0
- t.Delete(sk)
- db.rmExpire(t, HashType, key)
- } else {
- t.Put(sk, PutInt64(size))
- }
- }
-
- return size, nil
-}
-
-func (db *DB) HIncrBy(key []byte, field []byte, delta int64) (int64, error) {
- if err := checkHashKFSize(key, field); err != nil {
- return 0, err
- }
-
- t := db.hashBatch
- var ek []byte
- var err error
-
- t.Lock()
- defer t.Unlock()
-
- ek = db.hEncodeHashKey(key, field)
-
- var n int64 = 0
- if n, err = StrInt64(db.bucket.Get(ek)); err != nil {
- return 0, err
- }
-
- n += delta
-
- _, err = db.hSetItem(key, field, StrPutInt64(n))
- if err != nil {
- return 0, err
- }
-
- err = t.Commit()
-
- return n, err
-}
-
-func (db *DB) HGetAll(key []byte) ([]FVPair, error) {
- if err := checkKeySize(key); err != nil {
- return nil, err
- }
-
- start := db.hEncodeStartKey(key)
- stop := db.hEncodeStopKey(key)
-
- v := make([]FVPair, 0, 16)
-
- it := db.bucket.RangeLimitIterator(start, stop, store.RangeROpen, 0, -1)
- for ; it.Valid(); it.Next() {
- _, f, err := db.hDecodeHashKey(it.Key())
- if err != nil {
- return nil, err
- }
-
- v = append(v, FVPair{Field: f, Value: it.Value()})
- }
-
- it.Close()
-
- return v, nil
-}
-
-func (db *DB) HKeys(key []byte) ([][]byte, error) {
- if err := checkKeySize(key); err != nil {
- return nil, err
- }
-
- start := db.hEncodeStartKey(key)
- stop := db.hEncodeStopKey(key)
-
- v := make([][]byte, 0, 16)
-
- it := db.bucket.RangeLimitIterator(start, stop, store.RangeROpen, 0, -1)
- for ; it.Valid(); it.Next() {
- _, f, err := db.hDecodeHashKey(it.Key())
- if err != nil {
- return nil, err
- }
- v = append(v, f)
- }
-
- it.Close()
-
- return v, nil
-}
-
-func (db *DB) HValues(key []byte) ([][]byte, error) {
- if err := checkKeySize(key); err != nil {
- return nil, err
- }
-
- start := db.hEncodeStartKey(key)
- stop := db.hEncodeStopKey(key)
-
- v := make([][]byte, 0, 16)
-
- it := db.bucket.RangeLimitIterator(start, stop, store.RangeROpen, 0, -1)
- for ; it.Valid(); it.Next() {
- _, _, err := db.hDecodeHashKey(it.Key())
- if err != nil {
- return nil, err
- }
-
- v = append(v, it.Value())
- }
-
- it.Close()
-
- return v, nil
-}
-
-func (db *DB) HClear(key []byte) (int64, error) {
- if err := checkKeySize(key); err != nil {
- return 0, err
- }
-
- t := db.hashBatch
- t.Lock()
- defer t.Unlock()
-
- num := db.hDelete(t, key)
- db.rmExpire(t, HashType, key)
-
- err := t.Commit()
- return num, err
-}
-
-func (db *DB) HMclear(keys ...[]byte) (int64, error) {
- t := db.hashBatch
- t.Lock()
- defer t.Unlock()
-
- for _, key := range keys {
- if err := checkKeySize(key); err != nil {
- return 0, err
- }
-
- db.hDelete(t, key)
- db.rmExpire(t, HashType, key)
- }
-
- err := t.Commit()
- return int64(len(keys)), err
-}
-
-func (db *DB) hFlush() (drop int64, err error) {
- t := db.hashBatch
-
- t.Lock()
- defer t.Unlock()
-
- return db.flushType(t, HashType)
-}
-
-func (db *DB) HScan(key []byte, count int, inclusive bool, match string) ([][]byte, error) {
- return db.scan(HSizeType, key, count, inclusive, match)
-}
-
-func (db *DB) HExpire(key []byte, duration int64) (int64, error) {
- if duration <= 0 {
- return 0, errExpireValue
- }
-
- return db.hExpireAt(key, time.Now().Unix()+duration)
-}
-
-func (db *DB) HExpireAt(key []byte, when int64) (int64, error) {
- if when <= time.Now().Unix() {
- return 0, errExpireValue
- }
-
- return db.hExpireAt(key, when)
-}
-
-func (db *DB) HTTL(key []byte) (int64, error) {
- if err := checkKeySize(key); err != nil {
- return -1, err
- }
-
- return db.ttl(HashType, key)
-}
-
-func (db *DB) HPersist(key []byte) (int64, error) {
- if err := checkKeySize(key); err != nil {
- return 0, err
- }
-
- t := db.hashBatch
- t.Lock()
- defer t.Unlock()
-
- n, err := db.rmExpire(t, HashType, key)
- if err != nil {
- return 0, err
- }
-
- err = t.Commit()
- return n, err
-}
+++ /dev/null
-package nodb
-
-import (
- "errors"
- "time"
-)
-
-type KVPair struct {
- Key []byte
- Value []byte
-}
-
-var errKVKey = errors.New("invalid encode kv key")
-
-func checkKeySize(key []byte) error {
- if len(key) > MaxKeySize || len(key) == 0 {
- return errKeySize
- }
- return nil
-}
-
-func checkValueSize(value []byte) error {
- if len(value) > MaxValueSize {
- return errValueSize
- }
-
- return nil
-}
-
-func (db *DB) encodeKVKey(key []byte) []byte {
- ek := make([]byte, len(key)+2)
- ek[0] = db.index
- ek[1] = KVType
- copy(ek[2:], key)
- return ek
-}
-
-func (db *DB) decodeKVKey(ek []byte) ([]byte, error) {
- if len(ek) < 2 || ek[0] != db.index || ek[1] != KVType {
- return nil, errKVKey
- }
-
- return ek[2:], nil
-}
-
-func (db *DB) encodeKVMinKey() []byte {
- ek := db.encodeKVKey(nil)
- return ek
-}
-
-func (db *DB) encodeKVMaxKey() []byte {
- ek := db.encodeKVKey(nil)
- ek[len(ek)-1] = KVType + 1
- return ek
-}
-
-func (db *DB) incr(key []byte, delta int64) (int64, error) {
- if err := checkKeySize(key); err != nil {
- return 0, err
- }
-
- var err error
- key = db.encodeKVKey(key)
-
- t := db.kvBatch
-
- t.Lock()
- defer t.Unlock()
-
- var n int64
- n, err = StrInt64(db.bucket.Get(key))
- if err != nil {
- return 0, err
- }
-
- n += delta
-
- t.Put(key, StrPutInt64(n))
-
- //todo binlog
-
- err = t.Commit()
- return n, err
-}
-
-// ps : here just focus on deleting the key-value data,
-// any other likes expire is ignore.
-func (db *DB) delete(t *batch, key []byte) int64 {
- key = db.encodeKVKey(key)
- t.Delete(key)
- return 1
-}
-
-func (db *DB) setExpireAt(key []byte, when int64) (int64, error) {
- t := db.kvBatch
- t.Lock()
- defer t.Unlock()
-
- if exist, err := db.Exists(key); err != nil || exist == 0 {
- return 0, err
- } else {
- db.expireAt(t, KVType, key, when)
- if err := t.Commit(); err != nil {
- return 0, err
- }
- }
- return 1, nil
-}
-
-func (db *DB) Decr(key []byte) (int64, error) {
- return db.incr(key, -1)
-}
-
-func (db *DB) DecrBy(key []byte, decrement int64) (int64, error) {
- return db.incr(key, -decrement)
-}
-
-func (db *DB) Del(keys ...[]byte) (int64, error) {
- if len(keys) == 0 {
- return 0, nil
- }
-
- codedKeys := make([][]byte, len(keys))
- for i, k := range keys {
- codedKeys[i] = db.encodeKVKey(k)
- }
-
- t := db.kvBatch
- t.Lock()
- defer t.Unlock()
-
- for i, k := range keys {
- t.Delete(codedKeys[i])
- db.rmExpire(t, KVType, k)
- }
-
- err := t.Commit()
- return int64(len(keys)), err
-}
-
-func (db *DB) Exists(key []byte) (int64, error) {
- if err := checkKeySize(key); err != nil {
- return 0, err
- }
-
- var err error
- key = db.encodeKVKey(key)
-
- var v []byte
- v, err = db.bucket.Get(key)
- if v != nil && err == nil {
- return 1, nil
- }
-
- return 0, err
-}
-
-func (db *DB) Get(key []byte) ([]byte, error) {
- if err := checkKeySize(key); err != nil {
- return nil, err
- }
-
- key = db.encodeKVKey(key)
-
- return db.bucket.Get(key)
-}
-
-func (db *DB) GetSet(key []byte, value []byte) ([]byte, error) {
- if err := checkKeySize(key); err != nil {
- return nil, err
- } else if err := checkValueSize(value); err != nil {
- return nil, err
- }
-
- key = db.encodeKVKey(key)
-
- t := db.kvBatch
-
- t.Lock()
- defer t.Unlock()
-
- oldValue, err := db.bucket.Get(key)
- if err != nil {
- return nil, err
- }
-
- t.Put(key, value)
- //todo, binlog
-
- err = t.Commit()
-
- return oldValue, err
-}
-
-func (db *DB) Incr(key []byte) (int64, error) {
- return db.incr(key, 1)
-}
-
-func (db *DB) IncrBy(key []byte, increment int64) (int64, error) {
- return db.incr(key, increment)
-}
-
-func (db *DB) MGet(keys ...[]byte) ([][]byte, error) {
- values := make([][]byte, len(keys))
-
- it := db.bucket.NewIterator()
- defer it.Close()
-
- for i := range keys {
- if err := checkKeySize(keys[i]); err != nil {
- return nil, err
- }
-
- values[i] = it.Find(db.encodeKVKey(keys[i]))
- }
-
- return values, nil
-}
-
-func (db *DB) MSet(args ...KVPair) error {
- if len(args) == 0 {
- return nil
- }
-
- t := db.kvBatch
-
- var err error
- var key []byte
- var value []byte
-
- t.Lock()
- defer t.Unlock()
-
- for i := 0; i < len(args); i++ {
- if err := checkKeySize(args[i].Key); err != nil {
- return err
- } else if err := checkValueSize(args[i].Value); err != nil {
- return err
- }
-
- key = db.encodeKVKey(args[i].Key)
-
- value = args[i].Value
-
- t.Put(key, value)
-
- //todo binlog
- }
-
- err = t.Commit()
- return err
-}
-
-func (db *DB) Set(key []byte, value []byte) error {
- if err := checkKeySize(key); err != nil {
- return err
- } else if err := checkValueSize(value); err != nil {
- return err
- }
-
- var err error
- key = db.encodeKVKey(key)
-
- t := db.kvBatch
-
- t.Lock()
- defer t.Unlock()
-
- t.Put(key, value)
-
- err = t.Commit()
-
- return err
-}
-
-func (db *DB) SetNX(key []byte, value []byte) (int64, error) {
- if err := checkKeySize(key); err != nil {
- return 0, err
- } else if err := checkValueSize(value); err != nil {
- return 0, err
- }
-
- var err error
- key = db.encodeKVKey(key)
-
- var n int64 = 1
-
- t := db.kvBatch
-
- t.Lock()
- defer t.Unlock()
-
- if v, err := db.bucket.Get(key); err != nil {
- return 0, err
- } else if v != nil {
- n = 0
- } else {
- t.Put(key, value)
-
- //todo binlog
-
- err = t.Commit()
- }
-
- return n, err
-}
-
-func (db *DB) flush() (drop int64, err error) {
- t := db.kvBatch
- t.Lock()
- defer t.Unlock()
- return db.flushType(t, KVType)
-}
-
-//if inclusive is true, scan range [key, inf) else (key, inf)
-func (db *DB) Scan(key []byte, count int, inclusive bool, match string) ([][]byte, error) {
- return db.scan(KVType, key, count, inclusive, match)
-}
-
-func (db *DB) Expire(key []byte, duration int64) (int64, error) {
- if duration <= 0 {
- return 0, errExpireValue
- }
-
- return db.setExpireAt(key, time.Now().Unix()+duration)
-}
-
-func (db *DB) ExpireAt(key []byte, when int64) (int64, error) {
- if when <= time.Now().Unix() {
- return 0, errExpireValue
- }
-
- return db.setExpireAt(key, when)
-}
-
-func (db *DB) TTL(key []byte) (int64, error) {
- if err := checkKeySize(key); err != nil {
- return -1, err
- }
-
- return db.ttl(KVType, key)
-}
-
-func (db *DB) Persist(key []byte) (int64, error) {
- if err := checkKeySize(key); err != nil {
- return 0, err
- }
-
- t := db.kvBatch
- t.Lock()
- defer t.Unlock()
- n, err := db.rmExpire(t, KVType, key)
- if err != nil {
- return 0, err
- }
-
- err = t.Commit()
- return n, err
-}
-
-func (db *DB) Lock() {
- t := db.kvBatch
- t.Lock()
-}
-
-func (db *DB) Remove(key []byte) bool {
- if len(key) == 0 {
- return false
- }
- t := db.kvBatch
- t.Delete(db.encodeKVKey(key))
- _, err := db.rmExpire(t, KVType, key)
- if err != nil {
- return false
- }
- return true
-}
-
-func (db *DB) Commit() error {
- t := db.kvBatch
- return t.Commit()
-}
-
-func (db *DB) Unlock() {
- t := db.kvBatch
- t.Unlock()
-}
+++ /dev/null
-package nodb
-
-import (
- "encoding/binary"
- "errors"
- "time"
-
- "gitea.com/lunny/nodb/store"
-)
-
-const (
- listHeadSeq int32 = 1
- listTailSeq int32 = 2
-
- listMinSeq int32 = 1000
- listMaxSeq int32 = 1<<31 - 1000
- listInitialSeq int32 = listMinSeq + (listMaxSeq-listMinSeq)/2
-)
-
-var errLMetaKey = errors.New("invalid lmeta key")
-var errListKey = errors.New("invalid list key")
-var errListSeq = errors.New("invalid list sequence, overflow")
-
-func (db *DB) lEncodeMetaKey(key []byte) []byte {
- buf := make([]byte, len(key)+2)
- buf[0] = db.index
- buf[1] = LMetaType
-
- copy(buf[2:], key)
- return buf
-}
-
-func (db *DB) lDecodeMetaKey(ek []byte) ([]byte, error) {
- if len(ek) < 2 || ek[0] != db.index || ek[1] != LMetaType {
- return nil, errLMetaKey
- }
-
- return ek[2:], nil
-}
-
-func (db *DB) lEncodeListKey(key []byte, seq int32) []byte {
- buf := make([]byte, len(key)+8)
-
- pos := 0
- buf[pos] = db.index
- pos++
- buf[pos] = ListType
- pos++
-
- binary.BigEndian.PutUint16(buf[pos:], uint16(len(key)))
- pos += 2
-
- copy(buf[pos:], key)
- pos += len(key)
-
- binary.BigEndian.PutUint32(buf[pos:], uint32(seq))
-
- return buf
-}
-
-func (db *DB) lDecodeListKey(ek []byte) (key []byte, seq int32, err error) {
- if len(ek) < 8 || ek[0] != db.index || ek[1] != ListType {
- err = errListKey
- return
- }
-
- keyLen := int(binary.BigEndian.Uint16(ek[2:]))
- if keyLen+8 != len(ek) {
- err = errListKey
- return
- }
-
- key = ek[4 : 4+keyLen]
- seq = int32(binary.BigEndian.Uint32(ek[4+keyLen:]))
- return
-}
-
-func (db *DB) lpush(key []byte, whereSeq int32, args ...[]byte) (int64, error) {
- if err := checkKeySize(key); err != nil {
- return 0, err
- }
-
- var headSeq int32
- var tailSeq int32
- var size int32
- var err error
-
- t := db.listBatch
- t.Lock()
- defer t.Unlock()
-
- metaKey := db.lEncodeMetaKey(key)
- headSeq, tailSeq, size, err = db.lGetMeta(nil, metaKey)
- if err != nil {
- return 0, err
- }
-
- var pushCnt int = len(args)
- if pushCnt == 0 {
- return int64(size), nil
- }
-
- var seq int32 = headSeq
- var delta int32 = -1
- if whereSeq == listTailSeq {
- seq = tailSeq
- delta = 1
- }
-
- // append elements
- if size > 0 {
- seq += delta
- }
-
- for i := 0; i < pushCnt; i++ {
- ek := db.lEncodeListKey(key, seq+int32(i)*delta)
- t.Put(ek, args[i])
- }
-
- seq += int32(pushCnt-1) * delta
- if seq <= listMinSeq || seq >= listMaxSeq {
- return 0, errListSeq
- }
-
- // set meta info
- if whereSeq == listHeadSeq {
- headSeq = seq
- } else {
- tailSeq = seq
- }
-
- db.lSetMeta(metaKey, headSeq, tailSeq)
-
- err = t.Commit()
- return int64(size) + int64(pushCnt), err
-}
-
-func (db *DB) lpop(key []byte, whereSeq int32) ([]byte, error) {
- if err := checkKeySize(key); err != nil {
- return nil, err
- }
-
- t := db.listBatch
- t.Lock()
- defer t.Unlock()
-
- var headSeq int32
- var tailSeq int32
- var err error
-
- metaKey := db.lEncodeMetaKey(key)
- headSeq, tailSeq, _, err = db.lGetMeta(nil, metaKey)
- if err != nil {
- return nil, err
- }
-
- var value []byte
-
- var seq int32 = headSeq
- if whereSeq == listTailSeq {
- seq = tailSeq
- }
-
- itemKey := db.lEncodeListKey(key, seq)
- value, err = db.bucket.Get(itemKey)
- if err != nil {
- return nil, err
- }
-
- if whereSeq == listHeadSeq {
- headSeq += 1
- } else {
- tailSeq -= 1
- }
-
- t.Delete(itemKey)
- size := db.lSetMeta(metaKey, headSeq, tailSeq)
- if size == 0 {
- db.rmExpire(t, HashType, key)
- }
-
- err = t.Commit()
- return value, err
-}
-
-// ps : here just focus on deleting the list data,
-// any other likes expire is ignore.
-func (db *DB) lDelete(t *batch, key []byte) int64 {
- mk := db.lEncodeMetaKey(key)
-
- var headSeq int32
- var tailSeq int32
- var err error
-
- it := db.bucket.NewIterator()
- defer it.Close()
-
- headSeq, tailSeq, _, err = db.lGetMeta(it, mk)
- if err != nil {
- return 0
- }
-
- var num int64 = 0
- startKey := db.lEncodeListKey(key, headSeq)
- stopKey := db.lEncodeListKey(key, tailSeq)
-
- rit := store.NewRangeIterator(it, &store.Range{startKey, stopKey, store.RangeClose})
- for ; rit.Valid(); rit.Next() {
- t.Delete(rit.RawKey())
- num++
- }
-
- t.Delete(mk)
-
- return num
-}
-
-func (db *DB) lGetMeta(it *store.Iterator, ek []byte) (headSeq int32, tailSeq int32, size int32, err error) {
- var v []byte
- if it != nil {
- v = it.Find(ek)
- } else {
- v, err = db.bucket.Get(ek)
- }
- if err != nil {
- return
- } else if v == nil {
- headSeq = listInitialSeq
- tailSeq = listInitialSeq
- size = 0
- return
- } else {
- headSeq = int32(binary.LittleEndian.Uint32(v[0:4]))
- tailSeq = int32(binary.LittleEndian.Uint32(v[4:8]))
- size = tailSeq - headSeq + 1
- }
- return
-}
-
-func (db *DB) lSetMeta(ek []byte, headSeq int32, tailSeq int32) int32 {
- t := db.listBatch
-
- var size int32 = tailSeq - headSeq + 1
- if size < 0 {
- // todo : log error + panic
- } else if size == 0 {
- t.Delete(ek)
- } else {
- buf := make([]byte, 8)
-
- binary.LittleEndian.PutUint32(buf[0:4], uint32(headSeq))
- binary.LittleEndian.PutUint32(buf[4:8], uint32(tailSeq))
-
- t.Put(ek, buf)
- }
-
- return size
-}
-
-func (db *DB) lExpireAt(key []byte, when int64) (int64, error) {
- t := db.listBatch
- t.Lock()
- defer t.Unlock()
-
- if llen, err := db.LLen(key); err != nil || llen == 0 {
- return 0, err
- } else {
- db.expireAt(t, ListType, key, when)
- if err := t.Commit(); err != nil {
- return 0, err
- }
- }
- return 1, nil
-}
-
-func (db *DB) LIndex(key []byte, index int32) ([]byte, error) {
- if err := checkKeySize(key); err != nil {
- return nil, err
- }
-
- var seq int32
- var headSeq int32
- var tailSeq int32
- var err error
-
- metaKey := db.lEncodeMetaKey(key)
-
- it := db.bucket.NewIterator()
- defer it.Close()
-
- headSeq, tailSeq, _, err = db.lGetMeta(it, metaKey)
- if err != nil {
- return nil, err
- }
-
- if index >= 0 {
- seq = headSeq + index
- } else {
- seq = tailSeq + index + 1
- }
-
- sk := db.lEncodeListKey(key, seq)
- v := it.Find(sk)
-
- return v, nil
-}
-
-func (db *DB) LLen(key []byte) (int64, error) {
- if err := checkKeySize(key); err != nil {
- return 0, err
- }
-
- ek := db.lEncodeMetaKey(key)
- _, _, size, err := db.lGetMeta(nil, ek)
- return int64(size), err
-}
-
-func (db *DB) LPop(key []byte) ([]byte, error) {
- return db.lpop(key, listHeadSeq)
-}
-
-func (db *DB) LPush(key []byte, arg1 []byte, args ...[]byte) (int64, error) {
- var argss = [][]byte{arg1}
- argss = append(argss, args...)
- return db.lpush(key, listHeadSeq, argss...)
-}
-
-func (db *DB) LRange(key []byte, start int32, stop int32) ([][]byte, error) {
- if err := checkKeySize(key); err != nil {
- return nil, err
- }
-
- var headSeq int32
- var llen int32
- var err error
-
- metaKey := db.lEncodeMetaKey(key)
-
- it := db.bucket.NewIterator()
- defer it.Close()
-
- if headSeq, _, llen, err = db.lGetMeta(it, metaKey); err != nil {
- return nil, err
- }
-
- if start < 0 {
- start = llen + start
- }
- if stop < 0 {
- stop = llen + stop
- }
- if start < 0 {
- start = 0
- }
-
- if start > stop || start >= llen {
- return [][]byte{}, nil
- }
-
- if stop >= llen {
- stop = llen - 1
- }
-
- limit := (stop - start) + 1
- headSeq += start
-
- v := make([][]byte, 0, limit)
-
- startKey := db.lEncodeListKey(key, headSeq)
- rit := store.NewRangeLimitIterator(it,
- &store.Range{
- Min: startKey,
- Max: nil,
- Type: store.RangeClose},
- &store.Limit{
- Offset: 0,
- Count: int(limit)})
-
- for ; rit.Valid(); rit.Next() {
- v = append(v, rit.Value())
- }
-
- return v, nil
-}
-
-func (db *DB) RPop(key []byte) ([]byte, error) {
- return db.lpop(key, listTailSeq)
-}
-
-func (db *DB) RPush(key []byte, arg1 []byte, args ...[]byte) (int64, error) {
- var argss = [][]byte{arg1}
- argss = append(argss, args...)
- return db.lpush(key, listTailSeq, argss...)
-}
-
-func (db *DB) LClear(key []byte) (int64, error) {
- if err := checkKeySize(key); err != nil {
- return 0, err
- }
-
- t := db.listBatch
- t.Lock()
- defer t.Unlock()
-
- num := db.lDelete(t, key)
- db.rmExpire(t, ListType, key)
-
- err := t.Commit()
- return num, err
-}
-
-func (db *DB) LMclear(keys ...[]byte) (int64, error) {
- t := db.listBatch
- t.Lock()
- defer t.Unlock()
-
- for _, key := range keys {
- if err := checkKeySize(key); err != nil {
- return 0, err
- }
-
- db.lDelete(t, key)
- db.rmExpire(t, ListType, key)
-
- }
-
- err := t.Commit()
- return int64(len(keys)), err
-}
-
-func (db *DB) lFlush() (drop int64, err error) {
- t := db.listBatch
- t.Lock()
- defer t.Unlock()
- return db.flushType(t, ListType)
-}
-
-func (db *DB) LExpire(key []byte, duration int64) (int64, error) {
- if duration <= 0 {
- return 0, errExpireValue
- }
-
- return db.lExpireAt(key, time.Now().Unix()+duration)
-}
-
-func (db *DB) LExpireAt(key []byte, when int64) (int64, error) {
- if when <= time.Now().Unix() {
- return 0, errExpireValue
- }
-
- return db.lExpireAt(key, when)
-}
-
-func (db *DB) LTTL(key []byte) (int64, error) {
- if err := checkKeySize(key); err != nil {
- return -1, err
- }
-
- return db.ttl(ListType, key)
-}
-
-func (db *DB) LPersist(key []byte) (int64, error) {
- if err := checkKeySize(key); err != nil {
- return 0, err
- }
-
- t := db.listBatch
- t.Lock()
- defer t.Unlock()
-
- n, err := db.rmExpire(t, ListType, key)
- if err != nil {
- return 0, err
- }
-
- err = t.Commit()
- return n, err
-}
-
-func (db *DB) LScan(key []byte, count int, inclusive bool, match string) ([][]byte, error) {
- return db.scan(LMetaType, key, count, inclusive, match)
-}
-
-func (db *DB) lEncodeMinKey() []byte {
- return db.lEncodeMetaKey(nil)
-}
-
-func (db *DB) lEncodeMaxKey() []byte {
- ek := db.lEncodeMetaKey(nil)
- ek[len(ek)-1] = LMetaType + 1
- return ek
-}
+++ /dev/null
-package nodb
-
-import (
- "encoding/binary"
- "errors"
- "time"
-
- "gitea.com/lunny/nodb/store"
-)
-
-var errSetKey = errors.New("invalid set key")
-var errSSizeKey = errors.New("invalid ssize key")
-
-const (
- setStartSep byte = ':'
- setStopSep byte = setStartSep + 1
- UnionType byte = 51
- DiffType byte = 52
- InterType byte = 53
-)
-
-func checkSetKMSize(key []byte, member []byte) error {
- if len(key) > MaxKeySize || len(key) == 0 {
- return errKeySize
- } else if len(member) > MaxSetMemberSize || len(member) == 0 {
- return errSetMemberSize
- }
- return nil
-}
-
-func (db *DB) sEncodeSizeKey(key []byte) []byte {
- buf := make([]byte, len(key)+2)
-
- buf[0] = db.index
- buf[1] = SSizeType
-
- copy(buf[2:], key)
- return buf
-}
-
-func (db *DB) sDecodeSizeKey(ek []byte) ([]byte, error) {
- if len(ek) < 2 || ek[0] != db.index || ek[1] != SSizeType {
- return nil, errSSizeKey
- }
-
- return ek[2:], nil
-}
-
-func (db *DB) sEncodeSetKey(key []byte, member []byte) []byte {
- buf := make([]byte, len(key)+len(member)+1+1+2+1)
-
- pos := 0
- buf[pos] = db.index
- pos++
- buf[pos] = SetType
- pos++
-
- binary.BigEndian.PutUint16(buf[pos:], uint16(len(key)))
- pos += 2
-
- copy(buf[pos:], key)
- pos += len(key)
-
- buf[pos] = setStartSep
- pos++
- copy(buf[pos:], member)
-
- return buf
-}
-
-func (db *DB) sDecodeSetKey(ek []byte) ([]byte, []byte, error) {
- if len(ek) < 5 || ek[0] != db.index || ek[1] != SetType {
- return nil, nil, errSetKey
- }
-
- pos := 2
- keyLen := int(binary.BigEndian.Uint16(ek[pos:]))
- pos += 2
-
- if keyLen+5 > len(ek) {
- return nil, nil, errSetKey
- }
-
- key := ek[pos : pos+keyLen]
- pos += keyLen
-
- if ek[pos] != hashStartSep {
- return nil, nil, errSetKey
- }
-
- pos++
- member := ek[pos:]
- return key, member, nil
-}
-
-func (db *DB) sEncodeStartKey(key []byte) []byte {
- return db.sEncodeSetKey(key, nil)
-}
-
-func (db *DB) sEncodeStopKey(key []byte) []byte {
- k := db.sEncodeSetKey(key, nil)
-
- k[len(k)-1] = setStopSep
-
- return k
-}
-
-func (db *DB) sFlush() (drop int64, err error) {
-
- t := db.setBatch
- t.Lock()
- defer t.Unlock()
-
- return db.flushType(t, SetType)
-}
-
-func (db *DB) sDelete(t *batch, key []byte) int64 {
- sk := db.sEncodeSizeKey(key)
- start := db.sEncodeStartKey(key)
- stop := db.sEncodeStopKey(key)
-
- var num int64 = 0
- it := db.bucket.RangeLimitIterator(start, stop, store.RangeROpen, 0, -1)
- for ; it.Valid(); it.Next() {
- t.Delete(it.RawKey())
- num++
- }
-
- it.Close()
-
- t.Delete(sk)
- return num
-}
-
-func (db *DB) sIncrSize(key []byte, delta int64) (int64, error) {
- t := db.setBatch
- sk := db.sEncodeSizeKey(key)
-
- var err error
- var size int64 = 0
- if size, err = Int64(db.bucket.Get(sk)); err != nil {
- return 0, err
- } else {
- size += delta
- if size <= 0 {
- size = 0
- t.Delete(sk)
- db.rmExpire(t, SetType, key)
- } else {
- t.Put(sk, PutInt64(size))
- }
- }
-
- return size, nil
-}
-
-func (db *DB) sExpireAt(key []byte, when int64) (int64, error) {
- t := db.setBatch
- t.Lock()
- defer t.Unlock()
-
- if scnt, err := db.SCard(key); err != nil || scnt == 0 {
- return 0, err
- } else {
- db.expireAt(t, SetType, key, when)
- if err := t.Commit(); err != nil {
- return 0, err
- }
-
- }
-
- return 1, nil
-}
-
-func (db *DB) sSetItem(key []byte, member []byte) (int64, error) {
- t := db.setBatch
- ek := db.sEncodeSetKey(key, member)
-
- var n int64 = 1
- if v, _ := db.bucket.Get(ek); v != nil {
- n = 0
- } else {
- if _, err := db.sIncrSize(key, 1); err != nil {
- return 0, err
- }
- }
-
- t.Put(ek, nil)
- return n, nil
-}
-
-func (db *DB) SAdd(key []byte, args ...[]byte) (int64, error) {
- t := db.setBatch
- t.Lock()
- defer t.Unlock()
-
- var err error
- var ek []byte
- var num int64 = 0
- for i := 0; i < len(args); i++ {
- if err := checkSetKMSize(key, args[i]); err != nil {
- return 0, err
- }
-
- ek = db.sEncodeSetKey(key, args[i])
-
- if v, err := db.bucket.Get(ek); err != nil {
- return 0, err
- } else if v == nil {
- num++
- }
-
- t.Put(ek, nil)
- }
-
- if _, err = db.sIncrSize(key, num); err != nil {
- return 0, err
- }
-
- err = t.Commit()
- return num, err
-
-}
-
-func (db *DB) SCard(key []byte) (int64, error) {
- if err := checkKeySize(key); err != nil {
- return 0, err
- }
-
- sk := db.sEncodeSizeKey(key)
-
- return Int64(db.bucket.Get(sk))
-}
-
-func (db *DB) sDiffGeneric(keys ...[]byte) ([][]byte, error) {
- destMap := make(map[string]bool)
-
- members, err := db.SMembers(keys[0])
- if err != nil {
- return nil, err
- }
-
- for _, m := range members {
- destMap[String(m)] = true
- }
-
- for _, k := range keys[1:] {
- members, err := db.SMembers(k)
- if err != nil {
- return nil, err
- }
-
- for _, m := range members {
- if _, ok := destMap[String(m)]; !ok {
- continue
- } else if ok {
- delete(destMap, String(m))
- }
- }
- // O - A = O, O is zero set.
- if len(destMap) == 0 {
- return nil, nil
- }
- }
-
- slice := make([][]byte, len(destMap))
- idx := 0
- for k, v := range destMap {
- if !v {
- continue
- }
- slice[idx] = []byte(k)
- idx++
- }
-
- return slice, nil
-}
-
-func (db *DB) SDiff(keys ...[]byte) ([][]byte, error) {
- v, err := db.sDiffGeneric(keys...)
- return v, err
-}
-
-func (db *DB) SDiffStore(dstKey []byte, keys ...[]byte) (int64, error) {
- n, err := db.sStoreGeneric(dstKey, DiffType, keys...)
- return n, err
-}
-
-func (db *DB) sInterGeneric(keys ...[]byte) ([][]byte, error) {
- destMap := make(map[string]bool)
-
- members, err := db.SMembers(keys[0])
- if err != nil {
- return nil, err
- }
-
- for _, m := range members {
- destMap[String(m)] = true
- }
-
- for _, key := range keys[1:] {
- if err := checkKeySize(key); err != nil {
- return nil, err
- }
-
- members, err := db.SMembers(key)
- if err != nil {
- return nil, err
- } else if len(members) == 0 {
- return nil, err
- }
-
- tempMap := make(map[string]bool)
- for _, member := range members {
- if err := checkKeySize(member); err != nil {
- return nil, err
- }
- if _, ok := destMap[String(member)]; ok {
- tempMap[String(member)] = true //mark this item as selected
- }
- }
- destMap = tempMap //reduce the size of the result set
- if len(destMap) == 0 {
- return nil, nil
- }
- }
-
- slice := make([][]byte, len(destMap))
- idx := 0
- for k, v := range destMap {
- if !v {
- continue
- }
-
- slice[idx] = []byte(k)
- idx++
- }
-
- return slice, nil
-
-}
-
-func (db *DB) SInter(keys ...[]byte) ([][]byte, error) {
- v, err := db.sInterGeneric(keys...)
- return v, err
-
-}
-
-func (db *DB) SInterStore(dstKey []byte, keys ...[]byte) (int64, error) {
- n, err := db.sStoreGeneric(dstKey, InterType, keys...)
- return n, err
-}
-
-func (db *DB) SIsMember(key []byte, member []byte) (int64, error) {
- ek := db.sEncodeSetKey(key, member)
-
- var n int64 = 1
- if v, err := db.bucket.Get(ek); err != nil {
- return 0, err
- } else if v == nil {
- n = 0
- }
- return n, nil
-}
-
-func (db *DB) SMembers(key []byte) ([][]byte, error) {
- if err := checkKeySize(key); err != nil {
- return nil, err
- }
-
- start := db.sEncodeStartKey(key)
- stop := db.sEncodeStopKey(key)
-
- v := make([][]byte, 0, 16)
-
- it := db.bucket.RangeLimitIterator(start, stop, store.RangeROpen, 0, -1)
- for ; it.Valid(); it.Next() {
- _, m, err := db.sDecodeSetKey(it.Key())
- if err != nil {
- return nil, err
- }
-
- v = append(v, m)
- }
-
- it.Close()
-
- return v, nil
-}
-
-func (db *DB) SRem(key []byte, args ...[]byte) (int64, error) {
- t := db.setBatch
- t.Lock()
- defer t.Unlock()
-
- var ek []byte
- var v []byte
- var err error
-
- it := db.bucket.NewIterator()
- defer it.Close()
-
- var num int64 = 0
- for i := 0; i < len(args); i++ {
- if err := checkSetKMSize(key, args[i]); err != nil {
- return 0, err
- }
-
- ek = db.sEncodeSetKey(key, args[i])
-
- v = it.RawFind(ek)
- if v == nil {
- continue
- } else {
- num++
- t.Delete(ek)
- }
- }
-
- if _, err = db.sIncrSize(key, -num); err != nil {
- return 0, err
- }
-
- err = t.Commit()
- return num, err
-
-}
-
-func (db *DB) sUnionGeneric(keys ...[]byte) ([][]byte, error) {
- dstMap := make(map[string]bool)
-
- for _, key := range keys {
- if err := checkKeySize(key); err != nil {
- return nil, err
- }
-
- members, err := db.SMembers(key)
- if err != nil {
- return nil, err
- }
-
- for _, member := range members {
- dstMap[String(member)] = true
- }
- }
-
- slice := make([][]byte, len(dstMap))
- idx := 0
- for k, v := range dstMap {
- if !v {
- continue
- }
- slice[idx] = []byte(k)
- idx++
- }
-
- return slice, nil
-}
-
-func (db *DB) SUnion(keys ...[]byte) ([][]byte, error) {
- v, err := db.sUnionGeneric(keys...)
- return v, err
-}
-
-func (db *DB) SUnionStore(dstKey []byte, keys ...[]byte) (int64, error) {
- n, err := db.sStoreGeneric(dstKey, UnionType, keys...)
- return n, err
-}
-
-func (db *DB) sStoreGeneric(dstKey []byte, optType byte, keys ...[]byte) (int64, error) {
- if err := checkKeySize(dstKey); err != nil {
- return 0, err
- }
-
- t := db.setBatch
- t.Lock()
- defer t.Unlock()
-
- db.sDelete(t, dstKey)
-
- var err error
- var ek []byte
- var v [][]byte
-
- switch optType {
- case UnionType:
- v, err = db.sUnionGeneric(keys...)
- case DiffType:
- v, err = db.sDiffGeneric(keys...)
- case InterType:
- v, err = db.sInterGeneric(keys...)
- }
-
- if err != nil {
- return 0, err
- }
-
- for _, m := range v {
- if err := checkSetKMSize(dstKey, m); err != nil {
- return 0, err
- }
-
- ek = db.sEncodeSetKey(dstKey, m)
-
- if _, err := db.bucket.Get(ek); err != nil {
- return 0, err
- }
-
- t.Put(ek, nil)
- }
-
- var num = int64(len(v))
- sk := db.sEncodeSizeKey(dstKey)
- t.Put(sk, PutInt64(num))
-
- if err = t.Commit(); err != nil {
- return 0, err
- }
- return num, nil
-}
-
-func (db *DB) SClear(key []byte) (int64, error) {
- if err := checkKeySize(key); err != nil {
- return 0, err
- }
-
- t := db.setBatch
- t.Lock()
- defer t.Unlock()
-
- num := db.sDelete(t, key)
- db.rmExpire(t, SetType, key)
-
- err := t.Commit()
- return num, err
-}
-
-func (db *DB) SMclear(keys ...[]byte) (int64, error) {
- t := db.setBatch
- t.Lock()
- defer t.Unlock()
-
- for _, key := range keys {
- if err := checkKeySize(key); err != nil {
- return 0, err
- }
-
- db.sDelete(t, key)
- db.rmExpire(t, SetType, key)
- }
-
- err := t.Commit()
- return int64(len(keys)), err
-}
-
-func (db *DB) SExpire(key []byte, duration int64) (int64, error) {
- if duration <= 0 {
- return 0, errExpireValue
- }
-
- return db.sExpireAt(key, time.Now().Unix()+duration)
-
-}
-
-func (db *DB) SExpireAt(key []byte, when int64) (int64, error) {
- if when <= time.Now().Unix() {
- return 0, errExpireValue
- }
-
- return db.sExpireAt(key, when)
-
-}
-
-func (db *DB) STTL(key []byte) (int64, error) {
- if err := checkKeySize(key); err != nil {
- return -1, err
- }
-
- return db.ttl(SetType, key)
-}
-
-func (db *DB) SPersist(key []byte) (int64, error) {
- if err := checkKeySize(key); err != nil {
- return 0, err
- }
-
- t := db.setBatch
- t.Lock()
- defer t.Unlock()
-
- n, err := db.rmExpire(t, SetType, key)
- if err != nil {
- return 0, err
- }
- err = t.Commit()
- return n, err
-}
-
-func (db *DB) SScan(key []byte, count int, inclusive bool, match string) ([][]byte, error) {
- return db.scan(SSizeType, key, count, inclusive, match)
-}
+++ /dev/null
-package nodb
-
-import (
- "encoding/binary"
- "errors"
- "time"
-
- "gitea.com/lunny/nodb/store"
-)
-
-var (
- errExpMetaKey = errors.New("invalid expire meta key")
- errExpTimeKey = errors.New("invalid expire time key")
-)
-
-type retireCallback func(*batch, []byte) int64
-
-type elimination struct {
- db *DB
- exp2Tx []*batch
- exp2Retire []retireCallback
-}
-
-var errExpType = errors.New("invalid expire type")
-
-func (db *DB) expEncodeTimeKey(dataType byte, key []byte, when int64) []byte {
- buf := make([]byte, len(key)+11)
-
- buf[0] = db.index
- buf[1] = ExpTimeType
- buf[2] = dataType
- pos := 3
-
- binary.BigEndian.PutUint64(buf[pos:], uint64(when))
- pos += 8
-
- copy(buf[pos:], key)
-
- return buf
-}
-
-func (db *DB) expEncodeMetaKey(dataType byte, key []byte) []byte {
- buf := make([]byte, len(key)+3)
-
- buf[0] = db.index
- buf[1] = ExpMetaType
- buf[2] = dataType
- pos := 3
-
- copy(buf[pos:], key)
-
- return buf
-}
-
-func (db *DB) expDecodeMetaKey(mk []byte) (byte, []byte, error) {
- if len(mk) <= 3 || mk[0] != db.index || mk[1] != ExpMetaType {
- return 0, nil, errExpMetaKey
- }
-
- return mk[2], mk[3:], nil
-}
-
-func (db *DB) expDecodeTimeKey(tk []byte) (byte, []byte, int64, error) {
- if len(tk) < 11 || tk[0] != db.index || tk[1] != ExpTimeType {
- return 0, nil, 0, errExpTimeKey
- }
-
- return tk[2], tk[11:], int64(binary.BigEndian.Uint64(tk[3:])), nil
-}
-
-func (db *DB) expire(t *batch, dataType byte, key []byte, duration int64) {
- db.expireAt(t, dataType, key, time.Now().Unix()+duration)
-}
-
-func (db *DB) expireAt(t *batch, dataType byte, key []byte, when int64) {
- mk := db.expEncodeMetaKey(dataType, key)
- tk := db.expEncodeTimeKey(dataType, key, when)
-
- t.Put(tk, mk)
- t.Put(mk, PutInt64(when))
-}
-
-func (db *DB) ttl(dataType byte, key []byte) (t int64, err error) {
- mk := db.expEncodeMetaKey(dataType, key)
-
- if t, err = Int64(db.bucket.Get(mk)); err != nil || t == 0 {
- t = -1
- } else {
- t -= time.Now().Unix()
- if t <= 0 {
- t = -1
- }
- // if t == -1 : to remove ????
- }
-
- return t, err
-}
-
-func (db *DB) rmExpire(t *batch, dataType byte, key []byte) (int64, error) {
- mk := db.expEncodeMetaKey(dataType, key)
- if v, err := db.bucket.Get(mk); err != nil {
- return 0, err
- } else if v == nil {
- return 0, nil
- } else if when, err2 := Int64(v, nil); err2 != nil {
- return 0, err2
- } else {
- tk := db.expEncodeTimeKey(dataType, key, when)
- t.Delete(mk)
- t.Delete(tk)
- return 1, nil
- }
-}
-
-func (db *DB) expFlush(t *batch, dataType byte) (err error) {
- minKey := make([]byte, 3)
- minKey[0] = db.index
- minKey[1] = ExpTimeType
- minKey[2] = dataType
-
- maxKey := make([]byte, 3)
- maxKey[0] = db.index
- maxKey[1] = ExpMetaType
- maxKey[2] = dataType + 1
-
- _, err = db.flushRegion(t, minKey, maxKey)
- err = t.Commit()
- return
-}
-
-//////////////////////////////////////////////////////////
-//
-//////////////////////////////////////////////////////////
-
-func newEliminator(db *DB) *elimination {
- eli := new(elimination)
- eli.db = db
- eli.exp2Tx = make([]*batch, maxDataType)
- eli.exp2Retire = make([]retireCallback, maxDataType)
- return eli
-}
-
-func (eli *elimination) regRetireContext(dataType byte, t *batch, onRetire retireCallback) {
-
- // todo .. need to ensure exist - mapExpMetaType[expType]
-
- eli.exp2Tx[dataType] = t
- eli.exp2Retire[dataType] = onRetire
-}
-
-// call by outside ... (from *db to another *db)
-func (eli *elimination) active() {
- now := time.Now().Unix()
- db := eli.db
- dbGet := db.bucket.Get
-
- minKey := db.expEncodeTimeKey(NoneType, nil, 0)
- maxKey := db.expEncodeTimeKey(maxDataType, nil, now)
-
- it := db.bucket.RangeLimitIterator(minKey, maxKey, store.RangeROpen, 0, -1)
- for ; it.Valid(); it.Next() {
- tk := it.RawKey()
- mk := it.RawValue()
-
- dt, k, _, err := db.expDecodeTimeKey(tk)
- if err != nil {
- continue
- }
-
- t := eli.exp2Tx[dt]
- onRetire := eli.exp2Retire[dt]
- if tk == nil || onRetire == nil {
- continue
- }
-
- t.Lock()
-
- if exp, err := Int64(dbGet(mk)); err == nil {
- // check expire again
- if exp <= now {
- onRetire(t, k)
- t.Delete(tk)
- t.Delete(mk)
-
- t.Commit()
- }
-
- }
-
- t.Unlock()
- }
- it.Close()
-
- return
-}
+++ /dev/null
-package nodb
-
-import (
- "bytes"
- "encoding/binary"
- "errors"
- "time"
-
- "gitea.com/lunny/nodb/store"
-)
-
-const (
- MinScore int64 = -1<<63 + 1
- MaxScore int64 = 1<<63 - 1
- InvalidScore int64 = -1 << 63
-
- AggregateSum byte = 0
- AggregateMin byte = 1
- AggregateMax byte = 2
-)
-
-type ScorePair struct {
- Score int64
- Member []byte
-}
-
-var errZSizeKey = errors.New("invalid zsize key")
-var errZSetKey = errors.New("invalid zset key")
-var errZScoreKey = errors.New("invalid zscore key")
-var errScoreOverflow = errors.New("zset score overflow")
-var errInvalidAggregate = errors.New("invalid aggregate")
-var errInvalidWeightNum = errors.New("invalid weight number")
-var errInvalidSrcKeyNum = errors.New("invalid src key number")
-
-const (
- zsetNScoreSep byte = '<'
- zsetPScoreSep byte = zsetNScoreSep + 1
- zsetStopScoreSep byte = zsetPScoreSep + 1
-
- zsetStartMemSep byte = ':'
- zsetStopMemSep byte = zsetStartMemSep + 1
-)
-
-func checkZSetKMSize(key []byte, member []byte) error {
- if len(key) > MaxKeySize || len(key) == 0 {
- return errKeySize
- } else if len(member) > MaxZSetMemberSize || len(member) == 0 {
- return errZSetMemberSize
- }
- return nil
-}
-
-func (db *DB) zEncodeSizeKey(key []byte) []byte {
- buf := make([]byte, len(key)+2)
- buf[0] = db.index
- buf[1] = ZSizeType
-
- copy(buf[2:], key)
- return buf
-}
-
-func (db *DB) zDecodeSizeKey(ek []byte) ([]byte, error) {
- if len(ek) < 2 || ek[0] != db.index || ek[1] != ZSizeType {
- return nil, errZSizeKey
- }
-
- return ek[2:], nil
-}
-
-func (db *DB) zEncodeSetKey(key []byte, member []byte) []byte {
- buf := make([]byte, len(key)+len(member)+5)
-
- pos := 0
- buf[pos] = db.index
- pos++
-
- buf[pos] = ZSetType
- pos++
-
- binary.BigEndian.PutUint16(buf[pos:], uint16(len(key)))
- pos += 2
-
- copy(buf[pos:], key)
- pos += len(key)
-
- buf[pos] = zsetStartMemSep
- pos++
-
- copy(buf[pos:], member)
-
- return buf
-}
-
-func (db *DB) zDecodeSetKey(ek []byte) ([]byte, []byte, error) {
- if len(ek) < 5 || ek[0] != db.index || ek[1] != ZSetType {
- return nil, nil, errZSetKey
- }
-
- keyLen := int(binary.BigEndian.Uint16(ek[2:]))
- if keyLen+5 > len(ek) {
- return nil, nil, errZSetKey
- }
-
- key := ek[4 : 4+keyLen]
-
- if ek[4+keyLen] != zsetStartMemSep {
- return nil, nil, errZSetKey
- }
-
- member := ek[5+keyLen:]
- return key, member, nil
-}
-
-func (db *DB) zEncodeStartSetKey(key []byte) []byte {
- k := db.zEncodeSetKey(key, nil)
- return k
-}
-
-func (db *DB) zEncodeStopSetKey(key []byte) []byte {
- k := db.zEncodeSetKey(key, nil)
- k[len(k)-1] = zsetStartMemSep + 1
- return k
-}
-
-func (db *DB) zEncodeScoreKey(key []byte, member []byte, score int64) []byte {
- buf := make([]byte, len(key)+len(member)+14)
-
- pos := 0
- buf[pos] = db.index
- pos++
-
- buf[pos] = ZScoreType
- pos++
-
- binary.BigEndian.PutUint16(buf[pos:], uint16(len(key)))
- pos += 2
-
- copy(buf[pos:], key)
- pos += len(key)
-
- if score < 0 {
- buf[pos] = zsetNScoreSep
- } else {
- buf[pos] = zsetPScoreSep
- }
-
- pos++
- binary.BigEndian.PutUint64(buf[pos:], uint64(score))
- pos += 8
-
- buf[pos] = zsetStartMemSep
- pos++
-
- copy(buf[pos:], member)
- return buf
-}
-
-func (db *DB) zEncodeStartScoreKey(key []byte, score int64) []byte {
- return db.zEncodeScoreKey(key, nil, score)
-}
-
-func (db *DB) zEncodeStopScoreKey(key []byte, score int64) []byte {
- k := db.zEncodeScoreKey(key, nil, score)
- k[len(k)-1] = zsetStopMemSep
- return k
-}
-
-func (db *DB) zDecodeScoreKey(ek []byte) (key []byte, member []byte, score int64, err error) {
- if len(ek) < 14 || ek[0] != db.index || ek[1] != ZScoreType {
- err = errZScoreKey
- return
- }
-
- keyLen := int(binary.BigEndian.Uint16(ek[2:]))
- if keyLen+14 > len(ek) {
- err = errZScoreKey
- return
- }
-
- key = ek[4 : 4+keyLen]
- pos := 4 + keyLen
-
- if (ek[pos] != zsetNScoreSep) && (ek[pos] != zsetPScoreSep) {
- err = errZScoreKey
- return
- }
- pos++
-
- score = int64(binary.BigEndian.Uint64(ek[pos:]))
- pos += 8
-
- if ek[pos] != zsetStartMemSep {
- err = errZScoreKey
- return
- }
-
- pos++
-
- member = ek[pos:]
- return
-}
-
-func (db *DB) zSetItem(t *batch, key []byte, score int64, member []byte) (int64, error) {
- if score <= MinScore || score >= MaxScore {
- return 0, errScoreOverflow
- }
-
- var exists int64 = 0
- ek := db.zEncodeSetKey(key, member)
-
- if v, err := db.bucket.Get(ek); err != nil {
- return 0, err
- } else if v != nil {
- exists = 1
-
- if s, err := Int64(v, err); err != nil {
- return 0, err
- } else {
- sk := db.zEncodeScoreKey(key, member, s)
- t.Delete(sk)
- }
- }
-
- t.Put(ek, PutInt64(score))
-
- sk := db.zEncodeScoreKey(key, member, score)
- t.Put(sk, []byte{})
-
- return exists, nil
-}
-
-func (db *DB) zDelItem(t *batch, key []byte, member []byte, skipDelScore bool) (int64, error) {
- ek := db.zEncodeSetKey(key, member)
- if v, err := db.bucket.Get(ek); err != nil {
- return 0, err
- } else if v == nil {
- //not exists
- return 0, nil
- } else {
- //exists
- if !skipDelScore {
- //we must del score
- if s, err := Int64(v, err); err != nil {
- return 0, err
- } else {
- sk := db.zEncodeScoreKey(key, member, s)
- t.Delete(sk)
- }
- }
- }
-
- t.Delete(ek)
-
- return 1, nil
-}
-
-func (db *DB) zDelete(t *batch, key []byte) int64 {
- delMembCnt, _ := db.zRemRange(t, key, MinScore, MaxScore, 0, -1)
- // todo : log err
- return delMembCnt
-}
-
-func (db *DB) zExpireAt(key []byte, when int64) (int64, error) {
- t := db.zsetBatch
- t.Lock()
- defer t.Unlock()
-
- if zcnt, err := db.ZCard(key); err != nil || zcnt == 0 {
- return 0, err
- } else {
- db.expireAt(t, ZSetType, key, when)
- if err := t.Commit(); err != nil {
- return 0, err
- }
- }
- return 1, nil
-}
-
-func (db *DB) ZAdd(key []byte, args ...ScorePair) (int64, error) {
- if len(args) == 0 {
- return 0, nil
- }
-
- t := db.zsetBatch
- t.Lock()
- defer t.Unlock()
-
- var num int64 = 0
- for i := 0; i < len(args); i++ {
- score := args[i].Score
- member := args[i].Member
-
- if err := checkZSetKMSize(key, member); err != nil {
- return 0, err
- }
-
- if n, err := db.zSetItem(t, key, score, member); err != nil {
- return 0, err
- } else if n == 0 {
- //add new
- num++
- }
- }
-
- if _, err := db.zIncrSize(t, key, num); err != nil {
- return 0, err
- }
-
- //todo add binlog
- err := t.Commit()
- return num, err
-}
-
-func (db *DB) zIncrSize(t *batch, key []byte, delta int64) (int64, error) {
- sk := db.zEncodeSizeKey(key)
-
- size, err := Int64(db.bucket.Get(sk))
- if err != nil {
- return 0, err
- } else {
- size += delta
- if size <= 0 {
- size = 0
- t.Delete(sk)
- db.rmExpire(t, ZSetType, key)
- } else {
- t.Put(sk, PutInt64(size))
- }
- }
-
- return size, nil
-}
-
-func (db *DB) ZCard(key []byte) (int64, error) {
- if err := checkKeySize(key); err != nil {
- return 0, err
- }
-
- sk := db.zEncodeSizeKey(key)
- return Int64(db.bucket.Get(sk))
-}
-
-func (db *DB) ZScore(key []byte, member []byte) (int64, error) {
- if err := checkZSetKMSize(key, member); err != nil {
- return InvalidScore, err
- }
-
- var score int64 = InvalidScore
-
- k := db.zEncodeSetKey(key, member)
- if v, err := db.bucket.Get(k); err != nil {
- return InvalidScore, err
- } else if v == nil {
- return InvalidScore, ErrScoreMiss
- } else {
- if score, err = Int64(v, nil); err != nil {
- return InvalidScore, err
- }
- }
-
- return score, nil
-}
-
-func (db *DB) ZRem(key []byte, members ...[]byte) (int64, error) {
- if len(members) == 0 {
- return 0, nil
- }
-
- t := db.zsetBatch
- t.Lock()
- defer t.Unlock()
-
- var num int64 = 0
- for i := 0; i < len(members); i++ {
- if err := checkZSetKMSize(key, members[i]); err != nil {
- return 0, err
- }
-
- if n, err := db.zDelItem(t, key, members[i], false); err != nil {
- return 0, err
- } else if n == 1 {
- num++
- }
- }
-
- if _, err := db.zIncrSize(t, key, -num); err != nil {
- return 0, err
- }
-
- err := t.Commit()
- return num, err
-}
-
-func (db *DB) ZIncrBy(key []byte, delta int64, member []byte) (int64, error) {
- if err := checkZSetKMSize(key, member); err != nil {
- return InvalidScore, err
- }
-
- t := db.zsetBatch
- t.Lock()
- defer t.Unlock()
-
- ek := db.zEncodeSetKey(key, member)
-
- var oldScore int64 = 0
- v, err := db.bucket.Get(ek)
- if err != nil {
- return InvalidScore, err
- } else if v == nil {
- db.zIncrSize(t, key, 1)
- } else {
- if oldScore, err = Int64(v, err); err != nil {
- return InvalidScore, err
- }
- }
-
- newScore := oldScore + delta
- if newScore >= MaxScore || newScore <= MinScore {
- return InvalidScore, errScoreOverflow
- }
-
- sk := db.zEncodeScoreKey(key, member, newScore)
- t.Put(sk, []byte{})
- t.Put(ek, PutInt64(newScore))
-
- if v != nil {
- // so as to update score, we must delete the old one
- oldSk := db.zEncodeScoreKey(key, member, oldScore)
- t.Delete(oldSk)
- }
-
- err = t.Commit()
- return newScore, err
-}
-
-func (db *DB) ZCount(key []byte, min int64, max int64) (int64, error) {
- if err := checkKeySize(key); err != nil {
- return 0, err
- }
- minKey := db.zEncodeStartScoreKey(key, min)
- maxKey := db.zEncodeStopScoreKey(key, max)
-
- rangeType := store.RangeROpen
-
- it := db.bucket.RangeLimitIterator(minKey, maxKey, rangeType, 0, -1)
- var n int64 = 0
- for ; it.Valid(); it.Next() {
- n++
- }
- it.Close()
-
- return n, nil
-}
-
-func (db *DB) zrank(key []byte, member []byte, reverse bool) (int64, error) {
- if err := checkZSetKMSize(key, member); err != nil {
- return 0, err
- }
-
- k := db.zEncodeSetKey(key, member)
-
- it := db.bucket.NewIterator()
- defer it.Close()
-
- if v := it.Find(k); v == nil {
- return -1, nil
- } else {
- if s, err := Int64(v, nil); err != nil {
- return 0, err
- } else {
- var rit *store.RangeLimitIterator
-
- sk := db.zEncodeScoreKey(key, member, s)
-
- if !reverse {
- minKey := db.zEncodeStartScoreKey(key, MinScore)
-
- rit = store.NewRangeIterator(it, &store.Range{minKey, sk, store.RangeClose})
- } else {
- maxKey := db.zEncodeStopScoreKey(key, MaxScore)
- rit = store.NewRevRangeIterator(it, &store.Range{sk, maxKey, store.RangeClose})
- }
-
- var lastKey []byte = nil
- var n int64 = 0
-
- for ; rit.Valid(); rit.Next() {
- n++
-
- lastKey = rit.BufKey(lastKey)
- }
-
- if _, m, _, err := db.zDecodeScoreKey(lastKey); err == nil && bytes.Equal(m, member) {
- n--
- return n, nil
- }
- }
- }
-
- return -1, nil
-}
-
-func (db *DB) zIterator(key []byte, min int64, max int64, offset int, count int, reverse bool) *store.RangeLimitIterator {
- minKey := db.zEncodeStartScoreKey(key, min)
- maxKey := db.zEncodeStopScoreKey(key, max)
-
- if !reverse {
- return db.bucket.RangeLimitIterator(minKey, maxKey, store.RangeClose, offset, count)
- } else {
- return db.bucket.RevRangeLimitIterator(minKey, maxKey, store.RangeClose, offset, count)
- }
-}
-
-func (db *DB) zRemRange(t *batch, key []byte, min int64, max int64, offset int, count int) (int64, error) {
- if len(key) > MaxKeySize {
- return 0, errKeySize
- }
-
- it := db.zIterator(key, min, max, offset, count, false)
- var num int64 = 0
- for ; it.Valid(); it.Next() {
- sk := it.RawKey()
- _, m, _, err := db.zDecodeScoreKey(sk)
- if err != nil {
- continue
- }
-
- if n, err := db.zDelItem(t, key, m, true); err != nil {
- return 0, err
- } else if n == 1 {
- num++
- }
-
- t.Delete(sk)
- }
- it.Close()
-
- if _, err := db.zIncrSize(t, key, -num); err != nil {
- return 0, err
- }
-
- return num, nil
-}
-
-func (db *DB) zRange(key []byte, min int64, max int64, offset int, count int, reverse bool) ([]ScorePair, error) {
- if len(key) > MaxKeySize {
- return nil, errKeySize
- }
-
- if offset < 0 {
- return []ScorePair{}, nil
- }
-
- nv := 64
- if count > 0 {
- nv = count
- }
-
- v := make([]ScorePair, 0, nv)
-
- var it *store.RangeLimitIterator
-
- //if reverse and offset is 0, count < 0, we may use forward iterator then reverse
- //because store iterator prev is slower than next
- if !reverse || (offset == 0 && count < 0) {
- it = db.zIterator(key, min, max, offset, count, false)
- } else {
- it = db.zIterator(key, min, max, offset, count, true)
- }
-
- for ; it.Valid(); it.Next() {
- _, m, s, err := db.zDecodeScoreKey(it.Key())
- //may be we will check key equal?
- if err != nil {
- continue
- }
-
- v = append(v, ScorePair{Member: m, Score: s})
- }
- it.Close()
-
- if reverse && (offset == 0 && count < 0) {
- for i, j := 0, len(v)-1; i < j; i, j = i+1, j-1 {
- v[i], v[j] = v[j], v[i]
- }
- }
-
- return v, nil
-}
-
-func (db *DB) zParseLimit(key []byte, start int, stop int) (offset int, count int, err error) {
- if start < 0 || stop < 0 {
- //refer redis implementation
- var size int64
- size, err = db.ZCard(key)
- if err != nil {
- return
- }
-
- llen := int(size)
-
- if start < 0 {
- start = llen + start
- }
- if stop < 0 {
- stop = llen + stop
- }
-
- if start < 0 {
- start = 0
- }
-
- if start >= llen {
- offset = -1
- return
- }
- }
-
- if start > stop {
- offset = -1
- return
- }
-
- offset = start
- count = (stop - start) + 1
- return
-}
-
-func (db *DB) ZClear(key []byte) (int64, error) {
- t := db.zsetBatch
- t.Lock()
- defer t.Unlock()
-
- rmCnt, err := db.zRemRange(t, key, MinScore, MaxScore, 0, -1)
- if err == nil {
- err = t.Commit()
- }
-
- return rmCnt, err
-}
-
-func (db *DB) ZMclear(keys ...[]byte) (int64, error) {
- t := db.zsetBatch
- t.Lock()
- defer t.Unlock()
-
- for _, key := range keys {
- if _, err := db.zRemRange(t, key, MinScore, MaxScore, 0, -1); err != nil {
- return 0, err
- }
- }
-
- err := t.Commit()
-
- return int64(len(keys)), err
-}
-
-func (db *DB) ZRange(key []byte, start int, stop int) ([]ScorePair, error) {
- return db.ZRangeGeneric(key, start, stop, false)
-}
-
-//min and max must be inclusive
-//if no limit, set offset = 0 and count = -1
-func (db *DB) ZRangeByScore(key []byte, min int64, max int64,
- offset int, count int) ([]ScorePair, error) {
- return db.ZRangeByScoreGeneric(key, min, max, offset, count, false)
-}
-
-func (db *DB) ZRank(key []byte, member []byte) (int64, error) {
- return db.zrank(key, member, false)
-}
-
-func (db *DB) ZRemRangeByRank(key []byte, start int, stop int) (int64, error) {
- offset, count, err := db.zParseLimit(key, start, stop)
- if err != nil {
- return 0, err
- }
-
- var rmCnt int64
-
- t := db.zsetBatch
- t.Lock()
- defer t.Unlock()
-
- rmCnt, err = db.zRemRange(t, key, MinScore, MaxScore, offset, count)
- if err == nil {
- err = t.Commit()
- }
-
- return rmCnt, err
-}
-
-//min and max must be inclusive
-func (db *DB) ZRemRangeByScore(key []byte, min int64, max int64) (int64, error) {
- t := db.zsetBatch
- t.Lock()
- defer t.Unlock()
-
- rmCnt, err := db.zRemRange(t, key, min, max, 0, -1)
- if err == nil {
- err = t.Commit()
- }
-
- return rmCnt, err
-}
-
-func (db *DB) ZRevRange(key []byte, start int, stop int) ([]ScorePair, error) {
- return db.ZRangeGeneric(key, start, stop, true)
-}
-
-func (db *DB) ZRevRank(key []byte, member []byte) (int64, error) {
- return db.zrank(key, member, true)
-}
-
-//min and max must be inclusive
-//if no limit, set offset = 0 and count = -1
-func (db *DB) ZRevRangeByScore(key []byte, min int64, max int64, offset int, count int) ([]ScorePair, error) {
- return db.ZRangeByScoreGeneric(key, min, max, offset, count, true)
-}
-
-func (db *DB) ZRangeGeneric(key []byte, start int, stop int, reverse bool) ([]ScorePair, error) {
- offset, count, err := db.zParseLimit(key, start, stop)
- if err != nil {
- return nil, err
- }
-
- return db.zRange(key, MinScore, MaxScore, offset, count, reverse)
-}
-
-//min and max must be inclusive
-//if no limit, set offset = 0 and count = -1
-func (db *DB) ZRangeByScoreGeneric(key []byte, min int64, max int64,
- offset int, count int, reverse bool) ([]ScorePair, error) {
-
- return db.zRange(key, min, max, offset, count, reverse)
-}
-
-func (db *DB) zFlush() (drop int64, err error) {
- t := db.zsetBatch
- t.Lock()
- defer t.Unlock()
- return db.flushType(t, ZSetType)
-}
-
-func (db *DB) ZExpire(key []byte, duration int64) (int64, error) {
- if duration <= 0 {
- return 0, errExpireValue
- }
-
- return db.zExpireAt(key, time.Now().Unix()+duration)
-}
-
-func (db *DB) ZExpireAt(key []byte, when int64) (int64, error) {
- if when <= time.Now().Unix() {
- return 0, errExpireValue
- }
-
- return db.zExpireAt(key, when)
-}
-
-func (db *DB) ZTTL(key []byte) (int64, error) {
- if err := checkKeySize(key); err != nil {
- return -1, err
- }
-
- return db.ttl(ZSetType, key)
-}
-
-func (db *DB) ZPersist(key []byte) (int64, error) {
- if err := checkKeySize(key); err != nil {
- return 0, err
- }
-
- t := db.zsetBatch
- t.Lock()
- defer t.Unlock()
-
- n, err := db.rmExpire(t, ZSetType, key)
- if err != nil {
- return 0, err
- }
-
- err = t.Commit()
- return n, err
-}
-
-func getAggregateFunc(aggregate byte) func(int64, int64) int64 {
- switch aggregate {
- case AggregateSum:
- return func(a int64, b int64) int64 {
- return a + b
- }
- case AggregateMax:
- return func(a int64, b int64) int64 {
- if a > b {
- return a
- }
- return b
- }
- case AggregateMin:
- return func(a int64, b int64) int64 {
- if a > b {
- return b
- }
- return a
- }
- }
- return nil
-}
-
-func (db *DB) ZUnionStore(destKey []byte, srcKeys [][]byte, weights []int64, aggregate byte) (int64, error) {
-
- var destMap = map[string]int64{}
- aggregateFunc := getAggregateFunc(aggregate)
- if aggregateFunc == nil {
- return 0, errInvalidAggregate
- }
- if len(srcKeys) < 1 {
- return 0, errInvalidSrcKeyNum
- }
- if weights != nil {
- if len(srcKeys) != len(weights) {
- return 0, errInvalidWeightNum
- }
- } else {
- weights = make([]int64, len(srcKeys))
- for i := 0; i < len(weights); i++ {
- weights[i] = 1
- }
- }
-
- for i, key := range srcKeys {
- scorePairs, err := db.ZRange(key, 0, -1)
- if err != nil {
- return 0, err
- }
- for _, pair := range scorePairs {
- if score, ok := destMap[String(pair.Member)]; !ok {
- destMap[String(pair.Member)] = pair.Score * weights[i]
- } else {
- destMap[String(pair.Member)] = aggregateFunc(score, pair.Score*weights[i])
- }
- }
- }
-
- t := db.zsetBatch
- t.Lock()
- defer t.Unlock()
-
- db.zDelete(t, destKey)
-
- for member, score := range destMap {
- if err := checkZSetKMSize(destKey, []byte(member)); err != nil {
- return 0, err
- }
-
- if _, err := db.zSetItem(t, destKey, score, []byte(member)); err != nil {
- return 0, err
- }
- }
-
- var num = int64(len(destMap))
- sk := db.zEncodeSizeKey(destKey)
- t.Put(sk, PutInt64(num))
-
- //todo add binlog
- if err := t.Commit(); err != nil {
- return 0, err
- }
- return num, nil
-}
-
-func (db *DB) ZInterStore(destKey []byte, srcKeys [][]byte, weights []int64, aggregate byte) (int64, error) {
-
- aggregateFunc := getAggregateFunc(aggregate)
- if aggregateFunc == nil {
- return 0, errInvalidAggregate
- }
- if len(srcKeys) < 1 {
- return 0, errInvalidSrcKeyNum
- }
- if weights != nil {
- if len(srcKeys) != len(weights) {
- return 0, errInvalidWeightNum
- }
- } else {
- weights = make([]int64, len(srcKeys))
- for i := 0; i < len(weights); i++ {
- weights[i] = 1
- }
- }
-
- var destMap = map[string]int64{}
- scorePairs, err := db.ZRange(srcKeys[0], 0, -1)
- if err != nil {
- return 0, err
- }
- for _, pair := range scorePairs {
- destMap[String(pair.Member)] = pair.Score * weights[0]
- }
-
- for i, key := range srcKeys[1:] {
- scorePairs, err := db.ZRange(key, 0, -1)
- if err != nil {
- return 0, err
- }
- tmpMap := map[string]int64{}
- for _, pair := range scorePairs {
- if score, ok := destMap[String(pair.Member)]; ok {
- tmpMap[String(pair.Member)] = aggregateFunc(score, pair.Score*weights[i+1])
- }
- }
- destMap = tmpMap
- }
-
- t := db.zsetBatch
- t.Lock()
- defer t.Unlock()
-
- db.zDelete(t, destKey)
-
- for member, score := range destMap {
- if err := checkZSetKMSize(destKey, []byte(member)); err != nil {
- return 0, err
- }
- if _, err := db.zSetItem(t, destKey, score, []byte(member)); err != nil {
- return 0, err
- }
- }
-
- var num int64 = int64(len(destMap))
- sk := db.zEncodeSizeKey(destKey)
- t.Put(sk, PutInt64(num))
- //todo add binlog
- if err := t.Commit(); err != nil {
- return 0, err
- }
- return num, nil
-}
-
-func (db *DB) ZScan(key []byte, count int, inclusive bool, match string) ([][]byte, error) {
- return db.scan(ZSizeType, key, count, inclusive, match)
-}
+++ /dev/null
-package nodb
-
-import (
- "errors"
- "fmt"
-
- "gitea.com/lunny/nodb/store"
-)
-
-var (
- ErrNestTx = errors.New("nest transaction not supported")
- ErrTxDone = errors.New("Transaction has already been committed or rolled back")
-)
-
-type Tx struct {
- *DB
-
- tx *store.Tx
-
- logs [][]byte
-}
-
-func (db *DB) IsTransaction() bool {
- return db.status == DBInTransaction
-}
-
-// Begin a transaction, it will block all other write operations before calling Commit or Rollback.
-// You must be very careful to prevent long-time transaction.
-func (db *DB) Begin() (*Tx, error) {
- if db.IsTransaction() {
- return nil, ErrNestTx
- }
-
- tx := new(Tx)
-
- tx.DB = new(DB)
- tx.DB.l = db.l
-
- tx.l.wLock.Lock()
-
- tx.DB.sdb = db.sdb
-
- var err error
- tx.tx, err = db.sdb.Begin()
- if err != nil {
- tx.l.wLock.Unlock()
- return nil, err
- }
-
- tx.DB.bucket = tx.tx
-
- tx.DB.status = DBInTransaction
-
- tx.DB.index = db.index
-
- tx.DB.kvBatch = tx.newBatch()
- tx.DB.listBatch = tx.newBatch()
- tx.DB.hashBatch = tx.newBatch()
- tx.DB.zsetBatch = tx.newBatch()
- tx.DB.binBatch = tx.newBatch()
- tx.DB.setBatch = tx.newBatch()
-
- return tx, nil
-}
-
-func (tx *Tx) Commit() error {
- if tx.tx == nil {
- return ErrTxDone
- }
-
- tx.l.commitLock.Lock()
- err := tx.tx.Commit()
- tx.tx = nil
-
- if len(tx.logs) > 0 {
- tx.l.binlog.Log(tx.logs...)
- }
-
- tx.l.commitLock.Unlock()
-
- tx.l.wLock.Unlock()
-
- tx.DB.bucket = nil
-
- return err
-}
-
-func (tx *Tx) Rollback() error {
- if tx.tx == nil {
- return ErrTxDone
- }
-
- err := tx.tx.Rollback()
- tx.tx = nil
-
- tx.l.wLock.Unlock()
- tx.DB.bucket = nil
-
- return err
-}
-
-func (tx *Tx) newBatch() *batch {
- return tx.l.newBatch(tx.tx.NewWriteBatch(), &txBatchLocker{}, tx)
-}
-
-func (tx *Tx) Select(index int) error {
- if index < 0 || index >= int(MaxDBNumber) {
- return fmt.Errorf("invalid db index %d", index)
- }
-
- tx.DB.index = uint8(index)
- return nil
-}
+++ /dev/null
-package nodb
-
-import (
- "encoding/binary"
- "errors"
- "reflect"
- "strconv"
- "unsafe"
-)
-
-var errIntNumber = errors.New("invalid integer")
-
-// no copy to change slice to string
-// use your own risk
-func String(b []byte) (s string) {
- pbytes := (*reflect.SliceHeader)(unsafe.Pointer(&b))
- pstring := (*reflect.StringHeader)(unsafe.Pointer(&s))
- pstring.Data = pbytes.Data
- pstring.Len = pbytes.Len
- return
-}
-
-// no copy to change string to slice
-// use your own risk
-func Slice(s string) (b []byte) {
- pbytes := (*reflect.SliceHeader)(unsafe.Pointer(&b))
- pstring := (*reflect.StringHeader)(unsafe.Pointer(&s))
- pbytes.Data = pstring.Data
- pbytes.Len = pstring.Len
- pbytes.Cap = pstring.Len
- return
-}
-
-func Int64(v []byte, err error) (int64, error) {
- if err != nil {
- return 0, err
- } else if v == nil || len(v) == 0 {
- return 0, nil
- } else if len(v) != 8 {
- return 0, errIntNumber
- }
-
- return int64(binary.LittleEndian.Uint64(v)), nil
-}
-
-func PutInt64(v int64) []byte {
- var b []byte
- pbytes := (*reflect.SliceHeader)(unsafe.Pointer(&b))
- pbytes.Data = uintptr(unsafe.Pointer(&v))
- pbytes.Len = 8
- pbytes.Cap = 8
- return b
-}
-
-func StrInt64(v []byte, err error) (int64, error) {
- if err != nil {
- return 0, err
- } else if v == nil {
- return 0, nil
- } else {
- return strconv.ParseInt(String(v), 10, 64)
- }
-}
-
-func StrInt32(v []byte, err error) (int32, error) {
- if err != nil {
- return 0, err
- } else if v == nil {
- return 0, nil
- } else {
- res, err := strconv.ParseInt(String(v), 10, 32)
- return int32(res), err
- }
-}
-
-func StrInt8(v []byte, err error) (int8, error) {
- if err != nil {
- return 0, err
- } else if v == nil {
- return 0, nil
- } else {
- res, err := strconv.ParseInt(String(v), 10, 8)
- return int8(res), err
- }
-}
-
-func StrPutInt64(v int64) []byte {
- return strconv.AppendInt(nil, v, 10)
-}
-
-func MinUInt32(a uint32, b uint32) uint32 {
- if a > b {
- return b
- } else {
- return a
- }
-}
-
-func MaxUInt32(a uint32, b uint32) uint32 {
- if a > b {
- return a
- } else {
- return b
- }
-}
-
-func MaxInt32(a int32, b int32) int32 {
- if a > b {
- return a
- } else {
- return b
- }
-}
+++ /dev/null
-kind: pipeline
-name: go1-1-1
-
-steps:
-- name: test
- image: golang:1.11
- environment:
- GOPROXY: https://goproxy.cn
- commands:
- - go build -v
- - go test -v -race -coverprofile=coverage.txt -covermode=atomic
-
----
-kind: pipeline
-name: go1-1-2
-
-steps:
-- name: test
- image: golang:1.12
- environment:
- GOPROXY: https://goproxy.cn
- commands:
- - go build -v
- - go test -v -race -coverprofile=coverage.txt -covermode=atomic
\ No newline at end of file
+++ /dev/null
-Apache License
-Version 2.0, January 2004
-http://www.apache.org/licenses/
-
-TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-1. Definitions.
-
-"License" shall mean the terms and conditions for use, reproduction, and
-distribution as defined by Sections 1 through 9 of this document.
-
-"Licensor" shall mean the copyright owner or entity authorized by the copyright
-owner that is granting the License.
-
-"Legal Entity" shall mean the union of the acting entity and all other entities
-that control, are controlled by, or are under common control with that entity.
-For the purposes of this definition, "control" means (i) the power, direct or
-indirect, to cause the direction or management of such entity, whether by
-contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
-outstanding shares, or (iii) beneficial ownership of such entity.
-
-"You" (or "Your") shall mean an individual or Legal Entity exercising
-permissions granted by this License.
-
-"Source" form shall mean the preferred form for making modifications, including
-but not limited to software source code, documentation source, and configuration
-files.
-
-"Object" form shall mean any form resulting from mechanical transformation or
-translation of a Source form, including but not limited to compiled object code,
-generated documentation, and conversions to other media types.
-
-"Work" shall mean the work of authorship, whether in Source or Object form, made
-available under the License, as indicated by a copyright notice that is included
-in or attached to the work (an example is provided in the Appendix below).
-
-"Derivative Works" shall mean any work, whether in Source or Object form, that
-is based on (or derived from) the Work and for which the editorial revisions,
-annotations, elaborations, or other modifications represent, as a whole, an
-original work of authorship. For the purposes of this License, Derivative Works
-shall not include works that remain separable from, or merely link (or bind by
-name) to the interfaces of, the Work and Derivative Works thereof.
-
-"Contribution" shall mean any work of authorship, including the original version
-of the Work and any modifications or additions to that Work or Derivative Works
-thereof, that is intentionally submitted to Licensor for inclusion in the Work
-by the copyright owner or by an individual or Legal Entity authorized to submit
-on behalf of the copyright owner. For the purposes of this definition,
-"submitted" means any form of electronic, verbal, or written communication sent
-to the Licensor or its representatives, including but not limited to
-communication on electronic mailing lists, source code control systems, and
-issue tracking systems that are managed by, or on behalf of, the Licensor for
-the purpose of discussing and improving the Work, but excluding communication
-that is conspicuously marked or otherwise designated in writing by the copyright
-owner as "Not a Contribution."
-
-"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
-of whom a Contribution has been received by Licensor and subsequently
-incorporated within the Work.
-
-2. Grant of Copyright License.
-
-Subject to the terms and conditions of this License, each Contributor hereby
-grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
-irrevocable copyright license to reproduce, prepare Derivative Works of,
-publicly display, publicly perform, sublicense, and distribute the Work and such
-Derivative Works in Source or Object form.
-
-3. Grant of Patent License.
-
-Subject to the terms and conditions of this License, each Contributor hereby
-grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
-irrevocable (except as stated in this section) patent license to make, have
-made, use, offer to sell, sell, import, and otherwise transfer the Work, where
-such license applies only to those patent claims licensable by such Contributor
-that are necessarily infringed by their Contribution(s) alone or by combination
-of their Contribution(s) with the Work to which such Contribution(s) was
-submitted. If You institute patent litigation against any entity (including a
-cross-claim or counterclaim in a lawsuit) alleging that the Work or a
-Contribution incorporated within the Work constitutes direct or contributory
-patent infringement, then any patent licenses granted to You under this License
-for that Work shall terminate as of the date such litigation is filed.
-
-4. Redistribution.
-
-You may reproduce and distribute copies of the Work or Derivative Works thereof
-in any medium, with or without modifications, and in Source or Object form,
-provided that You meet the following conditions:
-
-You must give any other recipients of the Work or Derivative Works a copy of
-this License; and
-You must cause any modified files to carry prominent notices stating that You
-changed the files; and
-You must retain, in the Source form of any Derivative Works that You distribute,
-all copyright, patent, trademark, and attribution notices from the Source form
-of the Work, excluding those notices that do not pertain to any part of the
-Derivative Works; and
-If the Work includes a "NOTICE" text file as part of its distribution, then any
-Derivative Works that You distribute must include a readable copy of the
-attribution notices contained within such NOTICE file, excluding those notices
-that do not pertain to any part of the Derivative Works, in at least one of the
-following places: within a NOTICE text file distributed as part of the
-Derivative Works; within the Source form or documentation, if provided along
-with the Derivative Works; or, within a display generated by the Derivative
-Works, if and wherever such third-party notices normally appear. The contents of
-the NOTICE file are for informational purposes only and do not modify the
-License. You may add Your own attribution notices within Derivative Works that
-You distribute, alongside or as an addendum to the NOTICE text from the Work,
-provided that such additional attribution notices cannot be construed as
-modifying the License.
-You may add Your own copyright statement to Your modifications and may provide
-additional or different license terms and conditions for use, reproduction, or
-distribution of Your modifications, or for any such Derivative Works as a whole,
-provided Your use, reproduction, and distribution of the Work otherwise complies
-with the conditions stated in this License.
-
-5. Submission of Contributions.
-
-Unless You explicitly state otherwise, any Contribution intentionally submitted
-for inclusion in the Work by You to the Licensor shall be under the terms and
-conditions of this License, without any additional terms or conditions.
-Notwithstanding the above, nothing herein shall supersede or modify the terms of
-any separate license agreement you may have executed with Licensor regarding
-such Contributions.
-
-6. Trademarks.
-
-This License does not grant permission to use the trade names, trademarks,
-service marks, or product names of the Licensor, except as required for
-reasonable and customary use in describing the origin of the Work and
-reproducing the content of the NOTICE file.
-
-7. Disclaimer of Warranty.
-
-Unless required by applicable law or agreed to in writing, Licensor provides the
-Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
-including, without limitation, any warranties or conditions of TITLE,
-NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
-solely responsible for determining the appropriateness of using or
-redistributing the Work and assume any risks associated with Your exercise of
-permissions under this License.
-
-8. Limitation of Liability.
-
-In no event and under no legal theory, whether in tort (including negligence),
-contract, or otherwise, unless required by applicable law (such as deliberate
-and grossly negligent acts) or agreed to in writing, shall any Contributor be
-liable to You for damages, including any direct, indirect, special, incidental,
-or consequential damages of any character arising as a result of this License or
-out of the use or inability to use the Work (including but not limited to
-damages for loss of goodwill, work stoppage, computer failure or malfunction, or
-any and all other commercial damages or losses), even if such Contributor has
-been advised of the possibility of such damages.
-
-9. Accepting Warranty or Additional Liability.
-
-While redistributing the Work or Derivative Works thereof, You may choose to
-offer, and charge a fee for, acceptance of support, warranty, indemnity, or
-other liability obligations and/or rights consistent with this License. However,
-in accepting such obligations, You may act only on Your own behalf and on Your
-sole responsibility, not on behalf of any other Contributor, and only if You
-agree to indemnify, defend, and hold each Contributor harmless for any liability
-incurred by, or claims asserted against, such Contributor by reason of your
-accepting any such warranty or additional liability.
-
-END OF TERMS AND CONDITIONS
-
-APPENDIX: How to apply the Apache License to your work
-
-To apply the Apache License to your work, attach the following boilerplate
-notice, with the fields enclosed by brackets "[]" replaced with your own
-identifying information. (Don't include the brackets!) The text should be
-enclosed in the appropriate comment syntax for the file format. We also
-recommend that a file or class name and description of purpose be included on
-the same "printed page" as the copyright notice for easier identification within
-third-party archives.
-
- Copyright [yyyy] [name of copyright owner]
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
\ No newline at end of file
+++ /dev/null
-# binding [![Build Status](https://travis-ci.org/go-macaron/binding.svg?branch=master)](https://travis-ci.org/go-macaron/binding) [![Sourcegraph](https://sourcegraph.com/github.com/go-macaron/binding/-/badge.svg)](https://sourcegraph.com/github.com/go-macaron/binding?badge)
-
-Middleware binding provides request data binding and validation for [Macaron](https://github.com/go-macaron/macaron).
-
-### Installation
-
- go get github.com/go-macaron/binding
-
-## Getting Help
-
-- [API Reference](https://gowalker.org/github.com/go-macaron/binding)
-- [Documentation](http://go-macaron.com/docs/middlewares/binding)
-
-## Credits
-
-This package is a modified version of [martini-contrib/binding](https://github.com/martini-contrib/binding).
-
-## License
-
-This project is under the Apache License, Version 2.0. See the [LICENSE](LICENSE) file for the full license text.
\ No newline at end of file
+++ /dev/null
-// Copyright 2014 Martini Authors
-// Copyright 2014 The Macaron Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-// Package binding is a middleware that provides request data binding and validation for Macaron.
-package binding
-
-import (
- "encoding/json"
- "fmt"
- "io"
- "mime/multipart"
- "net/http"
- "net/url"
- "reflect"
- "regexp"
- "strconv"
- "strings"
- "unicode/utf8"
-
- "gitea.com/macaron/macaron"
- "github.com/unknwon/com"
-)
-
-const _VERSION = "0.6.0"
-
-func Version() string {
- return _VERSION
-}
-
-func bind(ctx *macaron.Context, obj interface{}, ifacePtr ...interface{}) {
- contentType := ctx.Req.Header.Get("Content-Type")
- if ctx.Req.Method == "POST" || ctx.Req.Method == "PUT" || len(contentType) > 0 {
- switch {
- case strings.Contains(contentType, "form-urlencoded"):
- ctx.Invoke(Form(obj, ifacePtr...))
- case strings.Contains(contentType, "multipart/form-data"):
- ctx.Invoke(MultipartForm(obj, ifacePtr...))
- case strings.Contains(contentType, "json"):
- ctx.Invoke(Json(obj, ifacePtr...))
- default:
- var errors Errors
- if contentType == "" {
- errors.Add([]string{}, ERR_CONTENT_TYPE, "Empty Content-Type")
- } else {
- errors.Add([]string{}, ERR_CONTENT_TYPE, "Unsupported Content-Type")
- }
- ctx.Map(errors)
- ctx.Map(obj) // Map a fake struct so handler won't panic.
- }
- } else {
- ctx.Invoke(Form(obj, ifacePtr...))
- }
-}
-
-const (
- _JSON_CONTENT_TYPE = "application/json; charset=utf-8"
- STATUS_UNPROCESSABLE_ENTITY = 422
-)
-
-// errorHandler simply counts the number of errors in the
-// context and, if more than 0, writes a response with an
-// error code and a JSON payload describing the errors.
-// The response will have a JSON content-type.
-// Middleware remaining on the stack will not even see the request
-// if, by this point, there are any errors.
-// This is a "default" handler, of sorts, and you are
-// welcome to use your own instead. The Bind middleware
-// invokes this automatically for convenience.
-func errorHandler(errs Errors, rw http.ResponseWriter) {
- if len(errs) > 0 {
- rw.Header().Set("Content-Type", _JSON_CONTENT_TYPE)
- if errs.Has(ERR_DESERIALIZATION) {
- rw.WriteHeader(http.StatusBadRequest)
- } else if errs.Has(ERR_CONTENT_TYPE) {
- rw.WriteHeader(http.StatusUnsupportedMediaType)
- } else {
- rw.WriteHeader(STATUS_UNPROCESSABLE_ENTITY)
- }
- errOutput, _ := json.Marshal(errs)
- rw.Write(errOutput)
- return
- }
-}
-
-// Bind wraps up the functionality of the Form and Json middleware
-// according to the Content-Type and verb of the request.
-// A Content-Type is required for POST and PUT requests.
-// Bind invokes the ErrorHandler middleware to bail out if errors
-// occurred. If you want to perform your own error handling, use
-// Form or Json middleware directly. An interface pointer can
-// be added as a second argument in order to map the struct to
-// a specific interface.
-func Bind(obj interface{}, ifacePtr ...interface{}) macaron.Handler {
- return func(ctx *macaron.Context) {
- bind(ctx, obj, ifacePtr...)
- if handler, ok := obj.(ErrorHandler); ok {
- ctx.Invoke(handler.Error)
- } else {
- ctx.Invoke(errorHandler)
- }
- }
-}
-
-// BindIgnErr will do the exactly same thing as Bind but without any
-// error handling, which user has freedom to deal with them.
-// This allows user take advantages of validation.
-func BindIgnErr(obj interface{}, ifacePtr ...interface{}) macaron.Handler {
- return func(ctx *macaron.Context) {
- bind(ctx, obj, ifacePtr...)
- }
-}
-
-// Form is middleware to deserialize form-urlencoded data from the request.
-// It gets data from the form-urlencoded body, if present, or from the
-// query string. It uses the http.Request.ParseForm() method
-// to perform deserialization, then reflection is used to map each field
-// into the struct with the proper type. Structs with primitive slice types
-// (bool, float, int, string) can support deserialization of repeated form
-// keys, for example: key=val1&key=val2&key=val3
-// An interface pointer can be added as a second argument in order
-// to map the struct to a specific interface.
-func Form(formStruct interface{}, ifacePtr ...interface{}) macaron.Handler {
- return func(ctx *macaron.Context) {
- var errors Errors
-
- ensureNotPointer(formStruct)
- formStruct := reflect.New(reflect.TypeOf(formStruct))
- parseErr := ctx.Req.ParseForm()
-
- // Format validation of the request body or the URL would add considerable overhead,
- // and ParseForm does not complain when URL encoding is off.
- // Because an empty request body or url can also mean absence of all needed values,
- // it is not in all cases a bad request, so let's return 422.
- if parseErr != nil {
- errors.Add([]string{}, ERR_DESERIALIZATION, parseErr.Error())
- }
- errors = mapForm(formStruct, ctx.Req.Form, nil, errors)
- validateAndMap(formStruct, ctx, errors, ifacePtr...)
- }
-}
-
-// Maximum amount of memory to use when parsing a multipart form.
-// Set this to whatever value you prefer; default is 10 MB.
-var MaxMemory = int64(1024 * 1024 * 10)
-
-// MultipartForm works much like Form, except it can parse multipart forms
-// and handle file uploads. Like the other deserialization middleware handlers,
-// you can pass in an interface to make the interface available for injection
-// into other handlers later.
-func MultipartForm(formStruct interface{}, ifacePtr ...interface{}) macaron.Handler {
- return func(ctx *macaron.Context) {
- var errors Errors
- ensureNotPointer(formStruct)
- formStruct := reflect.New(reflect.TypeOf(formStruct))
- // This if check is necessary due to https://github.com/martini-contrib/csrf/issues/6
- if ctx.Req.MultipartForm == nil {
- // Workaround for multipart forms returning nil instead of an error
- // when content is not multipart; see https://code.google.com/p/go/issues/detail?id=6334
- if multipartReader, err := ctx.Req.MultipartReader(); err != nil {
- errors.Add([]string{}, ERR_DESERIALIZATION, err.Error())
- } else {
- form, parseErr := multipartReader.ReadForm(MaxMemory)
- if parseErr != nil {
- errors.Add([]string{}, ERR_DESERIALIZATION, parseErr.Error())
- }
-
- if ctx.Req.Form == nil {
- ctx.Req.ParseForm()
- }
- for k, v := range form.Value {
- ctx.Req.Form[k] = append(ctx.Req.Form[k], v...)
- }
-
- ctx.Req.MultipartForm = form
- }
- }
- errors = mapForm(formStruct, ctx.Req.MultipartForm.Value, ctx.Req.MultipartForm.File, errors)
- validateAndMap(formStruct, ctx, errors, ifacePtr...)
- }
-}
-
-// Json is middleware to deserialize a JSON payload from the request
-// into the struct that is passed in. The resulting struct is then
-// validated, but no error handling is actually performed here.
-// An interface pointer can be added as a second argument in order
-// to map the struct to a specific interface.
-func Json(jsonStruct interface{}, ifacePtr ...interface{}) macaron.Handler {
- return func(ctx *macaron.Context) {
- var errors Errors
- ensureNotPointer(jsonStruct)
- jsonStruct := reflect.New(reflect.TypeOf(jsonStruct))
- if ctx.Req.Request.Body != nil {
- defer ctx.Req.Request.Body.Close()
- err := json.NewDecoder(ctx.Req.Request.Body).Decode(jsonStruct.Interface())
- if err != nil && err != io.EOF {
- errors.Add([]string{}, ERR_DESERIALIZATION, err.Error())
- }
- }
- validateAndMap(jsonStruct, ctx, errors, ifacePtr...)
- }
-}
-
-// RawValidate is same as Validate but does not require a HTTP context,
-// and can be used independently just for validation.
-// This function does not support Validator interface.
-func RawValidate(obj interface{}) Errors {
- var errs Errors
- v := reflect.ValueOf(obj)
- k := v.Kind()
- if k == reflect.Interface || k == reflect.Ptr {
- v = v.Elem()
- k = v.Kind()
- }
- if k == reflect.Slice || k == reflect.Array {
- for i := 0; i < v.Len(); i++ {
- e := v.Index(i).Interface()
- errs = validateStruct(errs, e)
- }
- } else {
- errs = validateStruct(errs, obj)
- }
- return errs
-}
-
-// Validate is middleware to enforce required fields. If the struct
-// passed in implements Validator, then the user-defined Validate method
-// is executed, and its errors are mapped to the context. This middleware
-// performs no error handling: it merely detects errors and maps them.
-func Validate(obj interface{}) macaron.Handler {
- return func(ctx *macaron.Context) {
- var errs Errors
- v := reflect.ValueOf(obj)
- k := v.Kind()
- if k == reflect.Interface || k == reflect.Ptr {
- v = v.Elem()
- k = v.Kind()
- }
- if k == reflect.Slice || k == reflect.Array {
- for i := 0; i < v.Len(); i++ {
- e := v.Index(i).Interface()
- errs = validateStruct(errs, e)
- if validator, ok := e.(Validator); ok {
- errs = validator.Validate(ctx, errs)
- }
- }
- } else {
- errs = validateStruct(errs, obj)
- if validator, ok := obj.(Validator); ok {
- errs = validator.Validate(ctx, errs)
- }
- }
- ctx.Map(errs)
- }
-}
-
-var (
- AlphaDashPattern = regexp.MustCompile("[^\\d\\w-_]")
- AlphaDashDotPattern = regexp.MustCompile("[^\\d\\w-_\\.]")
- EmailPattern = regexp.MustCompile("[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[a-zA-Z0-9](?:[\\w-]*[\\w])?")
-)
-
-// Copied from github.com/asaskevich/govalidator.
-const _MAX_URL_RUNE_COUNT = 2083
-const _MIN_URL_RUNE_COUNT = 3
-
-var (
- urlSchemaRx = `((ftp|tcp|udp|wss?|https?):\/\/)`
- urlUsernameRx = `(\S+(:\S*)?@)`
- urlIPRx = `([1-9]\d?|1\d\d|2[01]\d|22[0-3])(\.(1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.([0-9]\d?|1\d\d|2[0-4]\d|25[0-4]))`
- ipRx = `(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))`
- urlSubdomainRx = `((www\.)|([a-zA-Z0-9]([-\.][-\._a-zA-Z0-9]+)*))`
- urlPortRx = `(:(\d{1,5}))`
- urlPathRx = `((\/|\?|#)[^\s]*)`
- URLPattern = regexp.MustCompile(`^` + urlSchemaRx + `?` + urlUsernameRx + `?` + `((` + urlIPRx + `|(\[` + ipRx + `\])|(([a-zA-Z0-9]([a-zA-Z0-9-_]+)?[a-zA-Z0-9]([-\.][a-zA-Z0-9]+)*)|(` + urlSubdomainRx + `?))?(([a-zA-Z\x{00a1}-\x{ffff}0-9]+-?-?)*[a-zA-Z\x{00a1}-\x{ffff}0-9]+)(?:\.([a-zA-Z\x{00a1}-\x{ffff}]{1,}))?))\.?` + urlPortRx + `?` + urlPathRx + `?$`)
-)
-
-// IsURL check if the string is an URL.
-func isURL(str string) bool {
- if str == "" || utf8.RuneCountInString(str) >= _MAX_URL_RUNE_COUNT || len(str) <= _MIN_URL_RUNE_COUNT || strings.HasPrefix(str, ".") {
- return false
- }
- u, err := url.Parse(str)
- if err != nil {
- return false
- }
- if strings.HasPrefix(u.Host, ".") {
- return false
- }
- if u.Host == "" && (u.Path != "" && !strings.Contains(u.Path, ".")) {
- return false
- }
- return URLPattern.MatchString(str)
-
-}
-
-type (
- // Rule represents a validation rule.
- Rule struct {
- // IsMatch checks if rule matches.
- IsMatch func(string) bool
- // IsValid applies validation rule to condition.
- IsValid func(Errors, string, interface{}) (bool, Errors)
- }
-
- // ParamRule does same thing as Rule but passes rule itself to IsValid method.
- ParamRule struct {
- // IsMatch checks if rule matches.
- IsMatch func(string) bool
- // IsValid applies validation rule to condition.
- IsValid func(Errors, string, string, interface{}) (bool, Errors)
- }
-
- // RuleMapper and ParamRuleMapper represent validation rule mappers,
- // it allwos users to add custom validation rules.
- RuleMapper []*Rule
- ParamRuleMapper []*ParamRule
-)
-
-var ruleMapper RuleMapper
-var paramRuleMapper ParamRuleMapper
-
-// AddRule adds new validation rule.
-func AddRule(r *Rule) {
- ruleMapper = append(ruleMapper, r)
-}
-
-// AddParamRule adds new validation rule.
-func AddParamRule(r *ParamRule) {
- paramRuleMapper = append(paramRuleMapper, r)
-}
-
-func in(fieldValue interface{}, arr string) bool {
- val := fmt.Sprintf("%v", fieldValue)
- vals := strings.Split(arr, ",")
- isIn := false
- for _, v := range vals {
- if v == val {
- isIn = true
- break
- }
- }
- return isIn
-}
-
-func parseFormName(raw, actual string) string {
- if len(actual) > 0 {
- return actual
- }
- return nameMapper(raw)
-}
-
-// Performs required field checking on a struct
-func validateStruct(errors Errors, obj interface{}) Errors {
- typ := reflect.TypeOf(obj)
- val := reflect.ValueOf(obj)
-
- if typ.Kind() == reflect.Ptr {
- typ = typ.Elem()
- val = val.Elem()
- }
-
- for i := 0; i < typ.NumField(); i++ {
- field := typ.Field(i)
-
- // Allow ignored fields in the struct
- if field.Tag.Get("form") == "-" || !val.Field(i).CanInterface() {
- continue
- }
-
- fieldVal := val.Field(i)
- fieldValue := fieldVal.Interface()
- zero := reflect.Zero(field.Type).Interface()
-
- // Validate nested and embedded structs (if pointer, only do so if not nil)
- if field.Type.Kind() == reflect.Struct ||
- (field.Type.Kind() == reflect.Ptr && !reflect.DeepEqual(zero, fieldValue) &&
- field.Type.Elem().Kind() == reflect.Struct) {
- errors = validateStruct(errors, fieldValue)
- }
- errors = validateField(errors, zero, field, fieldVal, fieldValue)
- }
- return errors
-}
-
-func validateField(errors Errors, zero interface{}, field reflect.StructField, fieldVal reflect.Value, fieldValue interface{}) Errors {
- if fieldVal.Kind() == reflect.Slice {
- for i := 0; i < fieldVal.Len(); i++ {
- sliceVal := fieldVal.Index(i)
- if sliceVal.Kind() == reflect.Ptr {
- sliceVal = sliceVal.Elem()
- }
-
- sliceValue := sliceVal.Interface()
- zero := reflect.Zero(sliceVal.Type()).Interface()
- if sliceVal.Kind() == reflect.Struct ||
- (sliceVal.Kind() == reflect.Ptr && !reflect.DeepEqual(zero, sliceValue) &&
- sliceVal.Elem().Kind() == reflect.Struct) {
- errors = validateStruct(errors, sliceValue)
- }
- /* Apply validation rules to each item in a slice. ISSUE #3
- else {
- errors = validateField(errors, zero, field, sliceVal, sliceValue)
- }*/
- }
- }
-
-VALIDATE_RULES:
- for _, rule := range strings.Split(field.Tag.Get("binding"), ";") {
- if len(rule) == 0 {
- continue
- }
-
- switch {
- case rule == "OmitEmpty":
- if reflect.DeepEqual(zero, fieldValue) {
- break VALIDATE_RULES
- }
- case rule == "Required":
- v := reflect.ValueOf(fieldValue)
- if v.Kind() == reflect.Slice {
- if v.Len() == 0 {
- errors.Add([]string{field.Name}, ERR_REQUIRED, "Required")
- break VALIDATE_RULES
- }
-
- continue
- }
-
- if reflect.DeepEqual(zero, fieldValue) {
- errors.Add([]string{field.Name}, ERR_REQUIRED, "Required")
- break VALIDATE_RULES
- }
- case rule == "AlphaDash":
- if AlphaDashPattern.MatchString(fmt.Sprintf("%v", fieldValue)) {
- errors.Add([]string{field.Name}, ERR_ALPHA_DASH, "AlphaDash")
- break VALIDATE_RULES
- }
- case rule == "AlphaDashDot":
- if AlphaDashDotPattern.MatchString(fmt.Sprintf("%v", fieldValue)) {
- errors.Add([]string{field.Name}, ERR_ALPHA_DASH_DOT, "AlphaDashDot")
- break VALIDATE_RULES
- }
- case strings.HasPrefix(rule, "Size("):
- size, _ := strconv.Atoi(rule[5 : len(rule)-1])
- if str, ok := fieldValue.(string); ok && utf8.RuneCountInString(str) != size {
- errors.Add([]string{field.Name}, ERR_SIZE, "Size")
- break VALIDATE_RULES
- }
- v := reflect.ValueOf(fieldValue)
- if v.Kind() == reflect.Slice && v.Len() != size {
- errors.Add([]string{field.Name}, ERR_SIZE, "Size")
- break VALIDATE_RULES
- }
- case strings.HasPrefix(rule, "MinSize("):
- min, _ := strconv.Atoi(rule[8 : len(rule)-1])
- if str, ok := fieldValue.(string); ok && utf8.RuneCountInString(str) < min {
- errors.Add([]string{field.Name}, ERR_MIN_SIZE, "MinSize")
- break VALIDATE_RULES
- }
- v := reflect.ValueOf(fieldValue)
- if v.Kind() == reflect.Slice && v.Len() < min {
- errors.Add([]string{field.Name}, ERR_MIN_SIZE, "MinSize")
- break VALIDATE_RULES
- }
- case strings.HasPrefix(rule, "MaxSize("):
- max, _ := strconv.Atoi(rule[8 : len(rule)-1])
- if str, ok := fieldValue.(string); ok && utf8.RuneCountInString(str) > max {
- errors.Add([]string{field.Name}, ERR_MAX_SIZE, "MaxSize")
- break VALIDATE_RULES
- }
- v := reflect.ValueOf(fieldValue)
- if v.Kind() == reflect.Slice && v.Len() > max {
- errors.Add([]string{field.Name}, ERR_MAX_SIZE, "MaxSize")
- break VALIDATE_RULES
- }
- case strings.HasPrefix(rule, "Range("):
- nums := strings.Split(rule[6:len(rule)-1], ",")
- if len(nums) != 2 {
- break VALIDATE_RULES
- }
- val := com.StrTo(fmt.Sprintf("%v", fieldValue)).MustInt()
- if val < com.StrTo(nums[0]).MustInt() || val > com.StrTo(nums[1]).MustInt() {
- errors.Add([]string{field.Name}, ERR_RANGE, "Range")
- break VALIDATE_RULES
- }
- case rule == "Email":
- if !EmailPattern.MatchString(fmt.Sprintf("%v", fieldValue)) {
- errors.Add([]string{field.Name}, ERR_EMAIL, "Email")
- break VALIDATE_RULES
- }
- case rule == "Url":
- str := fmt.Sprintf("%v", fieldValue)
- if len(str) == 0 {
- continue
- } else if !isURL(str) {
- errors.Add([]string{field.Name}, ERR_URL, "Url")
- break VALIDATE_RULES
- }
- case strings.HasPrefix(rule, "In("):
- if !in(fieldValue, rule[3:len(rule)-1]) {
- errors.Add([]string{field.Name}, ERR_IN, "In")
- break VALIDATE_RULES
- }
- case strings.HasPrefix(rule, "NotIn("):
- if in(fieldValue, rule[6:len(rule)-1]) {
- errors.Add([]string{field.Name}, ERR_NOT_INT, "NotIn")
- break VALIDATE_RULES
- }
- case strings.HasPrefix(rule, "Include("):
- if !strings.Contains(fmt.Sprintf("%v", fieldValue), rule[8:len(rule)-1]) {
- errors.Add([]string{field.Name}, ERR_INCLUDE, "Include")
- break VALIDATE_RULES
- }
- case strings.HasPrefix(rule, "Exclude("):
- if strings.Contains(fmt.Sprintf("%v", fieldValue), rule[8:len(rule)-1]) {
- errors.Add([]string{field.Name}, ERR_EXCLUDE, "Exclude")
- break VALIDATE_RULES
- }
- case strings.HasPrefix(rule, "Default("):
- if reflect.DeepEqual(zero, fieldValue) {
- if fieldVal.CanAddr() {
- errors = setWithProperType(field.Type.Kind(), rule[8:len(rule)-1], fieldVal, field.Tag.Get("form"), errors)
- } else {
- errors.Add([]string{field.Name}, ERR_EXCLUDE, "Default")
- break VALIDATE_RULES
- }
- }
- default:
- // Apply custom validation rules
- var isValid bool
- for i := range ruleMapper {
- if ruleMapper[i].IsMatch(rule) {
- isValid, errors = ruleMapper[i].IsValid(errors, field.Name, fieldValue)
- if !isValid {
- break VALIDATE_RULES
- }
- }
- }
- for i := range paramRuleMapper {
- if paramRuleMapper[i].IsMatch(rule) {
- isValid, errors = paramRuleMapper[i].IsValid(errors, rule, field.Name, fieldValue)
- if !isValid {
- break VALIDATE_RULES
- }
- }
- }
- }
- }
- return errors
-}
-
-// NameMapper represents a form tag name mapper.
-type NameMapper func(string) string
-
-var (
- nameMapper = func(field string) string {
- newstr := make([]rune, 0, len(field))
- for i, chr := range field {
- if isUpper := 'A' <= chr && chr <= 'Z'; isUpper {
- if i > 0 {
- newstr = append(newstr, '_')
- }
- chr -= ('A' - 'a')
- }
- newstr = append(newstr, chr)
- }
- return string(newstr)
- }
-)
-
-// SetNameMapper sets name mapper.
-func SetNameMapper(nm NameMapper) {
- nameMapper = nm
-}
-
-// Takes values from the form data and puts them into a struct
-func mapForm(formStruct reflect.Value, form map[string][]string,
- formfile map[string][]*multipart.FileHeader, errors Errors) Errors {
-
- if formStruct.Kind() == reflect.Ptr {
- formStruct = formStruct.Elem()
- }
- typ := formStruct.Type()
-
- for i := 0; i < typ.NumField(); i++ {
- typeField := typ.Field(i)
- structField := formStruct.Field(i)
-
- if typeField.Type.Kind() == reflect.Ptr && typeField.Anonymous {
- structField.Set(reflect.New(typeField.Type.Elem()))
- errors = mapForm(structField.Elem(), form, formfile, errors)
- if reflect.DeepEqual(structField.Elem().Interface(), reflect.Zero(structField.Elem().Type()).Interface()) {
- structField.Set(reflect.Zero(structField.Type()))
- }
- } else if typeField.Type.Kind() == reflect.Struct {
- errors = mapForm(structField, form, formfile, errors)
- }
-
- inputFieldName := parseFormName(typeField.Name, typeField.Tag.Get("form"))
- if len(inputFieldName) == 0 || !structField.CanSet() {
- continue
- }
-
- inputValue, exists := form[inputFieldName]
- if exists {
- numElems := len(inputValue)
- if structField.Kind() == reflect.Slice && numElems > 0 {
- sliceOf := structField.Type().Elem().Kind()
- slice := reflect.MakeSlice(structField.Type(), numElems, numElems)
- for i := 0; i < numElems; i++ {
- errors = setWithProperType(sliceOf, inputValue[i], slice.Index(i), inputFieldName, errors)
- }
- formStruct.Field(i).Set(slice)
- } else {
- errors = setWithProperType(typeField.Type.Kind(), inputValue[0], structField, inputFieldName, errors)
- }
- continue
- }
-
- inputFile, exists := formfile[inputFieldName]
- if !exists {
- continue
- }
- fhType := reflect.TypeOf((*multipart.FileHeader)(nil))
- numElems := len(inputFile)
- if structField.Kind() == reflect.Slice && numElems > 0 && structField.Type().Elem() == fhType {
- slice := reflect.MakeSlice(structField.Type(), numElems, numElems)
- for i := 0; i < numElems; i++ {
- slice.Index(i).Set(reflect.ValueOf(inputFile[i]))
- }
- structField.Set(slice)
- } else if structField.Type() == fhType {
- structField.Set(reflect.ValueOf(inputFile[0]))
- }
- }
- return errors
-}
-
-// This sets the value in a struct of an indeterminate type to the
-// matching value from the request (via Form middleware) in the
-// same type, so that not all deserialized values have to be strings.
-// Supported types are string, int, float, and bool.
-func setWithProperType(valueKind reflect.Kind, val string, structField reflect.Value, nameInTag string, errors Errors) Errors {
- switch valueKind {
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- if val == "" {
- val = "0"
- }
- intVal, err := strconv.ParseInt(val, 10, 64)
- if err != nil {
- errors.Add([]string{nameInTag}, ERR_INTERGER_TYPE, "Value could not be parsed as integer")
- } else {
- structField.SetInt(intVal)
- }
- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
- if val == "" {
- val = "0"
- }
- uintVal, err := strconv.ParseUint(val, 10, 64)
- if err != nil {
- errors.Add([]string{nameInTag}, ERR_INTERGER_TYPE, "Value could not be parsed as unsigned integer")
- } else {
- structField.SetUint(uintVal)
- }
- case reflect.Bool:
- if val == "on" {
- structField.SetBool(true)
- break
- }
-
- if val == "" {
- val = "false"
- }
- boolVal, err := strconv.ParseBool(val)
- if err != nil {
- errors.Add([]string{nameInTag}, ERR_BOOLEAN_TYPE, "Value could not be parsed as boolean")
- } else if boolVal {
- structField.SetBool(true)
- }
- case reflect.Float32:
- if val == "" {
- val = "0.0"
- }
- floatVal, err := strconv.ParseFloat(val, 32)
- if err != nil {
- errors.Add([]string{nameInTag}, ERR_FLOAT_TYPE, "Value could not be parsed as 32-bit float")
- } else {
- structField.SetFloat(floatVal)
- }
- case reflect.Float64:
- if val == "" {
- val = "0.0"
- }
- floatVal, err := strconv.ParseFloat(val, 64)
- if err != nil {
- errors.Add([]string{nameInTag}, ERR_FLOAT_TYPE, "Value could not be parsed as 64-bit float")
- } else {
- structField.SetFloat(floatVal)
- }
- case reflect.String:
- structField.SetString(val)
- }
- return errors
-}
-
-// Don't pass in pointers to bind to. Can lead to bugs.
-func ensureNotPointer(obj interface{}) {
- if reflect.TypeOf(obj).Kind() == reflect.Ptr {
- panic("Pointers are not accepted as binding models")
- }
-}
-
-// Performs validation and combines errors from validation
-// with errors from deserialization, then maps both the
-// resulting struct and the errors to the context.
-func validateAndMap(obj reflect.Value, ctx *macaron.Context, errors Errors, ifacePtr ...interface{}) {
- ctx.Invoke(Validate(obj.Interface()))
- errors = append(errors, getErrors(ctx)...)
- ctx.Map(errors)
- ctx.Map(obj.Elem().Interface())
- if len(ifacePtr) > 0 {
- ctx.MapTo(obj.Elem().Interface(), ifacePtr[0])
- }
-}
-
-// getErrors simply gets the errors from the context (it's kind of a chore)
-func getErrors(ctx *macaron.Context) Errors {
- return ctx.GetVal(reflect.TypeOf(Errors{})).Interface().(Errors)
-}
-
-type (
- // ErrorHandler is the interface that has custom error handling process.
- ErrorHandler interface {
- // Error handles validation errors with custom process.
- Error(*macaron.Context, Errors)
- }
-
- // Validator is the interface that handles some rudimentary
- // request validation logic so your application doesn't have to.
- Validator interface {
- // Validate validates that the request is OK. It is recommended
- // that validation be limited to checking values for syntax and
- // semantics, enough to know that you can make sense of the request
- // in your application. For example, you might verify that a credit
- // card number matches a valid pattern, but you probably wouldn't
- // perform an actual credit card authorization here.
- Validate(*macaron.Context, Errors) Errors
- }
-)
+++ /dev/null
-// Copyright 2014 Martini Authors
-// Copyright 2014 The Macaron Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package binding
-
-const (
- // Type mismatch errors.
- ERR_CONTENT_TYPE = "ContentTypeError"
- ERR_DESERIALIZATION = "DeserializationError"
- ERR_INTERGER_TYPE = "IntegerTypeError"
- ERR_BOOLEAN_TYPE = "BooleanTypeError"
- ERR_FLOAT_TYPE = "FloatTypeError"
-
- // Validation errors.
- ERR_REQUIRED = "RequiredError"
- ERR_ALPHA_DASH = "AlphaDashError"
- ERR_ALPHA_DASH_DOT = "AlphaDashDotError"
- ERR_SIZE = "SizeError"
- ERR_MIN_SIZE = "MinSizeError"
- ERR_MAX_SIZE = "MaxSizeError"
- ERR_RANGE = "RangeError"
- ERR_EMAIL = "EmailError"
- ERR_URL = "UrlError"
- ERR_IN = "InError"
- ERR_NOT_INT = "NotInError"
- ERR_INCLUDE = "IncludeError"
- ERR_EXCLUDE = "ExcludeError"
- ERR_DEFAULT = "DefaultError"
-)
-
-type (
- // Errors may be generated during deserialization, binding,
- // or validation. This type is mapped to the context so you
- // can inject it into your own handlers and use it in your
- // application if you want all your errors to look the same.
- Errors []Error
-
- Error struct {
- // An error supports zero or more field names, because an
- // error can morph three ways: (1) it can indicate something
- // wrong with the request as a whole, (2) it can point to a
- // specific problem with a particular input field, or (3) it
- // can span multiple related input fields.
- FieldNames []string `json:"fieldNames,omitempty"`
-
- // The classification is like an error code, convenient to
- // use when processing or categorizing an error programmatically.
- // It may also be called the "kind" of error.
- Classification string `json:"classification,omitempty"`
-
- // Message should be human-readable and detailed enough to
- // pinpoint and resolve the problem, but it should be brief. For
- // example, a payload of 100 objects in a JSON array might have
- // an error in the 41st object. The message should help the
- // end user find and fix the error with their request.
- Message string `json:"message,omitempty"`
- }
-)
-
-// Add adds an error associated with the fields indicated
-// by fieldNames, with the given classification and message.
-func (e *Errors) Add(fieldNames []string, classification, message string) {
- *e = append(*e, Error{
- FieldNames: fieldNames,
- Classification: classification,
- Message: message,
- })
-}
-
-// Len returns the number of errors.
-func (e *Errors) Len() int {
- return len(*e)
-}
-
-// Has determines whether an Errors slice has an Error with
-// a given classification in it; it does not search on messages
-// or field names.
-func (e *Errors) Has(class string) bool {
- for _, err := range *e {
- if err.Kind() == class {
- return true
- }
- }
- return false
-}
-
-/*
-// WithClass gets a copy of errors that are classified by the
-// the given classification.
-func (e *Errors) WithClass(classification string) Errors {
- var errs Errors
- for _, err := range *e {
- if err.Kind() == classification {
- errs = append(errs, err)
- }
- }
- return errs
-}
-
-// ForField gets a copy of errors that are associated with the
-// field by the given name.
-func (e *Errors) ForField(name string) Errors {
- var errs Errors
- for _, err := range *e {
- for _, fieldName := range err.Fields() {
- if fieldName == name {
- errs = append(errs, err)
- break
- }
- }
- }
- return errs
-}
-
-// Get gets errors of a particular class for the specified
-// field name.
-func (e *Errors) Get(class, fieldName string) Errors {
- var errs Errors
- for _, err := range *e {
- if err.Kind() == class {
- for _, nameOfField := range err.Fields() {
- if nameOfField == fieldName {
- errs = append(errs, err)
- break
- }
- }
- }
- }
- return errs
-}
-*/
-
-// Fields returns the list of field names this error is
-// associated with.
-func (e Error) Fields() []string {
- return e.FieldNames
-}
-
-// Kind returns this error's classification.
-func (e Error) Kind() string {
- return e.Classification
-}
-
-// Error returns this error's message.
-func (e Error) Error() string {
- return e.Message
-}
+++ /dev/null
-module gitea.com/macaron/binding
-
-go 1.11
-
-require (
- gitea.com/macaron/macaron v1.3.3-0.20190821202302-9646c0587edb
- github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337
- github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e
-)
+++ /dev/null
-gitea.com/macaron/inject v0.0.0-20190803172902-8375ba841591 h1:UbCTjPcLrNxR9LzKDjQBMT2zoxZuEnca1pZCpgeMuhQ=
-gitea.com/macaron/inject v0.0.0-20190803172902-8375ba841591/go.mod h1:h6E4kLao1Yko6DOU6QDnQPcuoNzvbZqzj2mtPcEn1aM=
-gitea.com/macaron/macaron v1.3.3-0.20190821202302-9646c0587edb h1:amL0md6orTj1tXY16ANzVU9FmzQB+W7aJwp8pVDbrmA=
-gitea.com/macaron/macaron v1.3.3-0.20190821202302-9646c0587edb/go.mod h1:0coI+mSPSwbsyAbOuFllVS38awuk9mevhLD52l50Gjs=
-github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
-github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
-github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
-github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
-github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
-github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY=
-github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
-github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
-github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8=
-github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
-github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e h1:GSGeB9EAKY2spCABz6xOX5DbxZEXolK+nBSvmsQwRjM=
-github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
-golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-gopkg.in/ini.v1 v1.44.0 h1:YRJzTUp0kSYWUVFF5XAbDFfyiqwsl0Vb9R8TVP5eRi0=
-gopkg.in/ini.v1 v1.44.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+++ /dev/null
-kind: pipeline
-name: go1-1-1
-
-steps:
-- name: test
- image: golang:1.11
- environment:
- GOPROXY: https://goproxy.cn
- commands:
- - go build -v
- - go test -v -race -coverprofile=coverage.txt -covermode=atomic
-
----
-kind: pipeline
-name: go1-1-2
-
-steps:
-- name: test
- image: golang:1.12
- environment:
- GOPROXY: https://goproxy.cn
- commands:
- - go build -v
- - go test -v -race -coverprofile=coverage.txt -covermode=atomic
\ No newline at end of file
+++ /dev/null
-nodb/cache
-ledis/tmp.db/
-nodb/tmp.db/
-/vendor
-/.idea
+++ /dev/null
-Apache License
-Version 2.0, January 2004
-http://www.apache.org/licenses/
-
-TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-1. Definitions.
-
-"License" shall mean the terms and conditions for use, reproduction, and
-distribution as defined by Sections 1 through 9 of this document.
-
-"Licensor" shall mean the copyright owner or entity authorized by the copyright
-owner that is granting the License.
-
-"Legal Entity" shall mean the union of the acting entity and all other entities
-that control, are controlled by, or are under common control with that entity.
-For the purposes of this definition, "control" means (i) the power, direct or
-indirect, to cause the direction or management of such entity, whether by
-contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
-outstanding shares, or (iii) beneficial ownership of such entity.
-
-"You" (or "Your") shall mean an individual or Legal Entity exercising
-permissions granted by this License.
-
-"Source" form shall mean the preferred form for making modifications, including
-but not limited to software source code, documentation source, and configuration
-files.
-
-"Object" form shall mean any form resulting from mechanical transformation or
-translation of a Source form, including but not limited to compiled object code,
-generated documentation, and conversions to other media types.
-
-"Work" shall mean the work of authorship, whether in Source or Object form, made
-available under the License, as indicated by a copyright notice that is included
-in or attached to the work (an example is provided in the Appendix below).
-
-"Derivative Works" shall mean any work, whether in Source or Object form, that
-is based on (or derived from) the Work and for which the editorial revisions,
-annotations, elaborations, or other modifications represent, as a whole, an
-original work of authorship. For the purposes of this License, Derivative Works
-shall not include works that remain separable from, or merely link (or bind by
-name) to the interfaces of, the Work and Derivative Works thereof.
-
-"Contribution" shall mean any work of authorship, including the original version
-of the Work and any modifications or additions to that Work or Derivative Works
-thereof, that is intentionally submitted to Licensor for inclusion in the Work
-by the copyright owner or by an individual or Legal Entity authorized to submit
-on behalf of the copyright owner. For the purposes of this definition,
-"submitted" means any form of electronic, verbal, or written communication sent
-to the Licensor or its representatives, including but not limited to
-communication on electronic mailing lists, source code control systems, and
-issue tracking systems that are managed by, or on behalf of, the Licensor for
-the purpose of discussing and improving the Work, but excluding communication
-that is conspicuously marked or otherwise designated in writing by the copyright
-owner as "Not a Contribution."
-
-"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
-of whom a Contribution has been received by Licensor and subsequently
-incorporated within the Work.
-
-2. Grant of Copyright License.
-
-Subject to the terms and conditions of this License, each Contributor hereby
-grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
-irrevocable copyright license to reproduce, prepare Derivative Works of,
-publicly display, publicly perform, sublicense, and distribute the Work and such
-Derivative Works in Source or Object form.
-
-3. Grant of Patent License.
-
-Subject to the terms and conditions of this License, each Contributor hereby
-grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
-irrevocable (except as stated in this section) patent license to make, have
-made, use, offer to sell, sell, import, and otherwise transfer the Work, where
-such license applies only to those patent claims licensable by such Contributor
-that are necessarily infringed by their Contribution(s) alone or by combination
-of their Contribution(s) with the Work to which such Contribution(s) was
-submitted. If You institute patent litigation against any entity (including a
-cross-claim or counterclaim in a lawsuit) alleging that the Work or a
-Contribution incorporated within the Work constitutes direct or contributory
-patent infringement, then any patent licenses granted to You under this License
-for that Work shall terminate as of the date such litigation is filed.
-
-4. Redistribution.
-
-You may reproduce and distribute copies of the Work or Derivative Works thereof
-in any medium, with or without modifications, and in Source or Object form,
-provided that You meet the following conditions:
-
-You must give any other recipients of the Work or Derivative Works a copy of
-this License; and
-You must cause any modified files to carry prominent notices stating that You
-changed the files; and
-You must retain, in the Source form of any Derivative Works that You distribute,
-all copyright, patent, trademark, and attribution notices from the Source form
-of the Work, excluding those notices that do not pertain to any part of the
-Derivative Works; and
-If the Work includes a "NOTICE" text file as part of its distribution, then any
-Derivative Works that You distribute must include a readable copy of the
-attribution notices contained within such NOTICE file, excluding those notices
-that do not pertain to any part of the Derivative Works, in at least one of the
-following places: within a NOTICE text file distributed as part of the
-Derivative Works; within the Source form or documentation, if provided along
-with the Derivative Works; or, within a display generated by the Derivative
-Works, if and wherever such third-party notices normally appear. The contents of
-the NOTICE file are for informational purposes only and do not modify the
-License. You may add Your own attribution notices within Derivative Works that
-You distribute, alongside or as an addendum to the NOTICE text from the Work,
-provided that such additional attribution notices cannot be construed as
-modifying the License.
-You may add Your own copyright statement to Your modifications and may provide
-additional or different license terms and conditions for use, reproduction, or
-distribution of Your modifications, or for any such Derivative Works as a whole,
-provided Your use, reproduction, and distribution of the Work otherwise complies
-with the conditions stated in this License.
-
-5. Submission of Contributions.
-
-Unless You explicitly state otherwise, any Contribution intentionally submitted
-for inclusion in the Work by You to the Licensor shall be under the terms and
-conditions of this License, without any additional terms or conditions.
-Notwithstanding the above, nothing herein shall supersede or modify the terms of
-any separate license agreement you may have executed with Licensor regarding
-such Contributions.
-
-6. Trademarks.
-
-This License does not grant permission to use the trade names, trademarks,
-service marks, or product names of the Licensor, except as required for
-reasonable and customary use in describing the origin of the Work and
-reproducing the content of the NOTICE file.
-
-7. Disclaimer of Warranty.
-
-Unless required by applicable law or agreed to in writing, Licensor provides the
-Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
-including, without limitation, any warranties or conditions of TITLE,
-NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
-solely responsible for determining the appropriateness of using or
-redistributing the Work and assume any risks associated with Your exercise of
-permissions under this License.
-
-8. Limitation of Liability.
-
-In no event and under no legal theory, whether in tort (including negligence),
-contract, or otherwise, unless required by applicable law (such as deliberate
-and grossly negligent acts) or agreed to in writing, shall any Contributor be
-liable to You for damages, including any direct, indirect, special, incidental,
-or consequential damages of any character arising as a result of this License or
-out of the use or inability to use the Work (including but not limited to
-damages for loss of goodwill, work stoppage, computer failure or malfunction, or
-any and all other commercial damages or losses), even if such Contributor has
-been advised of the possibility of such damages.
-
-9. Accepting Warranty or Additional Liability.
-
-While redistributing the Work or Derivative Works thereof, You may choose to
-offer, and charge a fee for, acceptance of support, warranty, indemnity, or
-other liability obligations and/or rights consistent with this License. However,
-in accepting such obligations, You may act only on Your own behalf and on Your
-sole responsibility, not on behalf of any other Contributor, and only if You
-agree to indemnify, defend, and hold each Contributor harmless for any liability
-incurred by, or claims asserted against, such Contributor by reason of your
-accepting any such warranty or additional liability.
-
-END OF TERMS AND CONDITIONS
-
-APPENDIX: How to apply the Apache License to your work
-
-To apply the Apache License to your work, attach the following boilerplate
-notice, with the fields enclosed by brackets "[]" replaced with your own
-identifying information. (Don't include the brackets!) The text should be
-enclosed in the appropriate comment syntax for the file format. We also
-recommend that a file or class name and description of purpose be included on
-the same "printed page" as the copyright notice for easier identification within
-third-party archives.
-
- Copyright [yyyy] [name of copyright owner]
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
\ No newline at end of file
+++ /dev/null
-# cache
-
-Middleware cache provides cache management for [Macaron](https://github.com/go-macaron/macaron). It can use many cache adapters, including memory, file, Redis, Memcache, PostgreSQL, MySQL, Ledis and Nodb.
-
-### Installation
-
- go get gitea.com/macaron/cache
-
-## Getting Help
-
-- [API Reference](https://gowalker.org/gitea.com/macaron/cache)
-- [Documentation](http://go-macaron.com/docs/middlewares/cache)
-
-## Credits
-
-This package is a modified version of [go-macaron/cache](github.com/go-macaron/cache).
-
-## License
-
-This project is under the Apache License, Version 2.0. See the [LICENSE](LICENSE) file for the full license text.
\ No newline at end of file
+++ /dev/null
-// Copyright 2013 Beego Authors
-// Copyright 2014 The Macaron Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-// Package cache is a middleware that provides the cache management of Macaron.
-package cache
-
-import (
- "fmt"
-
- "gitea.com/macaron/macaron"
-)
-
-const _VERSION = "0.3.0"
-
-func Version() string {
- return _VERSION
-}
-
-// Cache is the interface that operates the cache data.
-type Cache interface {
- // Put puts value into cache with key and expire time.
- Put(key string, val interface{}, timeout int64) error
- // Get gets cached value by given key.
- Get(key string) interface{}
- // Delete deletes cached value by given key.
- Delete(key string) error
- // Incr increases cached int-type value by given key as a counter.
- Incr(key string) error
- // Decr decreases cached int-type value by given key as a counter.
- Decr(key string) error
- // IsExist returns true if cached value exists.
- IsExist(key string) bool
- // Flush deletes all cached data.
- Flush() error
- // StartAndGC starts GC routine based on config string settings.
- StartAndGC(opt Options) error
-}
-
-// Options represents a struct for specifying configuration options for the cache middleware.
-type Options struct {
- // Name of adapter. Default is "memory".
- Adapter string
- // Adapter configuration, it's corresponding to adapter.
- AdapterConfig string
- // GC interval time in seconds. Default is 60.
- Interval int
- // Occupy entire database. Default is false.
- OccupyMode bool
- // Configuration section name. Default is "cache".
- Section string
-}
-
-func prepareOptions(options []Options) Options {
- var opt Options
- if len(options) > 0 {
- opt = options[0]
- }
- if len(opt.Section) == 0 {
- opt.Section = "cache"
- }
- sec := macaron.Config().Section(opt.Section)
-
- if len(opt.Adapter) == 0 {
- opt.Adapter = sec.Key("ADAPTER").MustString("memory")
- }
- if opt.Interval == 0 {
- opt.Interval = sec.Key("INTERVAL").MustInt(60)
- }
- if len(opt.AdapterConfig) == 0 {
- opt.AdapterConfig = sec.Key("ADAPTER_CONFIG").MustString("data/caches")
- }
-
- return opt
-}
-
-// NewCacher creates and returns a new cacher by given adapter name and configuration.
-// It panics when given adapter isn't registered and starts GC automatically.
-func NewCacher(name string, opt Options) (Cache, error) {
- adapter, ok := adapters[name]
- if !ok {
- return nil, fmt.Errorf("cache: unknown adapter '%s'(forgot to import?)", name)
- }
- return adapter, adapter.StartAndGC(opt)
-}
-
-// Cacher is a middleware that maps a cache.Cache service into the Macaron handler chain.
-// An single variadic cache.Options struct can be optionally provided to configure.
-func Cacher(options ...Options) macaron.Handler {
- opt := prepareOptions(options)
- cache, err := NewCacher(opt.Adapter, opt)
- if err != nil {
- panic(err)
- }
- return func(ctx *macaron.Context) {
- ctx.Map(cache)
- }
-}
-
-var adapters = make(map[string]Cache)
-
-// Register registers a adapter.
-func Register(name string, adapter Cache) {
- if adapter == nil {
- panic("cache: cannot register adapter with nil value")
- }
- if _, dup := adapters[name]; dup {
- panic(fmt.Errorf("cache: cannot register adapter '%s' twice", name))
- }
- adapters[name] = adapter
-}
+++ /dev/null
-// Copyright 2013 Beego Authors
-// Copyright 2014 The Macaron Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package cache
-
-import (
- "crypto/md5"
- "encoding/hex"
- "fmt"
- "io/ioutil"
- "log"
- "os"
- "path/filepath"
- "sync"
- "time"
-
- "gitea.com/macaron/macaron"
- "github.com/unknwon/com"
-)
-
-// Item represents a cache item.
-type Item struct {
- Val interface{}
- Created int64
- Expire int64
-}
-
-func (item *Item) hasExpired() bool {
- return item.Expire > 0 &&
- (time.Now().Unix()-item.Created) >= item.Expire
-}
-
-// FileCacher represents a file cache adapter implementation.
-type FileCacher struct {
- lock sync.Mutex
- rootPath string
- interval int // GC interval.
-}
-
-// NewFileCacher creates and returns a new file cacher.
-func NewFileCacher() *FileCacher {
- return &FileCacher{}
-}
-
-func (c *FileCacher) filepath(key string) string {
- m := md5.Sum([]byte(key))
- hash := hex.EncodeToString(m[:])
- return filepath.Join(c.rootPath, string(hash[0]), string(hash[1]), hash)
-}
-
-// Put puts value into cache with key and expire time.
-// If expired is 0, it will be deleted by next GC operation.
-func (c *FileCacher) Put(key string, val interface{}, expire int64) error {
- filename := c.filepath(key)
- item := &Item{val, time.Now().Unix(), expire}
- data, err := EncodeGob(item)
- if err != nil {
- return err
- }
-
- os.MkdirAll(filepath.Dir(filename), os.ModePerm)
- return ioutil.WriteFile(filename, data, os.ModePerm)
-}
-
-func (c *FileCacher) read(key string) (*Item, error) {
- filename := c.filepath(key)
-
- data, err := ioutil.ReadFile(filename)
- if err != nil {
- return nil, err
- }
-
- item := new(Item)
- return item, DecodeGob(data, item)
-}
-
-// Get gets cached value by given key.
-func (c *FileCacher) Get(key string) interface{} {
- item, err := c.read(key)
- if err != nil {
- return nil
- }
-
- if item.hasExpired() {
- os.Remove(c.filepath(key))
- return nil
- }
- return item.Val
-}
-
-// Delete deletes cached value by given key.
-func (c *FileCacher) Delete(key string) error {
- return os.Remove(c.filepath(key))
-}
-
-// Incr increases cached int-type value by given key as a counter.
-func (c *FileCacher) Incr(key string) error {
- item, err := c.read(key)
- if err != nil {
- return err
- }
-
- item.Val, err = Incr(item.Val)
- if err != nil {
- return err
- }
-
- return c.Put(key, item.Val, item.Expire)
-}
-
-// Decrease cached int value.
-func (c *FileCacher) Decr(key string) error {
- item, err := c.read(key)
- if err != nil {
- return err
- }
-
- item.Val, err = Decr(item.Val)
- if err != nil {
- return err
- }
-
- return c.Put(key, item.Val, item.Expire)
-}
-
-// IsExist returns true if cached value exists.
-func (c *FileCacher) IsExist(key string) bool {
- return com.IsExist(c.filepath(key))
-}
-
-// Flush deletes all cached data.
-func (c *FileCacher) Flush() error {
- return os.RemoveAll(c.rootPath)
-}
-
-func (c *FileCacher) startGC() {
- c.lock.Lock()
- defer c.lock.Unlock()
-
- if c.interval < 1 {
- return
- }
-
- if err := filepath.Walk(c.rootPath, func(path string, fi os.FileInfo, err error) error {
- if err != nil {
- return fmt.Errorf("Walk: %v", err)
- }
-
- if fi.IsDir() {
- return nil
- }
-
- data, err := ioutil.ReadFile(path)
- if err != nil && !os.IsNotExist(err) {
- fmt.Errorf("ReadFile: %v", err)
- }
-
- item := new(Item)
- if err = DecodeGob(data, item); err != nil {
- return err
- }
- if item.hasExpired() {
- if err = os.Remove(path); err != nil && !os.IsNotExist(err) {
- return fmt.Errorf("Remove: %v", err)
- }
- }
- return nil
- }); err != nil {
- log.Printf("error garbage collecting cache files: %v", err)
- }
-
- time.AfterFunc(time.Duration(c.interval)*time.Second, func() { c.startGC() })
-}
-
-// StartAndGC starts GC routine based on config string settings.
-func (c *FileCacher) StartAndGC(opt Options) error {
- c.lock.Lock()
- c.rootPath = opt.AdapterConfig
- c.interval = opt.Interval
-
- if !filepath.IsAbs(c.rootPath) {
- c.rootPath = filepath.Join(macaron.Root, c.rootPath)
- }
- c.lock.Unlock()
-
- if err := os.MkdirAll(c.rootPath, os.ModePerm); err != nil {
- return err
- }
-
- go c.startGC()
- return nil
-}
-
-func init() {
- Register("file", NewFileCacher())
-}
+++ /dev/null
-module gitea.com/macaron/cache
-
-go 1.11
-
-require (
- gitea.com/macaron/macaron v1.3.3-0.20190821202302-9646c0587edb
- github.com/BurntSushi/toml v0.3.1 // indirect
- github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668
- github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76 // indirect
- github.com/edsrzf/mmap-go v1.0.0 // indirect
- github.com/go-redis/redis v6.15.2+incompatible
- github.com/go-sql-driver/mysql v1.4.1
- github.com/golang/snappy v0.0.2 // indirect
- github.com/lib/pq v1.2.0
- github.com/lunny/log v0.0.0-20160921050905-7887c61bf0de // indirect
- github.com/lunny/nodb v0.0.0-20160621015157-fc1ef06ad4af
- github.com/mattn/go-sqlite3 v1.11.0 // indirect
- github.com/onsi/ginkgo v1.8.0 // indirect
- github.com/onsi/gomega v1.5.0 // indirect
- github.com/pelletier/go-toml v1.8.1 // indirect
- github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726 // indirect
- github.com/siddontang/go-snappy v0.0.0-20140704025258-d8f7bb82a96d // indirect
- github.com/siddontang/ledisdb v0.0.0-20190202134119-8ceb77e66a92
- github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d // indirect
- github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337
- github.com/syndtr/goleveldb v1.0.0 // indirect
- github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e
- golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 // indirect
- golang.org/x/sys v0.0.0-20190730183949-1393eb018365 // indirect
- google.golang.org/appengine v1.6.1 // indirect
- gopkg.in/ini.v1 v1.44.2
- gopkg.in/yaml.v2 v2.2.2 // indirect
-)
+++ /dev/null
-gitea.com/macaron/inject v0.0.0-20190803172902-8375ba841591 h1:UbCTjPcLrNxR9LzKDjQBMT2zoxZuEnca1pZCpgeMuhQ=
-gitea.com/macaron/inject v0.0.0-20190803172902-8375ba841591/go.mod h1:h6E4kLao1Yko6DOU6QDnQPcuoNzvbZqzj2mtPcEn1aM=
-gitea.com/macaron/macaron v1.3.3-0.20190821202302-9646c0587edb h1:amL0md6orTj1tXY16ANzVU9FmzQB+W7aJwp8pVDbrmA=
-gitea.com/macaron/macaron v1.3.3-0.20190821202302-9646c0587edb/go.mod h1:0coI+mSPSwbsyAbOuFllVS38awuk9mevhLD52l50Gjs=
-github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
-github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668 h1:U/lr3Dgy4WK+hNk4tyD+nuGjpVLPEHuJSFXMw11/HPA=
-github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
-github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76 h1:Lgdd/Qp96Qj8jqLpq2cI1I1X7BJnu06efS+XkhRoLUQ=
-github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
-github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
-github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw=
-github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
-github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
-github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
-github.com/go-redis/redis v6.15.2+incompatible h1:9SpNVG76gr6InJGxoZ6IuuxaCOQwDAhzyXg+Bs+0Sb4=
-github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
-github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
-github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
-github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
-github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
-github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
-github.com/golang/snappy v0.0.2 h1:aeE13tS0IiQgFjYdoL8qN3K1N2bXXtI6Vi51/y7BpMw=
-github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
-github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
-github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
-github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
-github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
-github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
-github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
-github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
-github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
-github.com/lunny/log v0.0.0-20160921050905-7887c61bf0de h1:nyxwRdWHAVxpFcDThedEgQ07DbcRc5xgNObtbTp76fk=
-github.com/lunny/log v0.0.0-20160921050905-7887c61bf0de/go.mod h1:3q8WtuPQsoRbatJuy3nvq/hRSvuBJrHHr+ybPPiNvHQ=
-github.com/lunny/nodb v0.0.0-20160621015157-fc1ef06ad4af h1:UaWHNBdukWrSG3DRvHFR/hyfg681fceqQDYVTBncKfQ=
-github.com/lunny/nodb v0.0.0-20160621015157-fc1ef06ad4af/go.mod h1:Cqz6pqow14VObJ7peltM+2n3PWOz7yTrfUuGbVFkzN0=
-github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q=
-github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
-github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w=
-github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
-github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo=
-github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
-github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM=
-github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
-github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726 h1:xT+JlYxNGqyT+XcU8iUrN18JYed2TvG9yN5ULG2jATM=
-github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
-github.com/siddontang/go-snappy v0.0.0-20140704025258-d8f7bb82a96d h1:qQWKKOvHN7Q9c6GdmUteCef2F9ubxMpxY1IKwpIKz68=
-github.com/siddontang/go-snappy v0.0.0-20140704025258-d8f7bb82a96d/go.mod h1:vq0tzqLRu6TS7Id0wMo2N5QzJoKedVeovOpHjnykSzY=
-github.com/siddontang/ledisdb v0.0.0-20190202134119-8ceb77e66a92 h1:qvsJwGToa8rxb42cDRhkbKeX2H5N8BH+s2aUikGt8mI=
-github.com/siddontang/ledisdb v0.0.0-20190202134119-8ceb77e66a92/go.mod h1:mF1DpOSOUiJRMR+FDqaqu3EBqrybQtrDDszLUZ6oxPg=
-github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d h1:NVwnfyR3rENtlz62bcrkXME3INVUa4lcdGt+opvxExs=
-github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=
-github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
-github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY=
-github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
-github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
-github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8=
-github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
-github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
-github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
-github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e h1:GSGeB9EAKY2spCABz6xOX5DbxZEXolK+nBSvmsQwRjM=
-github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
-golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
-golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk=
-golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190730183949-1393eb018365 h1:SaXEMXhWzMJThc05vu6uh61Q245r4KaWMrsTedk0FDc=
-golang.org/x/sys v0.0.0-20190730183949-1393eb018365/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
-golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I=
-google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
-gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
-gopkg.in/ini.v1 v1.44.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/ini.v1 v1.44.2 h1:N6kNUPqiIyxP+s/aINPzRvNpcTVV30qLC0t6ZjZFlUU=
-gopkg.in/ini.v1 v1.44.2/go.mod h1:M3Cogqpuv0QCi3ExAY5V4uOt4qb/R3xZubo9m8lK5wg=
-gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
-gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
-gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
-gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+++ /dev/null
-// Copyright 2013 Beego Authors
-// Copyright 2014 The Macaron Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package cache
-
-import (
- "strings"
-
- "github.com/bradfitz/gomemcache/memcache"
- "github.com/unknwon/com"
-
- "gitea.com/macaron/cache"
-)
-
-// MemcacheCacher represents a memcache cache adapter implementation.
-type MemcacheCacher struct {
- c *memcache.Client
-}
-
-func NewItem(key string, data []byte, expire int32) *memcache.Item {
- return &memcache.Item{
- Key: key,
- Value: data,
- Expiration: expire,
- }
-}
-
-// Put puts value into cache with key and expire time.
-// If expired is 0, it lives forever.
-func (c *MemcacheCacher) Put(key string, val interface{}, expire int64) error {
- return c.c.Set(NewItem(key, []byte(com.ToStr(val)), int32(expire)))
-}
-
-// Get gets cached value by given key.
-func (c *MemcacheCacher) Get(key string) interface{} {
- item, err := c.c.Get(key)
- if err != nil {
- return nil
- }
- return string(item.Value)
-}
-
-// Delete deletes cached value by given key.
-func (c *MemcacheCacher) Delete(key string) error {
- return c.c.Delete(key)
-}
-
-// Incr increases cached int-type value by given key as a counter.
-func (c *MemcacheCacher) Incr(key string) error {
- _, err := c.c.Increment(key, 1)
- return err
-}
-
-// Decr decreases cached int-type value by given key as a counter.
-func (c *MemcacheCacher) Decr(key string) error {
- _, err := c.c.Decrement(key, 1)
- return err
-}
-
-// IsExist returns true if cached value exists.
-func (c *MemcacheCacher) IsExist(key string) bool {
- _, err := c.c.Get(key)
- return err == nil
-}
-
-// Flush deletes all cached data.
-func (c *MemcacheCacher) Flush() error {
- return c.c.FlushAll()
-}
-
-// StartAndGC starts GC routine based on config string settings.
-// AdapterConfig: 127.0.0.1:9090;127.0.0.1:9091
-func (c *MemcacheCacher) StartAndGC(opt cache.Options) error {
- c.c = memcache.New(strings.Split(opt.AdapterConfig, ";")...)
- return nil
-}
-
-func init() {
- cache.Register("memcache", &MemcacheCacher{})
-}
+++ /dev/null
-ignore
\ No newline at end of file
+++ /dev/null
-// Copyright 2013 Beego Authors
-// Copyright 2014 The Macaron Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package cache
-
-import (
- "errors"
- "sync"
- "time"
-)
-
-// MemoryItem represents a memory cache item.
-type MemoryItem struct {
- val interface{}
- created int64
- expire int64
-}
-
-func (item *MemoryItem) hasExpired() bool {
- return item.expire > 0 &&
- (time.Now().Unix()-item.created) >= item.expire
-}
-
-// MemoryCacher represents a memory cache adapter implementation.
-type MemoryCacher struct {
- lock sync.RWMutex
- items map[string]*MemoryItem
- interval int // GC interval.
-}
-
-// NewMemoryCacher creates and returns a new memory cacher.
-func NewMemoryCacher() *MemoryCacher {
- return &MemoryCacher{items: make(map[string]*MemoryItem)}
-}
-
-// Put puts value into cache with key and expire time.
-// If expired is 0, it will be deleted by next GC operation.
-func (c *MemoryCacher) Put(key string, val interface{}, expire int64) error {
- c.lock.Lock()
- defer c.lock.Unlock()
-
- c.items[key] = &MemoryItem{
- val: val,
- created: time.Now().Unix(),
- expire: expire,
- }
- return nil
-}
-
-// Get gets cached value by given key.
-func (c *MemoryCacher) Get(key string) interface{} {
- c.lock.RLock()
- defer c.lock.RUnlock()
-
- item, ok := c.items[key]
- if !ok {
- return nil
- }
- if item.hasExpired() {
- go c.Delete(key)
- return nil
- }
- return item.val
-}
-
-// Delete deletes cached value by given key.
-func (c *MemoryCacher) Delete(key string) error {
- c.lock.Lock()
- defer c.lock.Unlock()
-
- delete(c.items, key)
- return nil
-}
-
-// Incr increases cached int-type value by given key as a counter.
-func (c *MemoryCacher) Incr(key string) (err error) {
- c.lock.RLock()
- defer c.lock.RUnlock()
-
- item, ok := c.items[key]
- if !ok {
- return errors.New("key not exist")
- }
- item.val, err = Incr(item.val)
- return err
-}
-
-// Decr decreases cached int-type value by given key as a counter.
-func (c *MemoryCacher) Decr(key string) (err error) {
- c.lock.RLock()
- defer c.lock.RUnlock()
-
- item, ok := c.items[key]
- if !ok {
- return errors.New("key not exist")
- }
-
- item.val, err = Decr(item.val)
- return err
-}
-
-// IsExist returns true if cached value exists.
-func (c *MemoryCacher) IsExist(key string) bool {
- c.lock.RLock()
- defer c.lock.RUnlock()
-
- _, ok := c.items[key]
- return ok
-}
-
-// Flush deletes all cached data.
-func (c *MemoryCacher) Flush() error {
- c.lock.Lock()
- defer c.lock.Unlock()
-
- c.items = make(map[string]*MemoryItem)
- return nil
-}
-
-func (c *MemoryCacher) checkRawExpiration(key string) {
- item, ok := c.items[key]
- if !ok {
- return
- }
-
- if item.hasExpired() {
- delete(c.items, key)
- }
-}
-
-func (c *MemoryCacher) checkExpiration(key string) {
- c.lock.Lock()
- defer c.lock.Unlock()
-
- c.checkRawExpiration(key)
-}
-
-func (c *MemoryCacher) startGC() {
- c.lock.Lock()
- defer c.lock.Unlock()
-
- if c.interval < 1 {
- return
- }
-
- if c.items != nil {
- for key, _ := range c.items {
- c.checkRawExpiration(key)
- }
- }
-
- time.AfterFunc(time.Duration(c.interval)*time.Second, func() { c.startGC() })
-}
-
-// StartAndGC starts GC routine based on config string settings.
-func (c *MemoryCacher) StartAndGC(opt Options) error {
- c.lock.Lock()
- c.interval = opt.Interval
- c.lock.Unlock()
-
- go c.startGC()
- return nil
-}
-
-func init() {
- Register("memory", NewMemoryCacher())
-}
+++ /dev/null
-// Copyright 2014 The Macaron Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package cache
-
-import (
- "bytes"
- "encoding/gob"
- "errors"
-)
-
-func EncodeGob(item *Item) ([]byte, error) {
- buf := bytes.NewBuffer(nil)
- err := gob.NewEncoder(buf).Encode(item)
- return buf.Bytes(), err
-}
-
-func DecodeGob(data []byte, out *Item) error {
- buf := bytes.NewBuffer(data)
- return gob.NewDecoder(buf).Decode(&out)
-}
-
-func Incr(val interface{}) (interface{}, error) {
- switch val.(type) {
- case int:
- val = val.(int) + 1
- case int32:
- val = val.(int32) + 1
- case int64:
- val = val.(int64) + 1
- case uint:
- val = val.(uint) + 1
- case uint32:
- val = val.(uint32) + 1
- case uint64:
- val = val.(uint64) + 1
- default:
- return val, errors.New("item value is not int-type")
- }
- return val, nil
-}
-
-func Decr(val interface{}) (interface{}, error) {
- switch val.(type) {
- case int:
- val = val.(int) - 1
- case int32:
- val = val.(int32) - 1
- case int64:
- val = val.(int64) - 1
- case uint:
- if val.(uint) > 0 {
- val = val.(uint) - 1
- } else {
- return val, errors.New("item value is less than 0")
- }
- case uint32:
- if val.(uint32) > 0 {
- val = val.(uint32) - 1
- } else {
- return val, errors.New("item value is less than 0")
- }
- case uint64:
- if val.(uint64) > 0 {
- val = val.(uint64) - 1
- } else {
- return val, errors.New("item value is less than 0")
- }
- default:
- return val, errors.New("item value is not int-type")
- }
- return val, nil
-}
+++ /dev/null
-kind: pipeline
-name: go1-1-1
-
-steps:
-- name: test
- image: golang:1.11
- environment:
- GOPROXY: https://goproxy.cn
- commands:
- - go build -v
- - go test -v -race -coverprofile=coverage.txt -covermode=atomic
-
----
-kind: pipeline
-name: go1-1-2
-
-steps:
-- name: test
- image: golang:1.12
- environment:
- GOPROXY: https://goproxy.cn
- commands:
- - go build -v
- - go test -v -race -coverprofile=coverage.txt -covermode=atomic
\ No newline at end of file
+++ /dev/null
-Apache License
-Version 2.0, January 2004
-http://www.apache.org/licenses/
-
-TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-1. Definitions.
-
-"License" shall mean the terms and conditions for use, reproduction, and
-distribution as defined by Sections 1 through 9 of this document.
-
-"Licensor" shall mean the copyright owner or entity authorized by the copyright
-owner that is granting the License.
-
-"Legal Entity" shall mean the union of the acting entity and all other entities
-that control, are controlled by, or are under common control with that entity.
-For the purposes of this definition, "control" means (i) the power, direct or
-indirect, to cause the direction or management of such entity, whether by
-contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
-outstanding shares, or (iii) beneficial ownership of such entity.
-
-"You" (or "Your") shall mean an individual or Legal Entity exercising
-permissions granted by this License.
-
-"Source" form shall mean the preferred form for making modifications, including
-but not limited to software source code, documentation source, and configuration
-files.
-
-"Object" form shall mean any form resulting from mechanical transformation or
-translation of a Source form, including but not limited to compiled object code,
-generated documentation, and conversions to other media types.
-
-"Work" shall mean the work of authorship, whether in Source or Object form, made
-available under the License, as indicated by a copyright notice that is included
-in or attached to the work (an example is provided in the Appendix below).
-
-"Derivative Works" shall mean any work, whether in Source or Object form, that
-is based on (or derived from) the Work and for which the editorial revisions,
-annotations, elaborations, or other modifications represent, as a whole, an
-original work of authorship. For the purposes of this License, Derivative Works
-shall not include works that remain separable from, or merely link (or bind by
-name) to the interfaces of, the Work and Derivative Works thereof.
-
-"Contribution" shall mean any work of authorship, including the original version
-of the Work and any modifications or additions to that Work or Derivative Works
-thereof, that is intentionally submitted to Licensor for inclusion in the Work
-by the copyright owner or by an individual or Legal Entity authorized to submit
-on behalf of the copyright owner. For the purposes of this definition,
-"submitted" means any form of electronic, verbal, or written communication sent
-to the Licensor or its representatives, including but not limited to
-communication on electronic mailing lists, source code control systems, and
-issue tracking systems that are managed by, or on behalf of, the Licensor for
-the purpose of discussing and improving the Work, but excluding communication
-that is conspicuously marked or otherwise designated in writing by the copyright
-owner as "Not a Contribution."
-
-"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
-of whom a Contribution has been received by Licensor and subsequently
-incorporated within the Work.
-
-2. Grant of Copyright License.
-
-Subject to the terms and conditions of this License, each Contributor hereby
-grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
-irrevocable copyright license to reproduce, prepare Derivative Works of,
-publicly display, publicly perform, sublicense, and distribute the Work and such
-Derivative Works in Source or Object form.
-
-3. Grant of Patent License.
-
-Subject to the terms and conditions of this License, each Contributor hereby
-grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
-irrevocable (except as stated in this section) patent license to make, have
-made, use, offer to sell, sell, import, and otherwise transfer the Work, where
-such license applies only to those patent claims licensable by such Contributor
-that are necessarily infringed by their Contribution(s) alone or by combination
-of their Contribution(s) with the Work to which such Contribution(s) was
-submitted. If You institute patent litigation against any entity (including a
-cross-claim or counterclaim in a lawsuit) alleging that the Work or a
-Contribution incorporated within the Work constitutes direct or contributory
-patent infringement, then any patent licenses granted to You under this License
-for that Work shall terminate as of the date such litigation is filed.
-
-4. Redistribution.
-
-You may reproduce and distribute copies of the Work or Derivative Works thereof
-in any medium, with or without modifications, and in Source or Object form,
-provided that You meet the following conditions:
-
-You must give any other recipients of the Work or Derivative Works a copy of
-this License; and
-You must cause any modified files to carry prominent notices stating that You
-changed the files; and
-You must retain, in the Source form of any Derivative Works that You distribute,
-all copyright, patent, trademark, and attribution notices from the Source form
-of the Work, excluding those notices that do not pertain to any part of the
-Derivative Works; and
-If the Work includes a "NOTICE" text file as part of its distribution, then any
-Derivative Works that You distribute must include a readable copy of the
-attribution notices contained within such NOTICE file, excluding those notices
-that do not pertain to any part of the Derivative Works, in at least one of the
-following places: within a NOTICE text file distributed as part of the
-Derivative Works; within the Source form or documentation, if provided along
-with the Derivative Works; or, within a display generated by the Derivative
-Works, if and wherever such third-party notices normally appear. The contents of
-the NOTICE file are for informational purposes only and do not modify the
-License. You may add Your own attribution notices within Derivative Works that
-You distribute, alongside or as an addendum to the NOTICE text from the Work,
-provided that such additional attribution notices cannot be construed as
-modifying the License.
-You may add Your own copyright statement to Your modifications and may provide
-additional or different license terms and conditions for use, reproduction, or
-distribution of Your modifications, or for any such Derivative Works as a whole,
-provided Your use, reproduction, and distribution of the Work otherwise complies
-with the conditions stated in this License.
-
-5. Submission of Contributions.
-
-Unless You explicitly state otherwise, any Contribution intentionally submitted
-for inclusion in the Work by You to the Licensor shall be under the terms and
-conditions of this License, without any additional terms or conditions.
-Notwithstanding the above, nothing herein shall supersede or modify the terms of
-any separate license agreement you may have executed with Licensor regarding
-such Contributions.
-
-6. Trademarks.
-
-This License does not grant permission to use the trade names, trademarks,
-service marks, or product names of the Licensor, except as required for
-reasonable and customary use in describing the origin of the Work and
-reproducing the content of the NOTICE file.
-
-7. Disclaimer of Warranty.
-
-Unless required by applicable law or agreed to in writing, Licensor provides the
-Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
-including, without limitation, any warranties or conditions of TITLE,
-NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
-solely responsible for determining the appropriateness of using or
-redistributing the Work and assume any risks associated with Your exercise of
-permissions under this License.
-
-8. Limitation of Liability.
-
-In no event and under no legal theory, whether in tort (including negligence),
-contract, or otherwise, unless required by applicable law (such as deliberate
-and grossly negligent acts) or agreed to in writing, shall any Contributor be
-liable to You for damages, including any direct, indirect, special, incidental,
-or consequential damages of any character arising as a result of this License or
-out of the use or inability to use the Work (including but not limited to
-damages for loss of goodwill, work stoppage, computer failure or malfunction, or
-any and all other commercial damages or losses), even if such Contributor has
-been advised of the possibility of such damages.
-
-9. Accepting Warranty or Additional Liability.
-
-While redistributing the Work or Derivative Works thereof, You may choose to
-offer, and charge a fee for, acceptance of support, warranty, indemnity, or
-other liability obligations and/or rights consistent with this License. However,
-in accepting such obligations, You may act only on Your own behalf and on Your
-sole responsibility, not on behalf of any other Contributor, and only if You
-agree to indemnify, defend, and hold each Contributor harmless for any liability
-incurred by, or claims asserted against, such Contributor by reason of your
-accepting any such warranty or additional liability.
-
-END OF TERMS AND CONDITIONS
-
-APPENDIX: How to apply the Apache License to your work
-
-To apply the Apache License to your work, attach the following boilerplate
-notice, with the fields enclosed by brackets "[]" replaced with your own
-identifying information. (Don't include the brackets!) The text should be
-enclosed in the appropriate comment syntax for the file format. We also
-recommend that a file or class name and description of purpose be included on
-the same "printed page" as the copyright notice for easier identification within
-third-party archives.
-
- Copyright [yyyy] [name of copyright owner]
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
\ No newline at end of file
+++ /dev/null
-# captcha [![Build Status](https://drone.gitea.com/api/badges/macaron/captcha/status.svg)](https://drone.gitea.com/macaron/captcha)
-
-Middleware captcha provides captcha service for [Macaron](https://gitea.com/macaron/macaron).
-
-### Installation
-
- go get gitea.com/macaron/captcha
-
-## Getting Help
-
-- [API Reference](https://gowalker.org/gitea.com/macaron/captcha)
-- [Documentation](http://go-macaron.com/docs/middlewares/captcha)
-
-## License
-
-This project is under the Apache License, Version 2.0. See the [LICENSE](LICENSE) file for the full license text.
\ No newline at end of file
+++ /dev/null
-// Copyright 2013 Beego Authors
-// Copyright 2014 The Macaron Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-// Package captcha a middleware that provides captcha service for Macaron.
-package captcha
-
-import (
- "fmt"
- "html/template"
- "image/color"
- "path"
- "strings"
-
- "gitea.com/macaron/cache"
- "gitea.com/macaron/macaron"
- "github.com/unknwon/com"
-)
-
-const _VERSION = "0.1.0"
-
-func Version() string {
- return _VERSION
-}
-
-var (
- defaultChars = []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
-)
-
-// Captcha represents a captcha service.
-type Captcha struct {
- store cache.Cache
- SubURL string
- URLPrefix string
- FieldIdName string
- FieldCaptchaName string
- StdWidth int
- StdHeight int
- ChallengeNums int
- Expiration int64
- CachePrefix string
- ColorPalette color.Palette
-}
-
-// generate key string
-func (c *Captcha) key(id string) string {
- return c.CachePrefix + id
-}
-
-// generate rand chars with default chars
-func (c *Captcha) genRandChars() string {
- return string(com.RandomCreateBytes(c.ChallengeNums, defaultChars...))
-}
-
-// CreateHTML outputs HTML for display and fetch new captcha images.
-func (c *Captcha) CreateHTML() template.HTML {
- value, err := c.CreateCaptcha()
- if err != nil {
- panic(fmt.Errorf("fail to create captcha: %v", err))
- }
- return template.HTML(fmt.Sprintf(`<input type="hidden" name="%[1]s" value="%[2]s">
- <a class="captcha" href="javascript:" tabindex="-1">
- <img onclick="this.src=('%[3]s%[4]s%[2]s.png?reload='+(new Date()).getTime())" class="captcha-img" src="%[3]s%[4]s%[2]s.png">
- </a>`, c.FieldIdName, value, c.SubURL, c.URLPrefix))
-}
-
-// DEPRECATED
-func (c *Captcha) CreateHtml() template.HTML {
- return c.CreateHTML()
-}
-
-// create a new captcha id
-func (c *Captcha) CreateCaptcha() (string, error) {
- id := string(com.RandomCreateBytes(15))
- if err := c.store.Put(c.key(id), c.genRandChars(), c.Expiration); err != nil {
- return "", err
- }
- return id, nil
-}
-
-// verify from a request
-func (c *Captcha) VerifyReq(req macaron.Request) bool {
- req.ParseForm()
- return c.Verify(req.Form.Get(c.FieldIdName), req.Form.Get(c.FieldCaptchaName))
-}
-
-// direct verify id and challenge string
-func (c *Captcha) Verify(id string, challenge string) bool {
- if len(challenge) == 0 || len(id) == 0 {
- return false
- }
-
- var chars string
-
- key := c.key(id)
-
- if v, ok := c.store.Get(key).(string); ok {
- chars = v
- } else {
- return false
- }
-
- defer c.store.Delete(key)
-
- if len(chars) != len(challenge) {
- return false
- }
-
- // verify challenge
- for i, c := range []byte(chars) {
- if c != challenge[i]-48 {
- return false
- }
- }
-
- return true
-}
-
-type Options struct {
- // Suburl path. Default is empty.
- SubURL string
- // URL prefix of getting captcha pictures. Default is "/captcha/".
- URLPrefix string
- // Hidden input element ID. Default is "captcha_id".
- FieldIdName string
- // User input value element name in request form. Default is "captcha".
- FieldCaptchaName string
- // Challenge number. Default is 6.
- ChallengeNums int
- // Captcha image width. Default is 240.
- Width int
- // Captcha image height. Default is 80.
- Height int
- // Captcha expiration time in seconds. Default is 600.
- Expiration int64
- // Cache key prefix captcha characters. Default is "captcha_".
- CachePrefix string
- // ColorPalette holds a collection of primary colors used for
- // the captcha's text. If not defined, a random color will be generated.
- ColorPalette color.Palette
-}
-
-func prepareOptions(options []Options) Options {
- var opt Options
- if len(options) > 0 {
- opt = options[0]
- }
-
- opt.SubURL = strings.TrimSuffix(opt.SubURL, "/")
-
- // Defaults.
- if len(opt.URLPrefix) == 0 {
- opt.URLPrefix = "/captcha/"
- } else if opt.URLPrefix[len(opt.URLPrefix)-1] != '/' {
- opt.URLPrefix += "/"
- }
- if len(opt.FieldIdName) == 0 {
- opt.FieldIdName = "captcha_id"
- }
- if len(opt.FieldCaptchaName) == 0 {
- opt.FieldCaptchaName = "captcha"
- }
- if opt.ChallengeNums == 0 {
- opt.ChallengeNums = 6
- }
- if opt.Width == 0 {
- opt.Width = stdWidth
- }
- if opt.Height == 0 {
- opt.Height = stdHeight
- }
- if opt.Expiration == 0 {
- opt.Expiration = 600
- }
- if len(opt.CachePrefix) == 0 {
- opt.CachePrefix = "captcha_"
- }
-
- return opt
-}
-
-// NewCaptcha initializes and returns a captcha with given options.
-func NewCaptcha(opt Options) *Captcha {
- return &Captcha{
- SubURL: opt.SubURL,
- URLPrefix: opt.URLPrefix,
- FieldIdName: opt.FieldIdName,
- FieldCaptchaName: opt.FieldCaptchaName,
- StdWidth: opt.Width,
- StdHeight: opt.Height,
- ChallengeNums: opt.ChallengeNums,
- Expiration: opt.Expiration,
- CachePrefix: opt.CachePrefix,
- ColorPalette: opt.ColorPalette,
- }
-}
-
-// Captchaer is a middleware that maps a captcha.Captcha service into the Macaron handler chain.
-// An single variadic captcha.Options struct can be optionally provided to configure.
-// This should be register after cache.Cacher.
-func Captchaer(options ...Options) macaron.Handler {
- return func(ctx *macaron.Context, cache cache.Cache) {
- cpt := NewCaptcha(prepareOptions(options))
- cpt.store = cache
-
- if strings.HasPrefix(ctx.Req.URL.Path, cpt.URLPrefix) {
- var chars string
- id := path.Base(ctx.Req.URL.Path)
- if i := strings.Index(id, "."); i > -1 {
- id = id[:i]
- }
- key := cpt.key(id)
-
- // Reload captcha.
- if len(ctx.Query("reload")) > 0 {
- chars = cpt.genRandChars()
- if err := cpt.store.Put(key, chars, cpt.Expiration); err != nil {
- ctx.Status(500)
- ctx.Write([]byte("captcha reload error"))
- panic(fmt.Errorf("reload captcha: %v", err))
- }
- } else {
- if v, ok := cpt.store.Get(key).(string); ok {
- chars = v
- } else {
- ctx.Status(404)
- ctx.Write([]byte("captcha not found"))
- return
- }
- }
-
- ctx.Status(200)
- if _, err := NewImage([]byte(chars), cpt.StdWidth, cpt.StdHeight, cpt.ColorPalette).WriteTo(ctx.Resp); err != nil {
- panic(fmt.Errorf("write captcha: %v", err))
- }
- return
- }
-
- ctx.Data["Captcha"] = cpt
- ctx.Map(cpt)
- }
-}
+++ /dev/null
-module gitea.com/macaron/captcha
-
-go 1.11
-
-require (
- gitea.com/macaron/cache v0.0.0-20190822004001-a6e7fee4ee76
- gitea.com/macaron/macaron v1.3.3-0.20190821202302-9646c0587edb
- github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337
- github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e
-)
+++ /dev/null
-gitea.com/macaron/cache v0.0.0-20190822004001-a6e7fee4ee76 h1:mMsMEg90c5KXQgRWsH8D6GHXfZIW1RAe5S9VYIb12lM=
-gitea.com/macaron/cache v0.0.0-20190822004001-a6e7fee4ee76/go.mod h1:NFHb9Of+LUnU86bU20CiXXg6ZlgCJ4XytP14UsHOXFs=
-gitea.com/macaron/inject v0.0.0-20190803172902-8375ba841591 h1:UbCTjPcLrNxR9LzKDjQBMT2zoxZuEnca1pZCpgeMuhQ=
-gitea.com/macaron/inject v0.0.0-20190803172902-8375ba841591/go.mod h1:h6E4kLao1Yko6DOU6QDnQPcuoNzvbZqzj2mtPcEn1aM=
-gitea.com/macaron/macaron v1.3.3-0.20190821202302-9646c0587edb h1:amL0md6orTj1tXY16ANzVU9FmzQB+W7aJwp8pVDbrmA=
-gitea.com/macaron/macaron v1.3.3-0.20190821202302-9646c0587edb/go.mod h1:0coI+mSPSwbsyAbOuFllVS38awuk9mevhLD52l50Gjs=
-github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
-github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
-github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
-github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
-github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
-github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
-github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
-github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
-github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
-github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
-github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
-github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
-github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
-github.com/lunny/log v0.0.0-20160921050905-7887c61bf0de/go.mod h1:3q8WtuPQsoRbatJuy3nvq/hRSvuBJrHHr+ybPPiNvHQ=
-github.com/lunny/nodb v0.0.0-20160621015157-fc1ef06ad4af/go.mod h1:Cqz6pqow14VObJ7peltM+2n3PWOz7yTrfUuGbVFkzN0=
-github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
-github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
-github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
-github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
-github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
-github.com/siddontang/go-snappy v0.0.0-20140704025258-d8f7bb82a96d/go.mod h1:vq0tzqLRu6TS7Id0wMo2N5QzJoKedVeovOpHjnykSzY=
-github.com/siddontang/ledisdb v0.0.0-20190202134119-8ceb77e66a92/go.mod h1:mF1DpOSOUiJRMR+FDqaqu3EBqrybQtrDDszLUZ6oxPg=
-github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=
-github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
-github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY=
-github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
-github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
-github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8=
-github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
-github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
-github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e h1:GSGeB9EAKY2spCABz6xOX5DbxZEXolK+nBSvmsQwRjM=
-github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
-golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
-golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190730183949-1393eb018365/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
-gopkg.in/ini.v1 v1.44.0 h1:YRJzTUp0kSYWUVFF5XAbDFfyiqwsl0Vb9R8TVP5eRi0=
-gopkg.in/ini.v1 v1.44.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/ini.v1 v1.44.2 h1:N6kNUPqiIyxP+s/aINPzRvNpcTVV30qLC0t6ZjZFlUU=
-gopkg.in/ini.v1 v1.44.2/go.mod h1:M3Cogqpuv0QCi3ExAY5V4uOt4qb/R3xZubo9m8lK5wg=
-gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
-gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+++ /dev/null
-// Copyright 2013 Beego Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package captcha
-
-import (
- "bytes"
- "image"
- "image/color"
- "image/png"
- "io"
- "math"
-)
-
-const (
- fontWidth = 11
- fontHeight = 18
- blackChar = 1
-
- // Standard width and height of a captcha image.
- stdWidth = 240
- stdHeight = 80
-
- // Maximum absolute skew factor of a single digit.
- maxSkew = 0.7
- // Number of background circles.
- circleCount = 20
-)
-
-var font = [][]byte{
- { // 0
- 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
- 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
- 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0,
- 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
- 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
- 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
- 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
- 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
- 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
- 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
- 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
- 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
- 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
- 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1,
- 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
- 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0,
- 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
- 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
- },
- { // 1
- 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
- 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
- 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0,
- 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0,
- 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0,
- 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
- 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- },
- { // 2
- 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0,
- 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
- 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0,
- 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
- 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
- 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0,
- 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
- 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
- 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0,
- 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
- 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
- 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- },
- { // 3
- 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0,
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
- 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
- 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0,
- 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0,
- 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
- 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
- 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
- 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
- },
- { // 4
- 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
- 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0,
- 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0,
- 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0,
- 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0,
- 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0,
- 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0,
- 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0,
- 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0,
- 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0,
- 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0,
- 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
- },
- { // 5
- 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
- 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
- 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
- 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
- 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
- 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0,
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
- 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0,
- },
- { // 6
- 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0,
- 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0,
- 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
- 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0,
- 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0,
- 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0,
- 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1,
- 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
- 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
- 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
- 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
- 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1,
- 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0,
- 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
- 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
- },
- { // 7
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
- 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
- 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
- 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
- 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0,
- 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0,
- 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
- 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
- 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0,
- 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0,
- 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0,
- 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
- },
- { // 8
- 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
- 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0,
- 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1,
- 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
- 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
- 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
- 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0,
- 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
- 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0,
- 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0,
- 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0,
- 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1,
- 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
- 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
- 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
- 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
- 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
- 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
- },
- { // 9
- 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
- 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
- 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0,
- 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0,
- 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
- 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
- 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
- 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1,
- 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1,
- 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1,
- 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
- 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
- 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
- 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0,
- 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
- 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
- },
-}
-
-type Image struct {
- *image.Paletted
- numWidth int
- numHeight int
- dotSize int
-}
-
-var prng = &siprng{}
-
-// randIntn returns a pseudorandom non-negative int in range [0, n).
-func randIntn(n int) int {
- return prng.Intn(n)
-}
-
-// randInt returns a pseudorandom int in range [from, to].
-func randInt(from, to int) int {
- return prng.Intn(to+1-from) + from
-}
-
-// randFloat returns a pseudorandom float64 in range [from, to].
-func randFloat(from, to float64) float64 {
- return (to-from)*prng.Float64() + from
-}
-
-func randomPalette(primary color.Palette) color.Palette {
- p := make([]color.Color, circleCount+1)
- // Transparent color.
- p[0] = color.RGBA{0xFF, 0xFF, 0xFF, 0x00}
- // Primary color.
- var prim color.RGBA
- if len(primary) == 0 {
- prim = color.RGBA{
- uint8(randIntn(129)),
- uint8(randIntn(129)),
- uint8(randIntn(129)),
- 0xFF,
- }
- } else {
- r, g, b, a := primary[randIntn(len(primary)-1)].RGBA()
- prim = color.RGBA{uint8(r), uint8(g), uint8(b), uint8(a)}
- }
- p[1] = prim
- // Circle colors.
- for i := 2; i <= circleCount; i++ {
- p[i] = randomBrightness(prim, 255)
- }
- return p
-}
-
-// NewImage returns a new captcha image of the given width and height with the
-// given digits, where each digit must be in range 0-9. The digit's color is
-// chosen by random from the colorPalette.
-func NewImage(digits []byte, width, height int, colorPalette color.Palette) *Image {
- m := new(Image)
- m.Paletted = image.NewPaletted(image.Rect(0, 0, width, height), randomPalette(colorPalette))
- m.calculateSizes(width, height, len(digits))
- // Randomly position captcha inside the image.
- maxx := width - (m.numWidth+m.dotSize)*len(digits) - m.dotSize
- maxy := height - m.numHeight - m.dotSize*2
- var border int
- if width > height {
- border = height / 5
- } else {
- border = width / 5
- }
- x := randInt(border, maxx-border)
- y := randInt(border, maxy-border)
- // Draw digits.
- for _, n := range digits {
- m.drawDigit(font[n], x, y)
- x += m.numWidth + m.dotSize
- }
- // Draw strike-through line.
- m.strikeThrough()
- // Apply wave distortion.
- m.distort(randFloat(5, 10), randFloat(100, 200))
- // Fill image with random circles.
- m.fillWithCircles(circleCount, m.dotSize)
- return m
-}
-
-// encodedPNG encodes an image to PNG and returns
-// the result as a byte slice.
-func (m *Image) encodedPNG() []byte {
- var buf bytes.Buffer
- if err := png.Encode(&buf, m.Paletted); err != nil {
- panic(err.Error())
- }
- return buf.Bytes()
-}
-
-// WriteTo writes captcha image in PNG format into the given writer.
-func (m *Image) WriteTo(w io.Writer) (int64, error) {
- n, err := w.Write(m.encodedPNG())
- return int64(n), err
-}
-
-func (m *Image) calculateSizes(width, height, ncount int) {
- // Goal: fit all digits inside the image.
- var border int
- if width > height {
- border = height / 4
- } else {
- border = width / 4
- }
- // Convert everything to floats for calculations.
- w := float64(width - border*2)
- h := float64(height - border*2)
- // fw takes into account 1-dot spacing between digits.
- fw := float64(fontWidth + 1)
- fh := float64(fontHeight)
- nc := float64(ncount)
- // Calculate the width of a single digit taking into account only the
- // width of the image.
- nw := w / nc
- // Calculate the height of a digit from this width.
- nh := nw * fh / fw
- // Digit too high?
- if nh > h {
- // Fit digits based on height.
- nh = h
- nw = fw / fh * nh
- }
- // Calculate dot size.
- m.dotSize = int(nh / fh)
- // Save everything, making the actual width smaller by 1 dot to account
- // for spacing between digits.
- m.numWidth = int(nw) - m.dotSize
- m.numHeight = int(nh)
-}
-
-func (m *Image) drawHorizLine(fromX, toX, y int, colorIdx uint8) {
- for x := fromX; x <= toX; x++ {
- m.SetColorIndex(x, y, colorIdx)
- }
-}
-
-func (m *Image) drawCircle(x, y, radius int, colorIdx uint8) {
- f := 1 - radius
- dfx := 1
- dfy := -2 * radius
- xo := 0
- yo := radius
-
- m.SetColorIndex(x, y+radius, colorIdx)
- m.SetColorIndex(x, y-radius, colorIdx)
- m.drawHorizLine(x-radius, x+radius, y, colorIdx)
-
- for xo < yo {
- if f >= 0 {
- yo--
- dfy += 2
- f += dfy
- }
- xo++
- dfx += 2
- f += dfx
- m.drawHorizLine(x-xo, x+xo, y+yo, colorIdx)
- m.drawHorizLine(x-xo, x+xo, y-yo, colorIdx)
- m.drawHorizLine(x-yo, x+yo, y+xo, colorIdx)
- m.drawHorizLine(x-yo, x+yo, y-xo, colorIdx)
- }
-}
-
-func (m *Image) fillWithCircles(n, maxradius int) {
- maxx := m.Bounds().Max.X
- maxy := m.Bounds().Max.Y
- for i := 0; i < n; i++ {
- colorIdx := uint8(randInt(1, circleCount-1))
- r := randInt(1, maxradius)
- m.drawCircle(randInt(r, maxx-r), randInt(r, maxy-r), r, colorIdx)
- }
-}
-
-func (m *Image) strikeThrough() {
- maxx := m.Bounds().Max.X
- maxy := m.Bounds().Max.Y
- y := randInt(maxy/3, maxy-maxy/3)
- amplitude := randFloat(5, 20)
- period := randFloat(80, 180)
- dx := 2.0 * math.Pi / period
- for x := 0; x < maxx; x++ {
- xo := amplitude * math.Cos(float64(y)*dx)
- yo := amplitude * math.Sin(float64(x)*dx)
- for yn := 0; yn < m.dotSize; yn++ {
- r := randInt(0, m.dotSize)
- m.drawCircle(x+int(xo), y+int(yo)+(yn*m.dotSize), r/2, 1)
- }
- }
-}
-
-func (m *Image) drawDigit(digit []byte, x, y int) {
- skf := randFloat(-maxSkew, maxSkew)
- xs := float64(x)
- r := m.dotSize / 2
- y += randInt(-r, r)
- for yo := 0; yo < fontHeight; yo++ {
- for xo := 0; xo < fontWidth; xo++ {
- if digit[yo*fontWidth+xo] != blackChar {
- continue
- }
- m.drawCircle(x+xo*m.dotSize, y+yo*m.dotSize, r, 1)
- }
- xs += skf
- x = int(xs)
- }
-}
-
-func (m *Image) distort(amplude float64, period float64) {
- w := m.Bounds().Max.X
- h := m.Bounds().Max.Y
-
- oldm := m.Paletted
- newm := image.NewPaletted(image.Rect(0, 0, w, h), oldm.Palette)
-
- dx := 2.0 * math.Pi / period
- for x := 0; x < w; x++ {
- for y := 0; y < h; y++ {
- xo := amplude * math.Sin(float64(y)*dx)
- yo := amplude * math.Cos(float64(x)*dx)
- newm.SetColorIndex(x, y, oldm.ColorIndexAt(x+int(xo), y+int(yo)))
- }
- }
- m.Paletted = newm
-}
-
-func randomBrightness(c color.RGBA, max uint8) color.RGBA {
- minc := min3(c.R, c.G, c.B)
- maxc := max3(c.R, c.G, c.B)
- if maxc > max {
- return c
- }
- n := randIntn(int(max-maxc)) - int(minc)
- return color.RGBA{
- uint8(int(c.R) + n),
- uint8(int(c.G) + n),
- uint8(int(c.B) + n),
- uint8(c.A),
- }
-}
-
-func min3(x, y, z uint8) (m uint8) {
- m = x
- if y < m {
- m = y
- }
- if z < m {
- m = z
- }
- return
-}
-
-func max3(x, y, z uint8) (m uint8) {
- m = x
- if y > m {
- m = y
- }
- if z > m {
- m = z
- }
- return
-}
+++ /dev/null
-// Copyright 2013 Beego Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package captcha
-
-import (
- "crypto/rand"
- "encoding/binary"
- "io"
- "sync"
-)
-
-// siprng is PRNG based on SipHash-2-4.
-type siprng struct {
- mu sync.Mutex
- k0, k1, ctr uint64
-}
-
-// siphash implements SipHash-2-4, accepting a uint64 as a message.
-func siphash(k0, k1, m uint64) uint64 {
- // Initialization.
- v0 := k0 ^ 0x736f6d6570736575
- v1 := k1 ^ 0x646f72616e646f6d
- v2 := k0 ^ 0x6c7967656e657261
- v3 := k1 ^ 0x7465646279746573
- t := uint64(8) << 56
-
- // Compression.
- v3 ^= m
-
- // Round 1.
- v0 += v1
- v1 = v1<<13 | v1>>(64-13)
- v1 ^= v0
- v0 = v0<<32 | v0>>(64-32)
-
- v2 += v3
- v3 = v3<<16 | v3>>(64-16)
- v3 ^= v2
-
- v0 += v3
- v3 = v3<<21 | v3>>(64-21)
- v3 ^= v0
-
- v2 += v1
- v1 = v1<<17 | v1>>(64-17)
- v1 ^= v2
- v2 = v2<<32 | v2>>(64-32)
-
- // Round 2.
- v0 += v1
- v1 = v1<<13 | v1>>(64-13)
- v1 ^= v0
- v0 = v0<<32 | v0>>(64-32)
-
- v2 += v3
- v3 = v3<<16 | v3>>(64-16)
- v3 ^= v2
-
- v0 += v3
- v3 = v3<<21 | v3>>(64-21)
- v3 ^= v0
-
- v2 += v1
- v1 = v1<<17 | v1>>(64-17)
- v1 ^= v2
- v2 = v2<<32 | v2>>(64-32)
-
- v0 ^= m
-
- // Compress last block.
- v3 ^= t
-
- // Round 1.
- v0 += v1
- v1 = v1<<13 | v1>>(64-13)
- v1 ^= v0
- v0 = v0<<32 | v0>>(64-32)
-
- v2 += v3
- v3 = v3<<16 | v3>>(64-16)
- v3 ^= v2
-
- v0 += v3
- v3 = v3<<21 | v3>>(64-21)
- v3 ^= v0
-
- v2 += v1
- v1 = v1<<17 | v1>>(64-17)
- v1 ^= v2
- v2 = v2<<32 | v2>>(64-32)
-
- // Round 2.
- v0 += v1
- v1 = v1<<13 | v1>>(64-13)
- v1 ^= v0
- v0 = v0<<32 | v0>>(64-32)
-
- v2 += v3
- v3 = v3<<16 | v3>>(64-16)
- v3 ^= v2
-
- v0 += v3
- v3 = v3<<21 | v3>>(64-21)
- v3 ^= v0
-
- v2 += v1
- v1 = v1<<17 | v1>>(64-17)
- v1 ^= v2
- v2 = v2<<32 | v2>>(64-32)
-
- v0 ^= t
-
- // Finalization.
- v2 ^= 0xff
-
- // Round 1.
- v0 += v1
- v1 = v1<<13 | v1>>(64-13)
- v1 ^= v0
- v0 = v0<<32 | v0>>(64-32)
-
- v2 += v3
- v3 = v3<<16 | v3>>(64-16)
- v3 ^= v2
-
- v0 += v3
- v3 = v3<<21 | v3>>(64-21)
- v3 ^= v0
-
- v2 += v1
- v1 = v1<<17 | v1>>(64-17)
- v1 ^= v2
- v2 = v2<<32 | v2>>(64-32)
-
- // Round 2.
- v0 += v1
- v1 = v1<<13 | v1>>(64-13)
- v1 ^= v0
- v0 = v0<<32 | v0>>(64-32)
-
- v2 += v3
- v3 = v3<<16 | v3>>(64-16)
- v3 ^= v2
-
- v0 += v3
- v3 = v3<<21 | v3>>(64-21)
- v3 ^= v0
-
- v2 += v1
- v1 = v1<<17 | v1>>(64-17)
- v1 ^= v2
- v2 = v2<<32 | v2>>(64-32)
-
- // Round 3.
- v0 += v1
- v1 = v1<<13 | v1>>(64-13)
- v1 ^= v0
- v0 = v0<<32 | v0>>(64-32)
-
- v2 += v3
- v3 = v3<<16 | v3>>(64-16)
- v3 ^= v2
-
- v0 += v3
- v3 = v3<<21 | v3>>(64-21)
- v3 ^= v0
-
- v2 += v1
- v1 = v1<<17 | v1>>(64-17)
- v1 ^= v2
- v2 = v2<<32 | v2>>(64-32)
-
- // Round 4.
- v0 += v1
- v1 = v1<<13 | v1>>(64-13)
- v1 ^= v0
- v0 = v0<<32 | v0>>(64-32)
-
- v2 += v3
- v3 = v3<<16 | v3>>(64-16)
- v3 ^= v2
-
- v0 += v3
- v3 = v3<<21 | v3>>(64-21)
- v3 ^= v0
-
- v2 += v1
- v1 = v1<<17 | v1>>(64-17)
- v1 ^= v2
- v2 = v2<<32 | v2>>(64-32)
-
- return v0 ^ v1 ^ v2 ^ v3
-}
-
-// rekey sets a new PRNG key, which is read from crypto/rand.
-func (p *siprng) rekey() {
- var k [16]byte
- if _, err := io.ReadFull(rand.Reader, k[:]); err != nil {
- panic(err.Error())
- }
- p.k0 = binary.LittleEndian.Uint64(k[0:8])
- p.k1 = binary.LittleEndian.Uint64(k[8:16])
- p.ctr = 1
-}
-
-// Uint64 returns a new pseudorandom uint64.
-// It rekeys PRNG on the first call and every 64 MB of generated data.
-func (p *siprng) Uint64() uint64 {
- p.mu.Lock()
- if p.ctr == 0 || p.ctr > 8*1024*1024 {
- p.rekey()
- }
- v := siphash(p.k0, p.k1, p.ctr)
- p.ctr++
- p.mu.Unlock()
- return v
-}
-
-func (p *siprng) Int63() int64 {
- return int64(p.Uint64() & 0x7fffffffffffffff)
-}
-
-func (p *siprng) Uint32() uint32 {
- return uint32(p.Uint64())
-}
-
-func (p *siprng) Int31() int32 {
- return int32(p.Uint32() & 0x7fffffff)
-}
-
-func (p *siprng) Intn(n int) int {
- if n <= 0 {
- panic("invalid argument to Intn")
- }
- if n <= 1<<31-1 {
- return int(p.Int31n(int32(n)))
- }
- return int(p.Int63n(int64(n)))
-}
-
-func (p *siprng) Int63n(n int64) int64 {
- if n <= 0 {
- panic("invalid argument to Int63n")
- }
- max := int64((1 << 63) - 1 - (1<<63)%uint64(n))
- v := p.Int63()
- for v > max {
- v = p.Int63()
- }
- return v % n
-}
-
-func (p *siprng) Int31n(n int32) int32 {
- if n <= 0 {
- panic("invalid argument to Int31n")
- }
- max := int32((1 << 31) - 1 - (1<<31)%uint32(n))
- v := p.Int31()
- for v > max {
- v = p.Int31()
- }
- return v % n
-}
-
-func (p *siprng) Float64() float64 { return float64(p.Int63()) / (1 << 63) }
+++ /dev/null
-kind: pipeline
-name: default
-
-steps:
-- name: test
- image: golang:1.11
- commands:
- - go build -v
- - go test -v -race -coverprofile=coverage.txt -covermode=atomic
+++ /dev/null
-# Binaries for programs and plugins
-*.exe
-*.exe~
-*.dll
-*.so
-*.dylib
-
-# Test binary, build with `go test -c`
-*.test
-
-# Output of the go coverage tool, specifically when used with LiteIDE
-*.out
+++ /dev/null
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright [yyyy] [name of copyright owner]
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
+++ /dev/null
-# cors
-
-[![Build Status](https://drone.gitea.com/api/badges/macaron/cors/status.svg)](https://drone.gitea.com/macaron/cors)
-
-Package cors is a middleware that handles CORS requests & headers for Macaron.
+++ /dev/null
-package cors
-
-import (
- "fmt"
- "log"
- "net/http"
- "net/url"
- "strconv"
- "strings"
-
- macaron "gitea.com/macaron/macaron"
-)
-
-const version = "0.1.1"
-
-const anyDomain = "!*"
-
-// Version returns the version of this module
-func Version() string {
- return version
-}
-
-/*
-Options to configure the CORS middleware read from the [cors] section of the ini configuration file.
-
-SCHEME may be http or https as accepted schemes or the '*' wildcard to accept any scheme.
-
-ALLOW_DOMAIN may be a comma separated list of domains that are allowed to run CORS requests
-Special values are the a single '*' wildcard that will allow any domain to send requests without
-credentials and the special '!*' wildcard which will reply with requesting domain in the 'access-control-allow-origin'
-header and hence allow requess from any domain *with* credentials.
-
-ALLOW_SUBDOMAIN set to true accepts requests from any subdomain of ALLOW_DOMAIN.
-
-METHODS may be a comma separated list of HTTP-methods to be accepted.
-
-MAX_AGE_SECONDS may be the duration in secs for which the response is cached (default 600).
-ref: https://stackoverflow.com/questions/54300997/is-it-possible-to-cache-http-options-response?noredirect=1#comment95790277_54300997
-ref: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age
-
-ALLOW_CREDENTIALS set to false rejects any request with credentials.
-*/
-type Options struct {
- Section string
- Scheme string
- AllowDomain []string
- AllowSubdomain bool
- Methods []string
- MaxAgeSeconds int
- AllowCredentials bool
-}
-
-func prepareOptions(options []Options) Options {
- var opt Options
- if len(options) > 0 {
- opt = options[0]
- }
-
- if len(opt.Section) == 0 {
- opt.Section = "cors"
- }
- sec := macaron.Config().Section(opt.Section)
-
- if len(opt.Scheme) == 0 {
- opt.Scheme = sec.Key("SCHEME").MustString("http")
- }
- if len(opt.AllowDomain) == 0 {
- opt.AllowDomain = sec.Key("ALLOW_DOMAIN").Strings(",")
- if len(opt.AllowDomain) == 0 {
- opt.AllowDomain = []string{"*"}
- }
- }
- if !opt.AllowSubdomain {
- opt.AllowSubdomain = sec.Key("ALLOW_SUBDOMAIN").MustBool(false)
- }
- if len(opt.Methods) == 0 {
- opt.Methods = sec.Key("METHODS").Strings(",")
- if len(opt.Methods) == 0 {
- opt.Methods = []string{
- http.MethodGet,
- http.MethodHead,
- http.MethodPost,
- http.MethodPut,
- http.MethodPatch,
- http.MethodDelete,
- http.MethodOptions,
- }
- }
- }
- if opt.MaxAgeSeconds <= 0 {
- opt.MaxAgeSeconds = sec.Key("MAX_AGE_SECONDS").MustInt(600)
- }
- if !opt.AllowCredentials {
- opt.AllowCredentials = sec.Key("ALLOW_CREDENTIALS").MustBool(true)
- }
-
- return opt
-}
-
-// CORS responds to preflight requests with adequat access-control-* respond headers
-// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
-// https://fetch.spec.whatwg.org/#cors-protocol-and-credentials
-func CORS(options ...Options) macaron.Handler {
- opt := prepareOptions(options)
- return func(ctx *macaron.Context, log *log.Logger) {
- reqOptions := ctx.Req.Method == http.MethodOptions
-
- headers := map[string]string{
- "access-control-allow-methods": strings.Join(opt.Methods, ","),
- "access-control-allow-headers": ctx.Req.Header.Get("access-control-request-headers"),
- "access-control-max-age": strconv.Itoa(opt.MaxAgeSeconds),
- }
- if opt.AllowDomain[0] == "*" {
- headers["access-control-allow-origin"] = "*"
- } else {
- origin := ctx.Req.Header.Get("Origin")
- if reqOptions && origin == "" {
- respErrorf(ctx, log, http.StatusBadRequest, "missing origin header in CORS request")
- return
- }
-
- u, err := url.Parse(origin)
- if err != nil {
- respErrorf(ctx, log, http.StatusBadRequest, "Failed to parse CORS origin header. Reason: %v", err)
- return
- }
-
- ok := false
- for _, d := range opt.AllowDomain {
- if u.Hostname() == d || (opt.AllowSubdomain && strings.HasSuffix(u.Hostname(), "."+d)) || d == anyDomain {
- ok = true
- break
- }
- }
- if ok {
- if opt.Scheme != "*" {
- u.Scheme = opt.Scheme
- }
- headers["access-control-allow-origin"] = u.String()
- headers["access-control-allow-credentials"] = strconv.FormatBool(opt.AllowCredentials)
- headers["vary"] = "Origin"
- }
- if reqOptions && !ok {
- respErrorf(ctx, log, http.StatusBadRequest, "CORS request from prohibited domain %v", origin)
- return
- }
- }
- ctx.Resp.Before(func(w macaron.ResponseWriter) {
- for k, v := range headers {
- w.Header().Set(k, v)
- }
- })
- if reqOptions {
- ctx.Resp.WriteHeader(200) // return response
- return
- }
- }
-}
-
-func respErrorf(ctx *macaron.Context, log *log.Logger, statusCode int, format string, a ...interface{}) {
- msg := fmt.Sprintf(format, a...)
- log.Println(msg)
- ctx.WriteHeader(statusCode)
- _, err := ctx.Write([]byte(msg))
- if err != nil {
- panic(err)
- }
- return
-}
+++ /dev/null
-module gitea.com/macaron/cors
-
-go 1.11
-
-require gitea.com/macaron/macaron v1.3.3-0.20190803174002-53e005ff4827
+++ /dev/null
-gitea.com/macaron/inject v0.0.0-20190803172902-8375ba841591 h1:UbCTjPcLrNxR9LzKDjQBMT2zoxZuEnca1pZCpgeMuhQ=
-gitea.com/macaron/inject v0.0.0-20190803172902-8375ba841591/go.mod h1:h6E4kLao1Yko6DOU6QDnQPcuoNzvbZqzj2mtPcEn1aM=
-gitea.com/macaron/macaron v1.3.3-0.20190803174002-53e005ff4827 h1:/rT4MEFjhdViy2BFWKUwbC0JSNSziEbBCM7q4/B9qgo=
-gitea.com/macaron/macaron v1.3.3-0.20190803174002-53e005ff4827/go.mod h1:/rvxMjIkOq4BM8uPUb+VHuU02ZfAO6R4+wD//tiCiRw=
-github.com/Unknwon/com v0.0.0-20190321035513-0fed4efef755 h1:1B7wb36fHLSwZfHg6ngZhhtIEHQjiC5H4p7qQGBEffg=
-github.com/Unknwon/com v0.0.0-20190321035513-0fed4efef755/go.mod h1:voKvFVpXBJxdIPeqjoJuLK+UVcRlo/JLjeToGxPYu68=
-github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
-github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE=
-github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
-github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
-github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
-github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
-github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY=
-github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
-github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w=
-github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
-github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8=
-github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
-golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-gopkg.in/ini.v1 v1.44.0 h1:YRJzTUp0kSYWUVFF5XAbDFfyiqwsl0Vb9R8TVP5eRi0=
-gopkg.in/ini.v1 v1.44.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+++ /dev/null
-kind: pipeline
-name: go1-1-1
-
-steps:
-- name: test
- image: golang:1.11
- environment:
- GOPROXY: https://goproxy.cn
- commands:
- - go build -v
- - go test -v -race -coverprofile=coverage.txt -covermode=atomic
-
----
-kind: pipeline
-name: go1-1-2
-
-steps:
-- name: test
- image: golang:1.12
- environment:
- GOPROXY: https://goproxy.cn
- commands:
- - go build -v
- - go test -v -race -coverprofile=coverage.txt -covermode=atomic
\ No newline at end of file
+++ /dev/null
-Apache License
-Version 2.0, January 2004
-http://www.apache.org/licenses/
-
-TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-1. Definitions.
-
-"License" shall mean the terms and conditions for use, reproduction, and
-distribution as defined by Sections 1 through 9 of this document.
-
-"Licensor" shall mean the copyright owner or entity authorized by the copyright
-owner that is granting the License.
-
-"Legal Entity" shall mean the union of the acting entity and all other entities
-that control, are controlled by, or are under common control with that entity.
-For the purposes of this definition, "control" means (i) the power, direct or
-indirect, to cause the direction or management of such entity, whether by
-contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
-outstanding shares, or (iii) beneficial ownership of such entity.
-
-"You" (or "Your") shall mean an individual or Legal Entity exercising
-permissions granted by this License.
-
-"Source" form shall mean the preferred form for making modifications, including
-but not limited to software source code, documentation source, and configuration
-files.
-
-"Object" form shall mean any form resulting from mechanical transformation or
-translation of a Source form, including but not limited to compiled object code,
-generated documentation, and conversions to other media types.
-
-"Work" shall mean the work of authorship, whether in Source or Object form, made
-available under the License, as indicated by a copyright notice that is included
-in or attached to the work (an example is provided in the Appendix below).
-
-"Derivative Works" shall mean any work, whether in Source or Object form, that
-is based on (or derived from) the Work and for which the editorial revisions,
-annotations, elaborations, or other modifications represent, as a whole, an
-original work of authorship. For the purposes of this License, Derivative Works
-shall not include works that remain separable from, or merely link (or bind by
-name) to the interfaces of, the Work and Derivative Works thereof.
-
-"Contribution" shall mean any work of authorship, including the original version
-of the Work and any modifications or additions to that Work or Derivative Works
-thereof, that is intentionally submitted to Licensor for inclusion in the Work
-by the copyright owner or by an individual or Legal Entity authorized to submit
-on behalf of the copyright owner. For the purposes of this definition,
-"submitted" means any form of electronic, verbal, or written communication sent
-to the Licensor or its representatives, including but not limited to
-communication on electronic mailing lists, source code control systems, and
-issue tracking systems that are managed by, or on behalf of, the Licensor for
-the purpose of discussing and improving the Work, but excluding communication
-that is conspicuously marked or otherwise designated in writing by the copyright
-owner as "Not a Contribution."
-
-"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
-of whom a Contribution has been received by Licensor and subsequently
-incorporated within the Work.
-
-2. Grant of Copyright License.
-
-Subject to the terms and conditions of this License, each Contributor hereby
-grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
-irrevocable copyright license to reproduce, prepare Derivative Works of,
-publicly display, publicly perform, sublicense, and distribute the Work and such
-Derivative Works in Source or Object form.
-
-3. Grant of Patent License.
-
-Subject to the terms and conditions of this License, each Contributor hereby
-grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
-irrevocable (except as stated in this section) patent license to make, have
-made, use, offer to sell, sell, import, and otherwise transfer the Work, where
-such license applies only to those patent claims licensable by such Contributor
-that are necessarily infringed by their Contribution(s) alone or by combination
-of their Contribution(s) with the Work to which such Contribution(s) was
-submitted. If You institute patent litigation against any entity (including a
-cross-claim or counterclaim in a lawsuit) alleging that the Work or a
-Contribution incorporated within the Work constitutes direct or contributory
-patent infringement, then any patent licenses granted to You under this License
-for that Work shall terminate as of the date such litigation is filed.
-
-4. Redistribution.
-
-You may reproduce and distribute copies of the Work or Derivative Works thereof
-in any medium, with or without modifications, and in Source or Object form,
-provided that You meet the following conditions:
-
-You must give any other recipients of the Work or Derivative Works a copy of
-this License; and
-You must cause any modified files to carry prominent notices stating that You
-changed the files; and
-You must retain, in the Source form of any Derivative Works that You distribute,
-all copyright, patent, trademark, and attribution notices from the Source form
-of the Work, excluding those notices that do not pertain to any part of the
-Derivative Works; and
-If the Work includes a "NOTICE" text file as part of its distribution, then any
-Derivative Works that You distribute must include a readable copy of the
-attribution notices contained within such NOTICE file, excluding those notices
-that do not pertain to any part of the Derivative Works, in at least one of the
-following places: within a NOTICE text file distributed as part of the
-Derivative Works; within the Source form or documentation, if provided along
-with the Derivative Works; or, within a display generated by the Derivative
-Works, if and wherever such third-party notices normally appear. The contents of
-the NOTICE file are for informational purposes only and do not modify the
-License. You may add Your own attribution notices within Derivative Works that
-You distribute, alongside or as an addendum to the NOTICE text from the Work,
-provided that such additional attribution notices cannot be construed as
-modifying the License.
-You may add Your own copyright statement to Your modifications and may provide
-additional or different license terms and conditions for use, reproduction, or
-distribution of Your modifications, or for any such Derivative Works as a whole,
-provided Your use, reproduction, and distribution of the Work otherwise complies
-with the conditions stated in this License.
-
-5. Submission of Contributions.
-
-Unless You explicitly state otherwise, any Contribution intentionally submitted
-for inclusion in the Work by You to the Licensor shall be under the terms and
-conditions of this License, without any additional terms or conditions.
-Notwithstanding the above, nothing herein shall supersede or modify the terms of
-any separate license agreement you may have executed with Licensor regarding
-such Contributions.
-
-6. Trademarks.
-
-This License does not grant permission to use the trade names, trademarks,
-service marks, or product names of the Licensor, except as required for
-reasonable and customary use in describing the origin of the Work and
-reproducing the content of the NOTICE file.
-
-7. Disclaimer of Warranty.
-
-Unless required by applicable law or agreed to in writing, Licensor provides the
-Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
-including, without limitation, any warranties or conditions of TITLE,
-NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
-solely responsible for determining the appropriateness of using or
-redistributing the Work and assume any risks associated with Your exercise of
-permissions under this License.
-
-8. Limitation of Liability.
-
-In no event and under no legal theory, whether in tort (including negligence),
-contract, or otherwise, unless required by applicable law (such as deliberate
-and grossly negligent acts) or agreed to in writing, shall any Contributor be
-liable to You for damages, including any direct, indirect, special, incidental,
-or consequential damages of any character arising as a result of this License or
-out of the use or inability to use the Work (including but not limited to
-damages for loss of goodwill, work stoppage, computer failure or malfunction, or
-any and all other commercial damages or losses), even if such Contributor has
-been advised of the possibility of such damages.
-
-9. Accepting Warranty or Additional Liability.
-
-While redistributing the Work or Derivative Works thereof, You may choose to
-offer, and charge a fee for, acceptance of support, warranty, indemnity, or
-other liability obligations and/or rights consistent with this License. However,
-in accepting such obligations, You may act only on Your own behalf and on Your
-sole responsibility, not on behalf of any other Contributor, and only if You
-agree to indemnify, defend, and hold each Contributor harmless for any liability
-incurred by, or claims asserted against, such Contributor by reason of your
-accepting any such warranty or additional liability.
-
-END OF TERMS AND CONDITIONS
-
-APPENDIX: How to apply the Apache License to your work
-
-To apply the Apache License to your work, attach the following boilerplate
-notice, with the fields enclosed by brackets "[]" replaced with your own
-identifying information. (Don't include the brackets!) The text should be
-enclosed in the appropriate comment syntax for the file format. We also
-recommend that a file or class name and description of purpose be included on
-the same "printed page" as the copyright notice for easier identification within
-third-party archives.
-
- Copyright [yyyy] [name of copyright owner]
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
\ No newline at end of file
+++ /dev/null
-# csrf [![Build Status](https://drone.gitea.com/api/badges/macaron/csrf/status.svg)](https://drone.gitea.com/macaron/csrf)
-
-Middleware csrf generates and validates CSRF tokens for [Macaron](https://gitea.com/macaron/macaron).
-
-[API Reference](https://gowalker.org/gitea.com/macaron/csrf)
-
-### Installation
-
- go get gitea.com/macaron/csrf
-
-## Getting Help
-
-- [API Reference](https://gowalker.org/gitea.com/macaron/csrf)
-- [Documentation](http://go-macaron.com/docs/middlewares/csrf)
-
-## License
-
-This project is under the Apache License, Version 2.0. See the [LICENSE](LICENSE) file for the full license text.
\ No newline at end of file
+++ /dev/null
-// Copyright 2013 Martini Authors
-// Copyright 2014 The Macaron Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-// Package csrf is a middleware that generates and validates CSRF tokens for Macaron.
-package csrf
-
-import (
- "net/http"
- "time"
-
- "gitea.com/macaron/macaron"
- "gitea.com/macaron/session"
- "github.com/unknwon/com"
-)
-
-const _VERSION = "0.1.1"
-
-func Version() string {
- return _VERSION
-}
-
-// CSRF represents a CSRF service and is used to get the current token and validate a suspect token.
-type CSRF interface {
- // Return HTTP header to search for token.
- GetHeaderName() string
- // Return form value to search for token.
- GetFormName() string
- // Return cookie name to search for token.
- GetCookieName() string
- // Return cookie path
- GetCookiePath() string
- // Return the flag value used for the csrf token.
- GetCookieHttpOnly() bool
- // Return the token.
- GetToken() string
- // Validate by token.
- ValidToken(t string) bool
- // Error replies to the request with a custom function when ValidToken fails.
- Error(w http.ResponseWriter)
-}
-
-type csrf struct {
- // Header name value for setting and getting csrf token.
- Header string
- // Form name value for setting and getting csrf token.
- Form string
- // Cookie name value for setting and getting csrf token.
- Cookie string
- //Cookie domain
- CookieDomain string
- //Cookie path
- CookiePath string
- // Cookie HttpOnly flag value used for the csrf token.
- CookieHttpOnly bool
- // Token generated to pass via header, cookie, or hidden form value.
- Token string
- // This value must be unique per user.
- ID string
- // Secret used along with the unique id above to generate the Token.
- Secret string
- // ErrorFunc is the custom function that replies to the request when ValidToken fails.
- ErrorFunc func(w http.ResponseWriter)
-}
-
-// GetHeaderName returns the name of the HTTP header for csrf token.
-func (c *csrf) GetHeaderName() string {
- return c.Header
-}
-
-// GetFormName returns the name of the form value for csrf token.
-func (c *csrf) GetFormName() string {
- return c.Form
-}
-
-// GetCookieName returns the name of the cookie for csrf token.
-func (c *csrf) GetCookieName() string {
- return c.Cookie
-}
-
-// GetCookiePath returns the path of the cookie for csrf token.
-func (c *csrf) GetCookiePath() string {
- return c.CookiePath
-}
-
-// GetCookieHttpOnly returns the flag value used for the csrf token.
-func (c *csrf) GetCookieHttpOnly() bool {
- return c.CookieHttpOnly
-}
-
-// GetToken returns the current token. This is typically used
-// to populate a hidden form in an HTML template.
-func (c *csrf) GetToken() string {
- return c.Token
-}
-
-// ValidToken validates the passed token against the existing Secret and ID.
-func (c *csrf) ValidToken(t string) bool {
- return ValidToken(t, c.Secret, c.ID, "POST")
-}
-
-// Error replies to the request when ValidToken fails.
-func (c *csrf) Error(w http.ResponseWriter) {
- c.ErrorFunc(w)
-}
-
-// Options maintains options to manage behavior of Generate.
-type Options struct {
- // The global secret value used to generate Tokens.
- Secret string
- // HTTP header used to set and get token.
- Header string
- // Form value used to set and get token.
- Form string
- // Cookie value used to set and get token.
- Cookie string
- // Cookie domain.
- CookieDomain string
- // Cookie path.
- CookiePath string
- CookieHttpOnly bool
- // Key used for getting the unique ID per user.
- SessionKey string
- // oldSeesionKey saves old value corresponding to SessionKey.
- oldSeesionKey string
- // If true, send token via X-CSRFToken header.
- SetHeader bool
- // If true, send token via _csrf cookie.
- SetCookie bool
- // Set the Secure flag to true on the cookie.
- Secure bool
- // Disallow Origin appear in request header.
- Origin bool
- // The function called when Validate fails.
- ErrorFunc func(w http.ResponseWriter)
-}
-
-func prepareOptions(options []Options) Options {
- var opt Options
- if len(options) > 0 {
- opt = options[0]
- }
-
- // Defaults.
- if len(opt.Secret) == 0 {
- opt.Secret = string(com.RandomCreateBytes(10))
- }
- if len(opt.Header) == 0 {
- opt.Header = "X-CSRFToken"
- }
- if len(opt.Form) == 0 {
- opt.Form = "_csrf"
- }
- if len(opt.Cookie) == 0 {
- opt.Cookie = "_csrf"
- }
- if len(opt.CookiePath) == 0 {
- opt.CookiePath = "/"
- }
- if len(opt.SessionKey) == 0 {
- opt.SessionKey = "uid"
- }
- opt.oldSeesionKey = "_old_" + opt.SessionKey
- if opt.ErrorFunc == nil {
- opt.ErrorFunc = func(w http.ResponseWriter) {
- http.Error(w, "Invalid csrf token.", http.StatusBadRequest)
- }
- }
-
- return opt
-}
-
-// Generate maps CSRF to each request. If this request is a Get request, it will generate a new token.
-// Additionally, depending on options set, generated tokens will be sent via Header and/or Cookie.
-func Generate(options ...Options) macaron.Handler {
- opt := prepareOptions(options)
- return func(ctx *macaron.Context, sess session.Store) {
- x := &csrf{
- Secret: opt.Secret,
- Header: opt.Header,
- Form: opt.Form,
- Cookie: opt.Cookie,
- CookieDomain: opt.CookieDomain,
- CookiePath: opt.CookiePath,
- CookieHttpOnly: opt.CookieHttpOnly,
- ErrorFunc: opt.ErrorFunc,
- }
- ctx.MapTo(x, (*CSRF)(nil))
-
- if opt.Origin && len(ctx.Req.Header.Get("Origin")) > 0 {
- return
- }
-
- x.ID = "0"
- uid := sess.Get(opt.SessionKey)
- if uid != nil {
- x.ID = com.ToStr(uid)
- }
-
- needsNew := false
- oldUid := sess.Get(opt.oldSeesionKey)
- if oldUid == nil || oldUid.(string) != x.ID {
- needsNew = true
- sess.Set(opt.oldSeesionKey, x.ID)
- } else {
- // If cookie present, map existing token, else generate a new one.
- if val := ctx.GetCookie(opt.Cookie); len(val) > 0 {
- // FIXME: test coverage.
- x.Token = val
- } else {
- needsNew = true
- }
- }
-
- if needsNew {
- // FIXME: actionId.
- x.Token = GenerateToken(x.Secret, x.ID, "POST")
- if opt.SetCookie {
- ctx.SetCookie(opt.Cookie, x.Token, 0, opt.CookiePath, opt.CookieDomain, opt.Secure, opt.CookieHttpOnly, time.Now().AddDate(0, 0, 1))
- }
- }
-
- if opt.SetHeader {
- ctx.Resp.Header().Add(opt.Header, x.Token)
- }
- }
-}
-
-// Csrfer maps CSRF to each request. If this request is a Get request, it will generate a new token.
-// Additionally, depending on options set, generated tokens will be sent via Header and/or Cookie.
-func Csrfer(options ...Options) macaron.Handler {
- return Generate(options...)
-}
-
-// Validate should be used as a per route middleware. It attempts to get a token from a "X-CSRFToken"
-// HTTP header and then a "_csrf" form value. If one of these is found, the token will be validated
-// using ValidToken. If this validation fails, custom Error is sent in the reply.
-// If neither a header or form value is found, http.StatusBadRequest is sent.
-func Validate(ctx *macaron.Context, x CSRF) {
- if token := ctx.Req.Header.Get(x.GetHeaderName()); len(token) > 0 {
- if !x.ValidToken(token) {
- ctx.SetCookie(x.GetCookieName(), "", -1, x.GetCookiePath())
- x.Error(ctx.Resp)
- }
- return
- }
- if token := ctx.Req.FormValue(x.GetFormName()); len(token) > 0 {
- if !x.ValidToken(token) {
- ctx.SetCookie(x.GetCookieName(), "", -1, x.GetCookiePath())
- x.Error(ctx.Resp)
- }
- return
- }
-
- http.Error(ctx.Resp, "Bad Request: no CSRF token present", http.StatusBadRequest)
-}
+++ /dev/null
-module gitea.com/macaron/csrf
-
-go 1.11
-
-require (
- gitea.com/macaron/macaron v1.3.3-0.20190821202302-9646c0587edb
- gitea.com/macaron/session v0.0.0-20190821211443-122c47c5f705
- github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c // indirect
- github.com/smartystreets/assertions v1.0.1 // indirect
- github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337
- github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e
-)
+++ /dev/null
-gitea.com/macaron/inject v0.0.0-20190803172902-8375ba841591 h1:UbCTjPcLrNxR9LzKDjQBMT2zoxZuEnca1pZCpgeMuhQ=
-gitea.com/macaron/inject v0.0.0-20190803172902-8375ba841591/go.mod h1:h6E4kLao1Yko6DOU6QDnQPcuoNzvbZqzj2mtPcEn1aM=
-gitea.com/macaron/macaron v1.3.3-0.20190821202302-9646c0587edb h1:amL0md6orTj1tXY16ANzVU9FmzQB+W7aJwp8pVDbrmA=
-gitea.com/macaron/macaron v1.3.3-0.20190821202302-9646c0587edb/go.mod h1:0coI+mSPSwbsyAbOuFllVS38awuk9mevhLD52l50Gjs=
-gitea.com/macaron/session v0.0.0-20190821211443-122c47c5f705 h1:mvkQGAlON1Z6Y8pqa/+FpYIskk54mazuECUfZK5oTg0=
-gitea.com/macaron/session v0.0.0-20190821211443-122c47c5f705/go.mod h1:1ujH0jD6Ca4iK9NL0Q2a7fG2chvXx5hVa7hBfABwpkA=
-github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
-github.com/couchbase/gomemcached v0.0.0-20190515232915-c4b4ca0eb21d/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c=
-github.com/couchbase/goutils v0.0.0-20190315194238-f9d42b11473b/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
-github.com/couchbaselabs/go-couchbase v0.0.0-20190708161019-23e7ca2ce2b7/go.mod h1:mby/05p8HE5yHEAKiIH/555NoblMs7PtW6NrYshDruc=
-github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
-github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
-github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
-github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
-github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
-github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4=
-github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
-github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
-github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
-github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
-github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
-github.com/lunny/log v0.0.0-20160921050905-7887c61bf0de/go.mod h1:3q8WtuPQsoRbatJuy3nvq/hRSvuBJrHHr+ybPPiNvHQ=
-github.com/lunny/nodb v0.0.0-20160621015157-fc1ef06ad4af/go.mod h1:Cqz6pqow14VObJ7peltM+2n3PWOz7yTrfUuGbVFkzN0=
-github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
-github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
-github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
-github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
-github.com/siddontang/go-snappy v0.0.0-20140704025258-d8f7bb82a96d/go.mod h1:vq0tzqLRu6TS7Id0wMo2N5QzJoKedVeovOpHjnykSzY=
-github.com/siddontang/ledisdb v0.0.0-20190202134119-8ceb77e66a92/go.mod h1:mF1DpOSOUiJRMR+FDqaqu3EBqrybQtrDDszLUZ6oxPg=
-github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=
-github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
-github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
-github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w=
-github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
-github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
-github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8=
-github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
-github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
-github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e h1:GSGeB9EAKY2spCABz6xOX5DbxZEXolK+nBSvmsQwRjM=
-github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
-golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
-golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
-gopkg.in/ini.v1 v1.44.0 h1:YRJzTUp0kSYWUVFF5XAbDFfyiqwsl0Vb9R8TVP5eRi0=
-gopkg.in/ini.v1 v1.44.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
-gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+++ /dev/null
-// Copyright 2012 Google Inc. All Rights Reserved.
-// Copyright 2014 The Macaron Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package csrf
-
-import (
- "bytes"
- "crypto/hmac"
- "crypto/sha1"
- "crypto/subtle"
- "encoding/base64"
- "fmt"
- "strconv"
- "strings"
- "time"
-)
-
-// The duration that XSRF tokens are valid.
-// It is exported so clients may set cookie timeouts that match generated tokens.
-const TIMEOUT = 24 * time.Hour
-
-// clean sanitizes a string for inclusion in a token by replacing all ":"s.
-func clean(s string) string {
- return strings.Replace(s, ":", "_", -1)
-}
-
-// GenerateToken returns a URL-safe secure XSRF token that expires in 24 hours.
-//
-// key is a secret key for your application.
-// userID is a unique identifier for the user.
-// actionID is the action the user is taking (e.g. POSTing to a particular path).
-func GenerateToken(key, userID, actionID string) string {
- return generateTokenAtTime(key, userID, actionID, time.Now())
-}
-
-// generateTokenAtTime is like Generate, but returns a token that expires 24 hours from now.
-func generateTokenAtTime(key, userID, actionID string, now time.Time) string {
- h := hmac.New(sha1.New, []byte(key))
- fmt.Fprintf(h, "%s:%s:%d", clean(userID), clean(actionID), now.UnixNano())
- tok := fmt.Sprintf("%s:%d", h.Sum(nil), now.UnixNano())
- return base64.RawURLEncoding.EncodeToString([]byte(tok))
-}
-
-// Valid returns true if token is a valid, unexpired token returned by Generate.
-func ValidToken(token, key, userID, actionID string) bool {
- return validTokenAtTime(token, key, userID, actionID, time.Now())
-}
-
-// validTokenAtTime is like Valid, but it uses now to check if the token is expired.
-func validTokenAtTime(token, key, userID, actionID string, now time.Time) bool {
- // Decode the token.
- data, err := base64.RawURLEncoding.DecodeString(token)
- if err != nil {
- return false
- }
-
- // Extract the issue time of the token.
- sep := bytes.LastIndex(data, []byte{':'})
- if sep < 0 {
- return false
- }
- nanos, err := strconv.ParseInt(string(data[sep+1:]), 10, 64)
- if err != nil {
- return false
- }
- issueTime := time.Unix(0, nanos)
-
- // Check that the token is not expired.
- if now.Sub(issueTime) >= TIMEOUT {
- return false
- }
-
- // Check that the token is not from the future.
- // Allow 1 minute grace period in case the token is being verified on a
- // machine whose clock is behind the machine that issued the token.
- if issueTime.After(now.Add(1 * time.Minute)) {
- return false
- }
-
- expected := generateTokenAtTime(key, userID, actionID, issueTime)
-
- // Check that the token matches the expected value.
- // Use constant time comparison to avoid timing attacks.
- return subtle.ConstantTimeCompare([]byte(token), []byte(expected)) == 1
-}
+++ /dev/null
-kind: pipeline
-name: go1-14
-
-steps:
-- name: test
- image: golang:1.14
- environment:
- GOPROXY: https://goproxy.cn
- commands:
- - go build -v
- - go test -v -race -coverprofile=coverage.txt -covermode=atomic
-
----
-kind: pipeline
-name: go1-15
-
-steps:
-- name: test
- image: golang:1.15
- environment:
- GOPROXY: https://goproxy.cn
- commands:
- - go build -v
- - go test -v -race -coverprofile=coverage.txt -covermode=atomic
\ No newline at end of file
+++ /dev/null
-# gzip
-
-Middleware gzip provides gzip comparess middleware for [Macaron](https://gitea.com/macaron/macaron).
-
-### Installation
-
- go get gitea.com/macaron/gzip
-
-## Getting Help
-
-- [API Reference](https://godoc.org/gitea.com/macaron/gzip)
-
-## Credits
-
-This package is a modified version of [go-macaron gzip](github.com/go-macaron/gzip).
-
-## License
-
-This project is under the Apache License, Version 2.0. See the [LICENSE](LICENSE) file for the full license text.
\ No newline at end of file
+++ /dev/null
-module gitea.com/macaron/gzip
-
-go 1.12
-
-require (
- gitea.com/macaron/macaron v1.5.0
- github.com/klauspost/compress v1.9.2
- github.com/stretchr/testify v1.4.0
- golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a // indirect
- gopkg.in/ini.v1 v1.60.1 // indirect
-)
+++ /dev/null
-gitea.com/macaron/inject v0.0.0-20190805023432-d4c86e31027a h1:aOKEXkDTnh4euoH0so/THLXeHtQuqHmDPb1xEk6Ehok=
-gitea.com/macaron/inject v0.0.0-20190805023432-d4c86e31027a/go.mod h1:h6E4kLao1Yko6DOU6QDnQPcuoNzvbZqzj2mtPcEn1aM=
-gitea.com/macaron/macaron v1.5.0 h1:TvWEcHw1/zaHlo0GTuKEukLh3A99+QsU2mjBrXLXjVQ=
-gitea.com/macaron/macaron v1.5.0/go.mod h1:P7hfDbQjcW22lkYkXlxdRIfWOXxH2+K4EogN4Q0UlLY=
-github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
-github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
-github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
-github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
-github.com/klauspost/compress v1.9.2 h1:LfVyl+ZlLlLDeQ/d2AqfGIIH4qEDu0Ed2S5GyhCWIWY=
-github.com/klauspost/compress v1.9.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
-github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
-github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
-github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY=
-github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
-github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w=
-github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
-github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
-github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8=
-github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
-github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
-github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
-github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs=
-github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
-golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM=
-golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/ini.v1 v1.44.0 h1:YRJzTUp0kSYWUVFF5XAbDFfyiqwsl0Vb9R8TVP5eRi0=
-gopkg.in/ini.v1 v1.44.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/ini.v1 v1.60.1 h1:P5y5shSkb0CFe44qEeMBgn8JLow09MP17jlJHanke5g=
-gopkg.in/ini.v1 v1.60.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
-gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+++ /dev/null
-// Copyright 2019 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package gzip
-
-import (
- "bufio"
- "errors"
- "fmt"
- "io"
- "net"
- "net/http"
- "regexp"
- "strconv"
- "strings"
- "sync"
-
- "gitea.com/macaron/macaron"
- "github.com/klauspost/compress/gzip"
-)
-
-const (
- acceptEncodingHeader = "Accept-Encoding"
- contentEncodingHeader = "Content-Encoding"
- contentLengthHeader = "Content-Length"
- contentTypeHeader = "Content-Type"
- rangeHeader = "Range"
- varyHeader = "Vary"
-)
-
-const (
- // MinSize is the minimum size of content we will compress
- MinSize = 1400
-)
-
-// noopClosers are io.Writers with a shim to prevent early closure
-type noopCloser struct {
- io.Writer
-}
-
-func (noopCloser) Close() error { return nil }
-
-// WriterPool is a gzip writer pool to reduce workload on creation of
-// gzip writers
-type WriterPool struct {
- pool sync.Pool
- compressionLevel int
-}
-
-// NewWriterPool creates a new pool
-func NewWriterPool(compressionLevel int) *WriterPool {
- return &WriterPool{pool: sync.Pool{
- // New will return nil, we'll manage the creation of new
- // writers in the middleware
- New: func() interface{} { return nil },
- },
- compressionLevel: compressionLevel}
-}
-
-// Get a writer from the pool - or create one if not available
-func (wp *WriterPool) Get(rw macaron.ResponseWriter) *gzip.Writer {
- ret := wp.pool.Get()
- if ret == nil {
- ret, _ = gzip.NewWriterLevel(rw, wp.compressionLevel)
- } else {
- ret.(*gzip.Writer).Reset(rw)
- }
- return ret.(*gzip.Writer)
-}
-
-// Put returns a writer to the pool
-func (wp *WriterPool) Put(w *gzip.Writer) {
- wp.pool.Put(w)
-}
-
-var writerPool WriterPool
-
-// Options represents the configuration for the gzip middleware
-type Options struct {
- CompressionLevel int
-}
-
-func validateCompressionLevel(level int) bool {
- return level == gzip.DefaultCompression ||
- level == gzip.ConstantCompression ||
- (level >= gzip.BestSpeed && level <= gzip.BestCompression)
-}
-
-func validate(options []Options) Options {
- // Default to level 4 compression (Best results seem to be between 4 and 6)
- opt := Options{CompressionLevel: 4}
- if len(options) > 0 {
- opt = options[0]
- }
- if !validateCompressionLevel(opt.CompressionLevel) {
- opt.CompressionLevel = 4
- }
- return opt
-}
-
-// Middleware creates a macaron.Handler to proxy the response
-func Middleware(options ...Options) macaron.Handler {
- opt := validate(options)
- writerPool = *NewWriterPool(opt.CompressionLevel)
- regex := regexp.MustCompile(`bytes=(\d+)\-.*`)
-
- return func(ctx *macaron.Context) {
- // If the client won't accept gzip or x-gzip don't compress
- if !strings.Contains(ctx.Req.Header.Get(acceptEncodingHeader), "gzip") &&
- !strings.Contains(ctx.Req.Header.Get(acceptEncodingHeader), "x-gzip") {
- return
- }
-
- // If the client is asking for a specific range of bytes - don't compress
- if rangeHdr := ctx.Req.Header.Get(rangeHeader); rangeHdr != "" {
-
- match := regex.FindStringSubmatch(rangeHdr)
- if len(match) > 1 {
- return
- }
- }
-
- // OK we should proxy the response writer
- // We are still not necessarily going to compress...
- proxyWriter := &ProxyResponseWriter{
- internal: ctx.Resp,
- }
- defer proxyWriter.Close()
-
- ctx.Resp = proxyWriter
- ctx.MapTo(proxyWriter, (*http.ResponseWriter)(nil))
-
- // Check if render middleware has been registered,
- // if yes, we need to modify ResponseWriter for it as well.
- if _, ok := ctx.Render.(*macaron.DummyRender); !ok {
- ctx.Render.SetResponseWriter(proxyWriter)
- }
-
- ctx.Next()
- ctx.Resp = proxyWriter.internal
- }
-}
-
-// ProxyResponseWriter is a wrapped macaron ResponseWriter that may compress its contents
-type ProxyResponseWriter struct {
- writer io.WriteCloser
- internal macaron.ResponseWriter
- stopped bool
-
- code int
- buf []byte
-}
-
-// Header returns the header map
-func (proxy *ProxyResponseWriter) Header() http.Header {
- return proxy.internal.Header()
-}
-
-// Status returns the status code of the response or 0 if the response has not been written.
-func (proxy *ProxyResponseWriter) Status() int {
- if proxy.code != 0 {
- return proxy.code
- }
- return proxy.internal.Status()
-}
-
-// Written returns whether or not the ResponseWriter has been written.
-func (proxy *ProxyResponseWriter) Written() bool {
- if proxy.code != 0 {
- return true
- }
- return proxy.internal.Written()
-}
-
-// Size returns the size of the response body.
-func (proxy *ProxyResponseWriter) Size() int {
- return proxy.internal.Size()
-}
-
-// Before allows for a function to be called before the ResponseWriter has been written to. This is
-// useful for setting headers or any other operations that must happen before a response has been written.
-func (proxy *ProxyResponseWriter) Before(before macaron.BeforeFunc) {
- proxy.internal.Before(before)
-}
-
-// Write appends data to the proxied gzip writer.
-func (proxy *ProxyResponseWriter) Write(b []byte) (int, error) {
- // if writer is initialized, use the writer
- if proxy.writer != nil {
- return proxy.writer.Write(b)
- }
-
- proxy.buf = append(proxy.buf, b...)
-
- var (
- contentLength, _ = strconv.Atoi(proxy.Header().Get(contentLengthHeader))
- contentType = proxy.Header().Get(contentTypeHeader)
- contentEncoding = proxy.Header().Get(contentEncodingHeader)
- )
-
- // OK if an encoding hasn't been chosen, and content length > 1400
- // and content type isn't a compressed type
- if contentEncoding == "" &&
- (contentLength == 0 || contentLength >= MinSize) &&
- (contentType == "" || !compressedContentType(contentType)) {
- // If current buffer is less than the min size and a Content-Length isn't set, then wait
- if len(proxy.buf) < MinSize && contentLength == 0 {
- return len(b), nil
- }
-
- // If the Content-Length is larger than minSize or the current buffer is larger than minSize, then continue.
- if contentLength >= MinSize || len(proxy.buf) >= MinSize {
- // if we don't know the content type, infer it
- if contentType == "" {
- contentType = http.DetectContentType(proxy.buf)
- proxy.Header().Set(contentTypeHeader, contentType)
- }
- // If the Content-Type is not compressed - Compress!
- if !compressedContentType(contentType) {
- if err := proxy.startGzip(); err != nil {
- return 0, err
- }
- return len(b), nil
- }
- }
- }
- // If we got here, we should not GZIP this response.
- if err := proxy.startPlain(); err != nil {
- return 0, err
- }
- return len(b), nil
-}
-
-func (proxy *ProxyResponseWriter) startGzip() error {
- // Set the content-encoding and vary headers.
- proxy.Header().Set(contentEncodingHeader, "gzip")
- proxy.Header().Set(varyHeader, acceptEncodingHeader)
-
- // if the Content-Length is already set, then calls to Write on gzip
- // will fail to set the Content-Length header since its already set
- // See: https://github.com/golang/go/issues/14975.
- proxy.Header().Del(contentLengthHeader)
-
- // Write the header to gzip response.
- if proxy.code != 0 {
- proxy.internal.WriteHeader(proxy.code)
- // Ensure that no other WriteHeader's happen
- proxy.code = 0
- }
-
- // Initialize and flush the buffer into the gzip response if there are any bytes.
- // If there aren't any, we shouldn't initialize it yet because on Close it will
- // write the gzip header even if nothing was ever written.
- if len(proxy.buf) > 0 {
- // Initialize the GZIP response.
- proxy.writer = writerPool.Get(proxy.internal)
-
- return proxy.writeBuf()
- }
- return nil
-}
-
-func (proxy *ProxyResponseWriter) startPlain() error {
- if proxy.code != 0 {
- proxy.internal.WriteHeader(proxy.code)
- proxy.code = 0
- }
- proxy.stopped = true
- proxy.writer = noopCloser{proxy.internal}
- return proxy.writeBuf()
-}
-
-func (proxy *ProxyResponseWriter) writeBuf() error {
- if proxy.buf == nil {
- return nil
- }
-
- n, err := proxy.writer.Write(proxy.buf)
-
- // This should never happen (per io.Writer docs), but if the write didn't
- // accept the entire buffer but returned no specific error, we have no clue
- // what's going on, so abort just to be safe.
- if err == nil && n < len(proxy.buf) {
- err = io.ErrShortWrite
- }
- proxy.buf = nil
- return err
-}
-
-// WriteHeader will ensure that we have setup the writer before we write the header
-func (proxy *ProxyResponseWriter) WriteHeader(code int) {
- if proxy.code == 0 {
- proxy.code = code
- }
-}
-
-// Close the writer
-func (proxy *ProxyResponseWriter) Close() error {
- if proxy.stopped {
- return nil
- }
-
- if proxy.writer == nil {
- err := proxy.startPlain()
- if err != nil {
- return fmt.Errorf("GzipMiddleware: write to regular responseWriter at close gets error: %q", err.Error())
- }
- }
-
- err := proxy.writer.Close()
-
- if poolWriter, ok := proxy.writer.(*gzip.Writer); ok {
- writerPool.Put(poolWriter)
- }
-
- proxy.writer = nil
- proxy.stopped = true
- return err
-}
-
-// Flush the writer
-func (proxy *ProxyResponseWriter) Flush() {
- if proxy.writer == nil {
- return
- }
-
- if gw, ok := proxy.writer.(*gzip.Writer); ok {
- gw.Flush()
- }
-
- proxy.internal.Flush()
-}
-
-// Push implements http.Pusher for HTTP/2 Push purposes
-func (proxy *ProxyResponseWriter) Push(target string, opts *http.PushOptions) error {
- pusher, ok := proxy.internal.(http.Pusher)
- if !ok {
- return errors.New("the ResponseWriter doesn't support the Pusher interface")
- }
- return pusher.Push(target, opts)
-}
-
-// Hijack implements http.Hijacker. If the underlying ResponseWriter is a
-// Hijacker, its Hijack method is returned. Otherwise an error is returned.
-func (proxy *ProxyResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
- hijacker, ok := proxy.internal.(http.Hijacker)
- if !ok {
- return nil, nil, fmt.Errorf("the ResponseWriter doesn't support the Hijacker interface")
- }
- return hijacker.Hijack()
-}
-
-// verify Hijacker interface implementation
-var _ http.Hijacker = &ProxyResponseWriter{}
-
-func compressedContentType(contentType string) bool {
- switch contentType {
- case "application/zip":
- return true
- case "application/x-gzip":
- return true
- case "application/gzip":
- return true
- default:
- return false
- }
-}
+++ /dev/null
-kind: pipeline
-name: golang-1-14
-
-steps:
-- name: test
- image: golang:1.14
- environment:
- GOPROXY: https://goproxy.cn
- commands:
- - go build -v
- - go test -v -race -coverprofile=coverage.txt -covermode=atomic
-
----
-kind: pipeline
-name: golang-1-15
-
-steps:
-- name: test
- image: golang:1.15
- environment:
- GOPROXY: https://goproxy.cn
- commands:
- - go build -v
- - go test -v -race -coverprofile=coverage.txt -covermode=atomic
\ No newline at end of file
+++ /dev/null
-Apache License
-Version 2.0, January 2004
-http://www.apache.org/licenses/
-
-TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-1. Definitions.
-
-"License" shall mean the terms and conditions for use, reproduction, and
-distribution as defined by Sections 1 through 9 of this document.
-
-"Licensor" shall mean the copyright owner or entity authorized by the copyright
-owner that is granting the License.
-
-"Legal Entity" shall mean the union of the acting entity and all other entities
-that control, are controlled by, or are under common control with that entity.
-For the purposes of this definition, "control" means (i) the power, direct or
-indirect, to cause the direction or management of such entity, whether by
-contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
-outstanding shares, or (iii) beneficial ownership of such entity.
-
-"You" (or "Your") shall mean an individual or Legal Entity exercising
-permissions granted by this License.
-
-"Source" form shall mean the preferred form for making modifications, including
-but not limited to software source code, documentation source, and configuration
-files.
-
-"Object" form shall mean any form resulting from mechanical transformation or
-translation of a Source form, including but not limited to compiled object code,
-generated documentation, and conversions to other media types.
-
-"Work" shall mean the work of authorship, whether in Source or Object form, made
-available under the License, as indicated by a copyright notice that is included
-in or attached to the work (an example is provided in the Appendix below).
-
-"Derivative Works" shall mean any work, whether in Source or Object form, that
-is based on (or derived from) the Work and for which the editorial revisions,
-annotations, elaborations, or other modifications represent, as a whole, an
-original work of authorship. For the purposes of this License, Derivative Works
-shall not include works that remain separable from, or merely link (or bind by
-name) to the interfaces of, the Work and Derivative Works thereof.
-
-"Contribution" shall mean any work of authorship, including the original version
-of the Work and any modifications or additions to that Work or Derivative Works
-thereof, that is intentionally submitted to Licensor for inclusion in the Work
-by the copyright owner or by an individual or Legal Entity authorized to submit
-on behalf of the copyright owner. For the purposes of this definition,
-"submitted" means any form of electronic, verbal, or written communication sent
-to the Licensor or its representatives, including but not limited to
-communication on electronic mailing lists, source code control systems, and
-issue tracking systems that are managed by, or on behalf of, the Licensor for
-the purpose of discussing and improving the Work, but excluding communication
-that is conspicuously marked or otherwise designated in writing by the copyright
-owner as "Not a Contribution."
-
-"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
-of whom a Contribution has been received by Licensor and subsequently
-incorporated within the Work.
-
-2. Grant of Copyright License.
-
-Subject to the terms and conditions of this License, each Contributor hereby
-grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
-irrevocable copyright license to reproduce, prepare Derivative Works of,
-publicly display, publicly perform, sublicense, and distribute the Work and such
-Derivative Works in Source or Object form.
-
-3. Grant of Patent License.
-
-Subject to the terms and conditions of this License, each Contributor hereby
-grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
-irrevocable (except as stated in this section) patent license to make, have
-made, use, offer to sell, sell, import, and otherwise transfer the Work, where
-such license applies only to those patent claims licensable by such Contributor
-that are necessarily infringed by their Contribution(s) alone or by combination
-of their Contribution(s) with the Work to which such Contribution(s) was
-submitted. If You institute patent litigation against any entity (including a
-cross-claim or counterclaim in a lawsuit) alleging that the Work or a
-Contribution incorporated within the Work constitutes direct or contributory
-patent infringement, then any patent licenses granted to You under this License
-for that Work shall terminate as of the date such litigation is filed.
-
-4. Redistribution.
-
-You may reproduce and distribute copies of the Work or Derivative Works thereof
-in any medium, with or without modifications, and in Source or Object form,
-provided that You meet the following conditions:
-
-You must give any other recipients of the Work or Derivative Works a copy of
-this License; and
-You must cause any modified files to carry prominent notices stating that You
-changed the files; and
-You must retain, in the Source form of any Derivative Works that You distribute,
-all copyright, patent, trademark, and attribution notices from the Source form
-of the Work, excluding those notices that do not pertain to any part of the
-Derivative Works; and
-If the Work includes a "NOTICE" text file as part of its distribution, then any
-Derivative Works that You distribute must include a readable copy of the
-attribution notices contained within such NOTICE file, excluding those notices
-that do not pertain to any part of the Derivative Works, in at least one of the
-following places: within a NOTICE text file distributed as part of the
-Derivative Works; within the Source form or documentation, if provided along
-with the Derivative Works; or, within a display generated by the Derivative
-Works, if and wherever such third-party notices normally appear. The contents of
-the NOTICE file are for informational purposes only and do not modify the
-License. You may add Your own attribution notices within Derivative Works that
-You distribute, alongside or as an addendum to the NOTICE text from the Work,
-provided that such additional attribution notices cannot be construed as
-modifying the License.
-You may add Your own copyright statement to Your modifications and may provide
-additional or different license terms and conditions for use, reproduction, or
-distribution of Your modifications, or for any such Derivative Works as a whole,
-provided Your use, reproduction, and distribution of the Work otherwise complies
-with the conditions stated in this License.
-
-5. Submission of Contributions.
-
-Unless You explicitly state otherwise, any Contribution intentionally submitted
-for inclusion in the Work by You to the Licensor shall be under the terms and
-conditions of this License, without any additional terms or conditions.
-Notwithstanding the above, nothing herein shall supersede or modify the terms of
-any separate license agreement you may have executed with Licensor regarding
-such Contributions.
-
-6. Trademarks.
-
-This License does not grant permission to use the trade names, trademarks,
-service marks, or product names of the Licensor, except as required for
-reasonable and customary use in describing the origin of the Work and
-reproducing the content of the NOTICE file.
-
-7. Disclaimer of Warranty.
-
-Unless required by applicable law or agreed to in writing, Licensor provides the
-Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
-including, without limitation, any warranties or conditions of TITLE,
-NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
-solely responsible for determining the appropriateness of using or
-redistributing the Work and assume any risks associated with Your exercise of
-permissions under this License.
-
-8. Limitation of Liability.
-
-In no event and under no legal theory, whether in tort (including negligence),
-contract, or otherwise, unless required by applicable law (such as deliberate
-and grossly negligent acts) or agreed to in writing, shall any Contributor be
-liable to You for damages, including any direct, indirect, special, incidental,
-or consequential damages of any character arising as a result of this License or
-out of the use or inability to use the Work (including but not limited to
-damages for loss of goodwill, work stoppage, computer failure or malfunction, or
-any and all other commercial damages or losses), even if such Contributor has
-been advised of the possibility of such damages.
-
-9. Accepting Warranty or Additional Liability.
-
-While redistributing the Work or Derivative Works thereof, You may choose to
-offer, and charge a fee for, acceptance of support, warranty, indemnity, or
-other liability obligations and/or rights consistent with this License. However,
-in accepting such obligations, You may act only on Your own behalf and on Your
-sole responsibility, not on behalf of any other Contributor, and only if You
-agree to indemnify, defend, and hold each Contributor harmless for any liability
-incurred by, or claims asserted against, such Contributor by reason of your
-accepting any such warranty or additional liability.
-
-END OF TERMS AND CONDITIONS
-
-APPENDIX: How to apply the Apache License to your work
-
-To apply the Apache License to your work, attach the following boilerplate
-notice, with the fields enclosed by brackets "[]" replaced with your own
-identifying information. (Don't include the brackets!) The text should be
-enclosed in the appropriate comment syntax for the file format. We also
-recommend that a file or class name and description of purpose be included on
-the same "printed page" as the copyright notice for easier identification within
-third-party archives.
-
- Copyright [yyyy] [name of copyright owner]
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
\ No newline at end of file
+++ /dev/null
-# i18n [![Build Status](https://drone.gitea.com/api/badges/macaron/i18n/status.svg)](https://drone.gitea.com/macaron/i18n) [![](http://gocover.io/_badge/github.com/go-macaron/i18n)](http://gocover.io/github.com/go-macaron/i18n)
-
-Middleware i18n provides app Internationalization and Localization for [Macaron](https://gitea.com/macaron/macaron).
-
-### Installation
-
- go get gitea.com/macaron/i18n
-
-## Getting Help
-
-- [API Reference](https://gowalker.org/gitea.com/macaron/i18n)
-- [Documentation](http://go-macaron.com/docs/middlewares/i18n)
-
-## License
-
-This project is under the Apache License, Version 2.0. See the [LICENSE](LICENSE) file for the full license text.
\ No newline at end of file
+++ /dev/null
-module gitea.com/macaron/i18n
-
-go 1.11
-
-require (
- gitea.com/macaron/macaron v1.5.0
- github.com/stretchr/testify v1.4.0
- github.com/unknwon/i18n v0.0.0-20190805065654-5c6446a380b6
- golang.org/x/text v0.3.2
-)
+++ /dev/null
-gitea.com/macaron/inject v0.0.0-20190805023432-d4c86e31027a h1:aOKEXkDTnh4euoH0so/THLXeHtQuqHmDPb1xEk6Ehok=
-gitea.com/macaron/inject v0.0.0-20190805023432-d4c86e31027a/go.mod h1:h6E4kLao1Yko6DOU6QDnQPcuoNzvbZqzj2mtPcEn1aM=
-gitea.com/macaron/macaron v1.5.0 h1:TvWEcHw1/zaHlo0GTuKEukLh3A99+QsU2mjBrXLXjVQ=
-gitea.com/macaron/macaron v1.5.0/go.mod h1:P7hfDbQjcW22lkYkXlxdRIfWOXxH2+K4EogN4Q0UlLY=
-github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
-github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
-github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
-github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
-github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
-github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
-github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY=
-github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
-github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w=
-github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
-github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
-github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8=
-github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
-github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
-github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
-github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e h1:GSGeB9EAKY2spCABz6xOX5DbxZEXolK+nBSvmsQwRjM=
-github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
-github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs=
-github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
-github.com/unknwon/i18n v0.0.0-20190805065654-5c6446a380b6 h1:sRrkJEHtNoaSvyXMbRgofEOX4/3gMiraevQKJdIBhYE=
-github.com/unknwon/i18n v0.0.0-20190805065654-5c6446a380b6/go.mod h1:+5rDk6sDGpl3azws3O+f+GpFSyN9GVr0K8cvQLQM2ZQ=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
-golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
-golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/ini.v1 v1.44.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/ini.v1 v1.46.0 h1:VeDZbLYGaupuvIrsYCEOe/L/2Pcs5n7hdO1ZTjporag=
-gopkg.in/ini.v1 v1.46.0 h1:VeDZbLYGaupuvIrsYCEOe/L/2Pcs5n7hdO1ZTjporag=
-gopkg.in/ini.v1 v1.46.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/ini.v1 v1.46.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
-gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+++ /dev/null
-// Copyright 2014 The Macaron Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-// Package i18n provides an Internationalization and Localization middleware for Macaron applications.
-package i18n
-
-import (
- "fmt"
- "log"
- "os"
- "path"
- "strings"
-
- "gitea.com/macaron/macaron"
- "github.com/unknwon/i18n"
- "golang.org/x/text/language"
-)
-
-const _VERSION = "0.4.0"
-
-func Version() string {
- return _VERSION
-}
-
-// isFile returns true if given path is a file,
-// or returns false when it's a directory or does not exist.
-func isFile(filePath string) bool {
- f, e := os.Stat(filePath)
- if e != nil {
- return false
- }
- return !f.IsDir()
-}
-
-// initLocales initializes language type list and Accept-Language header matcher.
-func initLocales(opt Options) language.Matcher {
- tags := make([]language.Tag, len(opt.Langs))
- for i, lang := range opt.Langs {
- tags[i] = language.Raw.Make(lang)
- fname := fmt.Sprintf(opt.Format, lang)
- // Append custom locale file.
- custom := []interface{}{}
- customPath := path.Join(opt.CustomDirectory, fname)
- if isFile(customPath) {
- custom = append(custom, customPath)
- }
-
- var locale interface{}
- if data, ok := opt.Files[fname]; ok {
- locale = data
- } else {
- locale = path.Join(opt.Directory, fname)
- }
-
- err := i18n.SetMessageWithDesc(lang, opt.Names[i], locale, custom...)
- if err != nil && err != i18n.ErrLangAlreadyExist {
- log.Printf("ERROR: failed to set message file(%s) for language %s: %v", lang, opt.Names[i], err)
- }
- }
- return language.NewMatcher(tags)
-}
-
-// A Locale describles the information of localization.
-type Locale struct {
- i18n.Locale
-}
-
-// Language returns language current locale represents.
-func (l Locale) Language() string {
- return l.Lang
-}
-
-// Options represents a struct for specifying configuration options for the i18n middleware.
-type Options struct {
- // Suburl of path. Default is empty.
- SubURL string
- // Directory to load locale files. Default is "conf/locale"
- Directory string
- // File stores actual data of locale files. Used for in-memory purpose.
- Files map[string][]byte
- // Custom directory to overload locale files. Default is "custom/conf/locale"
- CustomDirectory string
- // Langauges that will be supported, order is meaningful.
- Langs []string
- // Human friendly names corresponding to Langs list.
- Names []string
- // Default language locale, leave empty to remain unset.
- DefaultLang string
- // Locale file naming style. Default is "locale_%s.ini".
- Format string
- // Name of language parameter name in URL. Default is "lang".
- Parameter string
- // Redirect when user uses get parameter to specify language.
- Redirect bool
- // Name that maps into template variable. Default is "i18n".
- TmplName string
- // Configuration section name. Default is "i18n".
- Section string
- // Domain used for `lang` cookie. Default is ""
- CookieDomain string
- // Set the Secure flag on the `lang` cookie. Default is disabled.
- Secure bool
- // Set the HTTP Only flag on the `lang` cookie. Default is disabled.
- CookieHttpOnly bool
-}
-
-func prepareOptions(options []Options) Options {
- var opt Options
- if len(options) > 0 {
- opt = options[0]
- }
-
- if len(opt.Section) == 0 {
- opt.Section = "i18n"
- }
- sec := macaron.Config().Section(opt.Section)
-
- opt.SubURL = strings.TrimSuffix(opt.SubURL, "/")
-
- if len(opt.Langs) == 0 {
- opt.Langs = sec.Key("LANGS").Strings(",")
- }
- if len(opt.Names) == 0 {
- opt.Names = sec.Key("NAMES").Strings(",")
- }
- if len(opt.Langs) == 0 {
- panic("no language is specified")
- } else if len(opt.Langs) != len(opt.Names) {
- panic("length of langs is not same as length of names")
- }
- i18n.SetDefaultLang(opt.DefaultLang)
-
- if len(opt.Directory) == 0 {
- opt.Directory = sec.Key("DIRECTORY").MustString("conf/locale")
- }
- if len(opt.CustomDirectory) == 0 {
- opt.CustomDirectory = sec.Key("CUSTOM_DIRECTORY").MustString("custom/conf/locale")
- }
- if len(opt.Format) == 0 {
- opt.Format = sec.Key("FORMAT").MustString("locale_%s.ini")
- }
- if len(opt.Parameter) == 0 {
- opt.Parameter = sec.Key("PARAMETER").MustString("lang")
- }
- if !opt.Redirect {
- opt.Redirect = sec.Key("REDIRECT").MustBool()
- }
- if len(opt.TmplName) == 0 {
- opt.TmplName = sec.Key("TMPL_NAME").MustString("i18n")
- }
-
- return opt
-}
-
-type LangType struct {
- Lang, Name string
-}
-
-// I18n is a middleware provides localization layer for your application.
-// Paramenter langs must be in the form of "en-US", "zh-CN", etc.
-// Otherwise it may not recognize browser input.
-func I18n(options ...Options) macaron.Handler {
- opt := prepareOptions(options)
- m := initLocales(opt)
- return func(ctx *macaron.Context) {
- isNeedRedir := false
- hasCookie := false
-
- // 1. Check URL arguments.
- lang := ctx.Query(opt.Parameter)
-
- // 2. Get language information from cookies.
- if len(lang) == 0 {
- lang = ctx.GetCookie("lang")
- hasCookie = true
- } else {
- isNeedRedir = true
- }
-
- // Check again in case someone modify by purpose.
- if !i18n.IsExist(lang) {
- lang = ""
- isNeedRedir = false
- hasCookie = false
- }
-
- // 3. Get language information from 'Accept-Language'.
- // The first element in the list is chosen to be the default language automatically.
- if len(lang) == 0 {
- tags, _, _ := language.ParseAcceptLanguage(ctx.Req.Header.Get("Accept-Language"))
- tag, _, _ := m.Match(tags...)
- lang = tag.String()
- isNeedRedir = false
- }
-
- curLang := LangType{
- Lang: lang,
- }
-
- // Save language information in cookies.
- if !hasCookie {
- ctx.SetCookie("lang", curLang.Lang, 1<<31-1, "/"+strings.TrimPrefix(opt.SubURL, "/"), opt.CookieDomain, opt.Secure, opt.CookieHttpOnly)
- }
-
- restLangs := make([]LangType, 0, i18n.Count()-1)
- langs := i18n.ListLangs()
- names := i18n.ListLangDescs()
- for i, v := range langs {
- if lang != v {
- restLangs = append(restLangs, LangType{v, names[i]})
- } else {
- curLang.Name = names[i]
- }
- }
-
- // Set language properties.
- locale := Locale{Locale: i18n.Locale{Lang: lang}}
- ctx.Map(locale)
- ctx.Locale = locale
- ctx.Data[opt.TmplName] = locale
- ctx.Data["Tr"] = i18n.Tr
- ctx.Data["Lang"] = locale.Lang
- ctx.Data["LangName"] = curLang.Name
- ctx.Data["AllLangs"] = append([]LangType{curLang}, restLangs...)
- ctx.Data["RestLangs"] = restLangs
-
- if opt.Redirect && isNeedRedir {
- ctx.Redirect(opt.SubURL + path.Clean(ctx.Req.RequestURI[:strings.Index(ctx.Req.RequestURI, "?")]))
- }
- }
-}
+++ /dev/null
-kind: pipeline
-name: go1-1-1
-
-steps:
-- name: test
- image: golang:1.11
- commands:
- - go build -v
- - go test -v -race -coverprofile=coverage.txt -covermode=atomic
-
----
-kind: pipeline
-name: go1-1-2
-
-steps:
-- name: test
- image: golang:1.12
- commands:
- - go build -v
- - go test -v -race -coverprofile=coverage.txt -covermode=atomic
+++ /dev/null
-Apache License
-Version 2.0, January 2004
-http://www.apache.org/licenses/
-
-TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-1. Definitions.
-
-"License" shall mean the terms and conditions for use, reproduction, and
-distribution as defined by Sections 1 through 9 of this document.
-
-"Licensor" shall mean the copyright owner or entity authorized by the copyright
-owner that is granting the License.
-
-"Legal Entity" shall mean the union of the acting entity and all other entities
-that control, are controlled by, or are under common control with that entity.
-For the purposes of this definition, "control" means (i) the power, direct or
-indirect, to cause the direction or management of such entity, whether by
-contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
-outstanding shares, or (iii) beneficial ownership of such entity.
-
-"You" (or "Your") shall mean an individual or Legal Entity exercising
-permissions granted by this License.
-
-"Source" form shall mean the preferred form for making modifications, including
-but not limited to software source code, documentation source, and configuration
-files.
-
-"Object" form shall mean any form resulting from mechanical transformation or
-translation of a Source form, including but not limited to compiled object code,
-generated documentation, and conversions to other media types.
-
-"Work" shall mean the work of authorship, whether in Source or Object form, made
-available under the License, as indicated by a copyright notice that is included
-in or attached to the work (an example is provided in the Appendix below).
-
-"Derivative Works" shall mean any work, whether in Source or Object form, that
-is based on (or derived from) the Work and for which the editorial revisions,
-annotations, elaborations, or other modifications represent, as a whole, an
-original work of authorship. For the purposes of this License, Derivative Works
-shall not include works that remain separable from, or merely link (or bind by
-name) to the interfaces of, the Work and Derivative Works thereof.
-
-"Contribution" shall mean any work of authorship, including the original version
-of the Work and any modifications or additions to that Work or Derivative Works
-thereof, that is intentionally submitted to Licensor for inclusion in the Work
-by the copyright owner or by an individual or Legal Entity authorized to submit
-on behalf of the copyright owner. For the purposes of this definition,
-"submitted" means any form of electronic, verbal, or written communication sent
-to the Licensor or its representatives, including but not limited to
-communication on electronic mailing lists, source code control systems, and
-issue tracking systems that are managed by, or on behalf of, the Licensor for
-the purpose of discussing and improving the Work, but excluding communication
-that is conspicuously marked or otherwise designated in writing by the copyright
-owner as "Not a Contribution."
-
-"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
-of whom a Contribution has been received by Licensor and subsequently
-incorporated within the Work.
-
-2. Grant of Copyright License.
-
-Subject to the terms and conditions of this License, each Contributor hereby
-grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
-irrevocable copyright license to reproduce, prepare Derivative Works of,
-publicly display, publicly perform, sublicense, and distribute the Work and such
-Derivative Works in Source or Object form.
-
-3. Grant of Patent License.
-
-Subject to the terms and conditions of this License, each Contributor hereby
-grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
-irrevocable (except as stated in this section) patent license to make, have
-made, use, offer to sell, sell, import, and otherwise transfer the Work, where
-such license applies only to those patent claims licensable by such Contributor
-that are necessarily infringed by their Contribution(s) alone or by combination
-of their Contribution(s) with the Work to which such Contribution(s) was
-submitted. If You institute patent litigation against any entity (including a
-cross-claim or counterclaim in a lawsuit) alleging that the Work or a
-Contribution incorporated within the Work constitutes direct or contributory
-patent infringement, then any patent licenses granted to You under this License
-for that Work shall terminate as of the date such litigation is filed.
-
-4. Redistribution.
-
-You may reproduce and distribute copies of the Work or Derivative Works thereof
-in any medium, with or without modifications, and in Source or Object form,
-provided that You meet the following conditions:
-
-You must give any other recipients of the Work or Derivative Works a copy of
-this License; and
-You must cause any modified files to carry prominent notices stating that You
-changed the files; and
-You must retain, in the Source form of any Derivative Works that You distribute,
-all copyright, patent, trademark, and attribution notices from the Source form
-of the Work, excluding those notices that do not pertain to any part of the
-Derivative Works; and
-If the Work includes a "NOTICE" text file as part of its distribution, then any
-Derivative Works that You distribute must include a readable copy of the
-attribution notices contained within such NOTICE file, excluding those notices
-that do not pertain to any part of the Derivative Works, in at least one of the
-following places: within a NOTICE text file distributed as part of the
-Derivative Works; within the Source form or documentation, if provided along
-with the Derivative Works; or, within a display generated by the Derivative
-Works, if and wherever such third-party notices normally appear. The contents of
-the NOTICE file are for informational purposes only and do not modify the
-License. You may add Your own attribution notices within Derivative Works that
-You distribute, alongside or as an addendum to the NOTICE text from the Work,
-provided that such additional attribution notices cannot be construed as
-modifying the License.
-You may add Your own copyright statement to Your modifications and may provide
-additional or different license terms and conditions for use, reproduction, or
-distribution of Your modifications, or for any such Derivative Works as a whole,
-provided Your use, reproduction, and distribution of the Work otherwise complies
-with the conditions stated in this License.
-
-5. Submission of Contributions.
-
-Unless You explicitly state otherwise, any Contribution intentionally submitted
-for inclusion in the Work by You to the Licensor shall be under the terms and
-conditions of this License, without any additional terms or conditions.
-Notwithstanding the above, nothing herein shall supersede or modify the terms of
-any separate license agreement you may have executed with Licensor regarding
-such Contributions.
-
-6. Trademarks.
-
-This License does not grant permission to use the trade names, trademarks,
-service marks, or product names of the Licensor, except as required for
-reasonable and customary use in describing the origin of the Work and
-reproducing the content of the NOTICE file.
-
-7. Disclaimer of Warranty.
-
-Unless required by applicable law or agreed to in writing, Licensor provides the
-Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
-including, without limitation, any warranties or conditions of TITLE,
-NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
-solely responsible for determining the appropriateness of using or
-redistributing the Work and assume any risks associated with Your exercise of
-permissions under this License.
-
-8. Limitation of Liability.
-
-In no event and under no legal theory, whether in tort (including negligence),
-contract, or otherwise, unless required by applicable law (such as deliberate
-and grossly negligent acts) or agreed to in writing, shall any Contributor be
-liable to You for damages, including any direct, indirect, special, incidental,
-or consequential damages of any character arising as a result of this License or
-out of the use or inability to use the Work (including but not limited to
-damages for loss of goodwill, work stoppage, computer failure or malfunction, or
-any and all other commercial damages or losses), even if such Contributor has
-been advised of the possibility of such damages.
-
-9. Accepting Warranty or Additional Liability.
-
-While redistributing the Work or Derivative Works thereof, You may choose to
-offer, and charge a fee for, acceptance of support, warranty, indemnity, or
-other liability obligations and/or rights consistent with this License. However,
-in accepting such obligations, You may act only on Your own behalf and on Your
-sole responsibility, not on behalf of any other Contributor, and only if You
-agree to indemnify, defend, and hold each Contributor harmless for any liability
-incurred by, or claims asserted against, such Contributor by reason of your
-accepting any such warranty or additional liability.
-
-END OF TERMS AND CONDITIONS
-
-APPENDIX: How to apply the Apache License to your work
-
-To apply the Apache License to your work, attach the following boilerplate
-notice, with the fields enclosed by brackets "[]" replaced with your own
-identifying information. (Don't include the brackets!) The text should be
-enclosed in the appropriate comment syntax for the file format. We also
-recommend that a file or class name and description of purpose be included on
-the same "printed page" as the copyright notice for easier identification within
-third-party archives.
-
- Copyright [yyyy] [name of copyright owner]
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
+++ /dev/null
-# inject [![Build Status](https://drone.gitea.com/api/badges/macaron/inject/status.svg)](https://drone.gitea.com/macaron/inject) [![](http://gocover.io/_badge/gitea.com/macaron/inject)](http://gocover.io/gitea.com/macaron/inject)
-
-Package inject provides utilities for mapping and injecting dependencies in various ways.
-
-**This a modified version of [codegangsta/inject](https://github.com/codegangsta/inject) for special purpose of Macaron**
-
-**Please use the original version if you need dependency injection feature**
-
-## License
-
-This project is under the Apache License, Version 2.0. See the [LICENSE](LICENSE) file for the full license text.
\ No newline at end of file
+++ /dev/null
-module gitea.com/macaron/inject
-
-go 1.11
-
-require github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337
+++ /dev/null
-github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
-github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
-github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
-github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
-github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
-github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8=
-github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+++ /dev/null
-// Copyright 2013 Jeremy Saenz
-// Copyright 2015 The Macaron Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-// Package inject provides utilities for mapping and injecting dependencies in various ways.
-package inject
-
-import (
- "fmt"
- "reflect"
-)
-
-// Injector represents an interface for mapping and injecting dependencies into structs
-// and function arguments.
-type Injector interface {
- Applicator
- Invoker
- TypeMapper
- // SetParent sets the parent of the injector. If the injector cannot find a
- // dependency in its Type map it will check its parent before returning an
- // error.
- SetParent(Injector)
-}
-
-// Applicator represents an interface for mapping dependencies to a struct.
-type Applicator interface {
- // Maps dependencies in the Type map to each field in the struct
- // that is tagged with 'inject'. Returns an error if the injection
- // fails.
- Apply(interface{}) error
-}
-
-// Invoker represents an interface for calling functions via reflection.
-type Invoker interface {
- // Invoke attempts to call the interface{} provided as a function,
- // providing dependencies for function arguments based on Type. Returns
- // a slice of reflect.Value representing the returned values of the function.
- // Returns an error if the injection fails.
- Invoke(interface{}) ([]reflect.Value, error)
-}
-
-// FastInvoker represents an interface in order to avoid the calling function via reflection.
-//
-// example:
-// type handlerFuncHandler func(http.ResponseWriter, *http.Request) error
-// func (f handlerFuncHandler)Invoke([]interface{}) ([]reflect.Value, error){
-// ret := f(p[0].(http.ResponseWriter), p[1].(*http.Request))
-// return []reflect.Value{reflect.ValueOf(ret)}, nil
-// }
-//
-// type funcHandler func(int, string)
-// func (f funcHandler)Invoke([]interface{}) ([]reflect.Value, error){
-// f(p[0].(int), p[1].(string))
-// return nil, nil
-// }
-type FastInvoker interface {
- // Invoke attempts to call the ordinary functions. If f is a function
- // with the appropriate signature, f.Invoke([]interface{}) is a Call that calls f.
- // Returns a slice of reflect.Value representing the returned values of the function.
- // Returns an error if the injection fails.
- Invoke([]interface{}) ([]reflect.Value, error)
-}
-
-// IsFastInvoker check interface is FastInvoker
-func IsFastInvoker(h interface{}) bool {
- _, ok := h.(FastInvoker)
- return ok
-}
-
-// TypeMapper represents an interface for mapping interface{} values based on type.
-type TypeMapper interface {
- // Maps the interface{} value based on its immediate type from reflect.TypeOf.
- Map(interface{}) TypeMapper
- // Maps the interface{} value based on the pointer of an Interface provided.
- // This is really only useful for mapping a value as an interface, as interfaces
- // cannot at this time be referenced directly without a pointer.
- MapTo(interface{}, interface{}) TypeMapper
- // Provides a possibility to directly insert a mapping based on type and value.
- // This makes it possible to directly map type arguments not possible to instantiate
- // with reflect like unidirectional channels.
- Set(reflect.Type, reflect.Value) TypeMapper
- // Returns the Value that is mapped to the current type. Returns a zeroed Value if
- // the Type has not been mapped.
- GetVal(reflect.Type) reflect.Value
-}
-
-type injector struct {
- values map[reflect.Type]reflect.Value
- parent Injector
-}
-
-// InterfaceOf dereferences a pointer to an Interface type.
-// It panics if value is not an pointer to an interface.
-func InterfaceOf(value interface{}) reflect.Type {
- t := reflect.TypeOf(value)
-
- for t.Kind() == reflect.Ptr {
- t = t.Elem()
- }
-
- if t.Kind() != reflect.Interface {
- panic("Called inject.InterfaceOf with a value that is not a pointer to an interface. (*MyInterface)(nil)")
- }
-
- return t
-}
-
-// New returns a new Injector.
-func New() Injector {
- return &injector{
- values: make(map[reflect.Type]reflect.Value),
- }
-}
-
-// Invoke attempts to call the interface{} provided as a function,
-// providing dependencies for function arguments based on Type.
-// Returns a slice of reflect.Value representing the returned values of the function.
-// Returns an error if the injection fails.
-// It panics if f is not a function
-func (inj *injector) Invoke(f interface{}) ([]reflect.Value, error) {
- t := reflect.TypeOf(f)
- switch v := f.(type) {
- case FastInvoker:
- return inj.fastInvoke(v, t, t.NumIn())
- default:
- return inj.callInvoke(f, t, t.NumIn())
- }
-}
-
-func (inj *injector) fastInvoke(f FastInvoker, t reflect.Type, numIn int) ([]reflect.Value, error) {
- var in []interface{}
- if numIn > 0 {
- in = make([]interface{}, numIn) // Panic if t is not kind of Func
- var argType reflect.Type
- var val reflect.Value
- for i := 0; i < numIn; i++ {
- argType = t.In(i)
- val = inj.GetVal(argType)
- if !val.IsValid() {
- return nil, fmt.Errorf("Value not found for type %v", argType)
- }
-
- in[i] = val.Interface()
- }
- }
- return f.Invoke(in)
-}
-
-// callInvoke reflect.Value.Call
-func (inj *injector) callInvoke(f interface{}, t reflect.Type, numIn int) ([]reflect.Value, error) {
- var in []reflect.Value
- if numIn > 0 {
- in = make([]reflect.Value, numIn)
- var argType reflect.Type
- var val reflect.Value
- for i := 0; i < numIn; i++ {
- argType = t.In(i)
- val = inj.GetVal(argType)
- if !val.IsValid() {
- return nil, fmt.Errorf("Value not found for type %v", argType)
- }
-
- in[i] = val
- }
- }
- return reflect.ValueOf(f).Call(in), nil
-}
-
-// Maps dependencies in the Type map to each field in the struct
-// that is tagged with 'inject'.
-// Returns an error if the injection fails.
-func (inj *injector) Apply(val interface{}) error {
- v := reflect.ValueOf(val)
-
- for v.Kind() == reflect.Ptr {
- v = v.Elem()
- }
-
- if v.Kind() != reflect.Struct {
- return nil // Should not panic here ?
- }
-
- t := v.Type()
-
- for i := 0; i < v.NumField(); i++ {
- f := v.Field(i)
- structField := t.Field(i)
- if f.CanSet() && (structField.Tag == "inject" || structField.Tag.Get("inject") != "") {
- ft := f.Type()
- v := inj.GetVal(ft)
- if !v.IsValid() {
- return fmt.Errorf("Value not found for type %v", ft)
- }
-
- f.Set(v)
- }
-
- }
-
- return nil
-}
-
-// Maps the concrete value of val to its dynamic type using reflect.TypeOf,
-// It returns the TypeMapper registered in.
-func (i *injector) Map(val interface{}) TypeMapper {
- i.values[reflect.TypeOf(val)] = reflect.ValueOf(val)
- return i
-}
-
-func (i *injector) MapTo(val interface{}, ifacePtr interface{}) TypeMapper {
- i.values[InterfaceOf(ifacePtr)] = reflect.ValueOf(val)
- return i
-}
-
-// Maps the given reflect.Type to the given reflect.Value and returns
-// the Typemapper the mapping has been registered in.
-func (i *injector) Set(typ reflect.Type, val reflect.Value) TypeMapper {
- i.values[typ] = val
- return i
-}
-
-func (i *injector) GetVal(t reflect.Type) reflect.Value {
- val := i.values[t]
-
- if val.IsValid() {
- return val
- }
-
- // no concrete types found, try to find implementors
- // if t is an interface
- if t.Kind() == reflect.Interface {
- for k, v := range i.values {
- if k.Implements(t) {
- val = v
- break
- }
- }
- }
-
- // Still no type found, try to look it up on the parent
- if !val.IsValid() && i.parent != nil {
- val = i.parent.GetVal(t)
- }
-
- return val
-
-}
-
-func (i *injector) SetParent(parent Injector) {
- i.parent = parent
-}
+++ /dev/null
-kind: pipeline
-name: default
-
-steps:
-- name: test
- image: golang:1.13
- environment:
- GOPROXY: https://goproxy.cn
- commands:
- - go get -u
- - go build -v
- - go test -v -race -coverprofile=coverage.txt -covermode=atomic
+++ /dev/null
-macaron.sublime-project
-macaron.sublime-workspace
-.idea
+++ /dev/null
-Apache License
-Version 2.0, January 2004
-http://www.apache.org/licenses/
-
-TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-1. Definitions.
-
-"License" shall mean the terms and conditions for use, reproduction, and
-distribution as defined by Sections 1 through 9 of this document.
-
-"Licensor" shall mean the copyright owner or entity authorized by the copyright
-owner that is granting the License.
-
-"Legal Entity" shall mean the union of the acting entity and all other entities
-that control, are controlled by, or are under common control with that entity.
-For the purposes of this definition, "control" means (i) the power, direct or
-indirect, to cause the direction or management of such entity, whether by
-contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
-outstanding shares, or (iii) beneficial ownership of such entity.
-
-"You" (or "Your") shall mean an individual or Legal Entity exercising
-permissions granted by this License.
-
-"Source" form shall mean the preferred form for making modifications, including
-but not limited to software source code, documentation source, and configuration
-files.
-
-"Object" form shall mean any form resulting from mechanical transformation or
-translation of a Source form, including but not limited to compiled object code,
-generated documentation, and conversions to other media types.
-
-"Work" shall mean the work of authorship, whether in Source or Object form, made
-available under the License, as indicated by a copyright notice that is included
-in or attached to the work (an example is provided in the Appendix below).
-
-"Derivative Works" shall mean any work, whether in Source or Object form, that
-is based on (or derived from) the Work and for which the editorial revisions,
-annotations, elaborations, or other modifications represent, as a whole, an
-original work of authorship. For the purposes of this License, Derivative Works
-shall not include works that remain separable from, or merely link (or bind by
-name) to the interfaces of, the Work and Derivative Works thereof.
-
-"Contribution" shall mean any work of authorship, including the original version
-of the Work and any modifications or additions to that Work or Derivative Works
-thereof, that is intentionally submitted to Licensor for inclusion in the Work
-by the copyright owner or by an individual or Legal Entity authorized to submit
-on behalf of the copyright owner. For the purposes of this definition,
-"submitted" means any form of electronic, verbal, or written communication sent
-to the Licensor or its representatives, including but not limited to
-communication on electronic mailing lists, source code control systems, and
-issue tracking systems that are managed by, or on behalf of, the Licensor for
-the purpose of discussing and improving the Work, but excluding communication
-that is conspicuously marked or otherwise designated in writing by the copyright
-owner as "Not a Contribution."
-
-"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
-of whom a Contribution has been received by Licensor and subsequently
-incorporated within the Work.
-
-2. Grant of Copyright License.
-
-Subject to the terms and conditions of this License, each Contributor hereby
-grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
-irrevocable copyright license to reproduce, prepare Derivative Works of,
-publicly display, publicly perform, sublicense, and distribute the Work and such
-Derivative Works in Source or Object form.
-
-3. Grant of Patent License.
-
-Subject to the terms and conditions of this License, each Contributor hereby
-grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
-irrevocable (except as stated in this section) patent license to make, have
-made, use, offer to sell, sell, import, and otherwise transfer the Work, where
-such license applies only to those patent claims licensable by such Contributor
-that are necessarily infringed by their Contribution(s) alone or by combination
-of their Contribution(s) with the Work to which such Contribution(s) was
-submitted. If You institute patent litigation against any entity (including a
-cross-claim or counterclaim in a lawsuit) alleging that the Work or a
-Contribution incorporated within the Work constitutes direct or contributory
-patent infringement, then any patent licenses granted to You under this License
-for that Work shall terminate as of the date such litigation is filed.
-
-4. Redistribution.
-
-You may reproduce and distribute copies of the Work or Derivative Works thereof
-in any medium, with or without modifications, and in Source or Object form,
-provided that You meet the following conditions:
-
-You must give any other recipients of the Work or Derivative Works a copy of
-this License; and
-You must cause any modified files to carry prominent notices stating that You
-changed the files; and
-You must retain, in the Source form of any Derivative Works that You distribute,
-all copyright, patent, trademark, and attribution notices from the Source form
-of the Work, excluding those notices that do not pertain to any part of the
-Derivative Works; and
-If the Work includes a "NOTICE" text file as part of its distribution, then any
-Derivative Works that You distribute must include a readable copy of the
-attribution notices contained within such NOTICE file, excluding those notices
-that do not pertain to any part of the Derivative Works, in at least one of the
-following places: within a NOTICE text file distributed as part of the
-Derivative Works; within the Source form or documentation, if provided along
-with the Derivative Works; or, within a display generated by the Derivative
-Works, if and wherever such third-party notices normally appear. The contents of
-the NOTICE file are for informational purposes only and do not modify the
-License. You may add Your own attribution notices within Derivative Works that
-You distribute, alongside or as an addendum to the NOTICE text from the Work,
-provided that such additional attribution notices cannot be construed as
-modifying the License.
-You may add Your own copyright statement to Your modifications and may provide
-additional or different license terms and conditions for use, reproduction, or
-distribution of Your modifications, or for any such Derivative Works as a whole,
-provided Your use, reproduction, and distribution of the Work otherwise complies
-with the conditions stated in this License.
-
-5. Submission of Contributions.
-
-Unless You explicitly state otherwise, any Contribution intentionally submitted
-for inclusion in the Work by You to the Licensor shall be under the terms and
-conditions of this License, without any additional terms or conditions.
-Notwithstanding the above, nothing herein shall supersede or modify the terms of
-any separate license agreement you may have executed with Licensor regarding
-such Contributions.
-
-6. Trademarks.
-
-This License does not grant permission to use the trade names, trademarks,
-service marks, or product names of the Licensor, except as required for
-reasonable and customary use in describing the origin of the Work and
-reproducing the content of the NOTICE file.
-
-7. Disclaimer of Warranty.
-
-Unless required by applicable law or agreed to in writing, Licensor provides the
-Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
-including, without limitation, any warranties or conditions of TITLE,
-NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
-solely responsible for determining the appropriateness of using or
-redistributing the Work and assume any risks associated with Your exercise of
-permissions under this License.
-
-8. Limitation of Liability.
-
-In no event and under no legal theory, whether in tort (including negligence),
-contract, or otherwise, unless required by applicable law (such as deliberate
-and grossly negligent acts) or agreed to in writing, shall any Contributor be
-liable to You for damages, including any direct, indirect, special, incidental,
-or consequential damages of any character arising as a result of this License or
-out of the use or inability to use the Work (including but not limited to
-damages for loss of goodwill, work stoppage, computer failure or malfunction, or
-any and all other commercial damages or losses), even if such Contributor has
-been advised of the possibility of such damages.
-
-9. Accepting Warranty or Additional Liability.
-
-While redistributing the Work or Derivative Works thereof, You may choose to
-offer, and charge a fee for, acceptance of support, warranty, indemnity, or
-other liability obligations and/or rights consistent with this License. However,
-in accepting such obligations, You may act only on Your own behalf and on Your
-sole responsibility, not on behalf of any other Contributor, and only if You
-agree to indemnify, defend, and hold each Contributor harmless for any liability
-incurred by, or claims asserted against, such Contributor by reason of your
-accepting any such warranty or additional liability.
-
-END OF TERMS AND CONDITIONS
-
-APPENDIX: How to apply the Apache License to your work
-
-To apply the Apache License to your work, attach the following boilerplate
-notice, with the fields enclosed by brackets "[]" replaced with your own
-identifying information. (Don't include the brackets!) The text should be
-enclosed in the appropriate comment syntax for the file format. We also
-recommend that a file or class name and description of purpose be included on
-the same "printed page" as the copyright notice for easier identification within
-third-party archives.
-
- Copyright 2014 The Macaron Authors
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
+++ /dev/null
-# Macaron
-
-[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/go-macaron/macaron/Go?logo=github&style=for-the-badge)](https://github.com/go-macaron/macaron/actions?query=workflow%3AGo)
-[![codecov](https://img.shields.io/codecov/c/github/go-macaron/macaron/master?logo=codecov&style=for-the-badge)](https://codecov.io/gh/go-macaron/macaron)
-[![GoDoc](https://img.shields.io/badge/GoDoc-Reference-blue?style=for-the-badge&logo=go)](https://pkg.go.dev/gopkg.in/macaron.v1?tab=doc)
-[![Sourcegraph](https://img.shields.io/badge/view%20on-Sourcegraph-brightgreen.svg?style=for-the-badge&logo=sourcegraph)](https://sourcegraph.com/github.com/go-macaron/macaron)
-
-![Macaron Logo](https://raw.githubusercontent.com/go-macaron/macaron/v1/macaronlogo.png)
-
-Package macaron is a high productive and modular web framework in Go.
-
-## Getting Started
-
-The minimum requirement of Go is **1.6**.
-
-To install Macaron:
-
- go get gitea.com/macaron/macaron
-
-The very basic usage of Macaron:
-
-```go
-package main
-
-import "gitea.com/macaron/macaron"
-
-func main() {
- m := macaron.Classic()
- m.Get("/", func() string {
- return "Hello world!"
- })
- m.Run()
-}
-```
-
-## Features
-
-- Powerful routing with suburl.
-- Flexible routes combinations.
-- Unlimited nested group routers.
-- Directly integrate with existing services.
-- Dynamically change template files at runtime.
-- Allow to use in-memory template and static files.
-- Easy to plugin/unplugin features with modular design.
-- Handy dependency injection powered by [inject](https://github.com/codegangsta/inject).
-- Better router layer and less reflection make faster speed.
-
-## Middlewares
-
-Middlewares allow you easily plugin/unplugin features for your Macaron applications.
-
-There are already many [middlewares](https://github.com/go-macaron) to simplify your work:
-
-- render - Go template engine
-- static - Serves static files
-- [gzip](https://github.com/go-macaron/gzip) - Gzip compression to all responses
-- [binding](https://github.com/go-macaron/binding) - Request data binding and validation
-- [i18n](https://github.com/go-macaron/i18n) - Internationalization and Localization
-- [cache](https://github.com/go-macaron/cache) - Cache manager
-- [session](https://github.com/go-macaron/session) - Session manager
-- [csrf](https://github.com/go-macaron/csrf) - Generates and validates csrf tokens
-- [captcha](https://github.com/go-macaron/captcha) - Captcha service
-- [pongo2](https://github.com/go-macaron/pongo2) - Pongo2 template engine support
-- [sockets](https://github.com/go-macaron/sockets) - WebSockets channels binding
-- [bindata](https://github.com/go-macaron/bindata) - Embed binary data as static and template files
-- [toolbox](https://github.com/go-macaron/toolbox) - Health check, pprof, profile and statistic services
-- [oauth2](https://github.com/go-macaron/oauth2) - OAuth 2.0 backend
-- [authz](https://github.com/go-macaron/authz) - ACL/RBAC/ABAC authorization based on Casbin
-- [switcher](https://github.com/go-macaron/switcher) - Multiple-site support
-- [method](https://github.com/go-macaron/method) - HTTP method override
-- [permissions2](https://github.com/xyproto/permissions2) - Cookies, users and permissions
-- [renders](https://github.com/go-macaron/renders) - Beego-like render engine(Macaron has built-in template engine, this is another option)
-- [piwik](https://github.com/veecue/piwik-middleware) - Server-side piwik analytics
-
-## Use Cases
-
-- [Gogs](https://gogs.io): A painless self-hosted Git Service
-- [Grafana](http://grafana.org/): The open platform for beautiful analytics and monitoring
-- [Peach](https://peachdocs.org): A modern web documentation server
-- [Go Walker](https://gowalker.org): Go online API documentation
-- [Switch](https://gopm.io): Gopm registry
-- [Critical Stack Intel](https://intel.criticalstack.com/): A 100% free intel marketplace from Critical Stack, Inc.
-
-## Getting Help
-
-- [API Reference](https://gowalker.org/gitea.com/macaron/macaron)
-- [Documentation](https://go-macaron.com)
-- [FAQs](https://go-macaron.com/docs/faqs)
-
-## Credits
-
-- Basic design of [Martini](https://github.com/go-martini/martini).
-- Logo is modified by [@insionng](https://github.com/insionng) based on [Tribal Dragon](http://xtremeyamazaki.deviantart.com/art/Tribal-Dragon-27005087).
-
-## License
-
-This project is under the Apache License, Version 2.0. See the [LICENSE](LICENSE) file for the full license text.
+++ /dev/null
-// Copyright 2014 The Macaron Authors
-// Copyright 2020 The Gitea Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package macaron
-
-import (
- "crypto/sha256"
- "encoding/hex"
- "html/template"
- "io"
- "io/ioutil"
- "mime/multipart"
- "net/http"
- "net/url"
- "os"
- "path"
- "path/filepath"
- "reflect"
- "strconv"
- "strings"
- "time"
-
- "gitea.com/macaron/inject"
- "github.com/unknwon/com"
- "golang.org/x/crypto/pbkdf2"
-)
-
-// Locale reprents a localization interface.
-type Locale interface {
- Language() string
- Tr(string, ...interface{}) string
-}
-
-// RequestBody represents a request body.
-type RequestBody struct {
- reader io.ReadCloser
-}
-
-// Bytes reads and returns content of request body in bytes.
-func (rb *RequestBody) Bytes() ([]byte, error) {
- return ioutil.ReadAll(rb.reader)
-}
-
-// String reads and returns content of request body in string.
-func (rb *RequestBody) String() (string, error) {
- data, err := rb.Bytes()
- return string(data), err
-}
-
-// ReadCloser returns a ReadCloser for request body.
-func (rb *RequestBody) ReadCloser() io.ReadCloser {
- return rb.reader
-}
-
-// Request represents an HTTP request received by a server or to be sent by a client.
-type Request struct {
- *http.Request
-}
-
-// Body returns a RequestBody for the request
-func (r *Request) Body() *RequestBody {
- return &RequestBody{r.Request.Body}
-}
-
-// ContextInvoker is an inject.FastInvoker wrapper of func(ctx *Context).
-type ContextInvoker func(ctx *Context)
-
-// Invoke implements inject.FastInvoker - in the context of a func(ctx *Context) this simply calls the function
-func (invoke ContextInvoker) Invoke(params []interface{}) ([]reflect.Value, error) {
- invoke(params[0].(*Context))
- return nil, nil
-}
-
-// Context represents the runtime context of current request of Macaron instance.
-// It is the integration of most frequently used middlewares and helper methods.
-type Context struct {
- inject.Injector
- handlers []Handler
- action Handler
- index int
-
- *Router
- Req Request
- Resp ResponseWriter
- params Params
- Render
- Locale
- Data map[string]interface{}
-}
-
-func (ctx *Context) handler() Handler {
- if ctx.index < len(ctx.handlers) {
- return ctx.handlers[ctx.index]
- }
- if ctx.index == len(ctx.handlers) {
- return ctx.action
- }
- panic("invalid index for context handler")
-}
-
-// Next runs the next handler in the context chain
-func (ctx *Context) Next() {
- ctx.index++
- ctx.run()
-}
-
-// Written returns whether the context response has been written to
-func (ctx *Context) Written() bool {
- return ctx.Resp.Written()
-}
-
-func (ctx *Context) run() {
- for ctx.index <= len(ctx.handlers) {
- vals, err := ctx.Invoke(ctx.handler())
- if err != nil {
- panic(err)
- }
- ctx.index++
-
- // if the handler returned something, write it to the http response
- if len(vals) > 0 {
- ev := ctx.GetVal(reflect.TypeOf(ReturnHandler(nil)))
- handleReturn := ev.Interface().(ReturnHandler)
- handleReturn(ctx, vals)
- }
-
- if ctx.Written() {
- return
- }
- }
-}
-
-// RemoteAddr returns more real IP address.
-func (ctx *Context) RemoteAddr() string {
- addr := ctx.Req.Header.Get("X-Real-IP")
- if len(addr) == 0 {
- addr = ctx.Req.Header.Get("X-Forwarded-For")
- if addr == "" {
- addr = ctx.Req.RemoteAddr
- if i := strings.LastIndex(addr, ":"); i > -1 {
- addr = addr[:i]
- }
- }
- }
- return addr
-}
-
-func (ctx *Context) renderHTML(status int, setName, tplName string, data ...interface{}) {
- if len(data) <= 0 {
- ctx.Render.HTMLSet(status, setName, tplName, ctx.Data)
- } else if len(data) == 1 {
- ctx.Render.HTMLSet(status, setName, tplName, data[0])
- } else {
- ctx.Render.HTMLSet(status, setName, tplName, data[0], data[1].(HTMLOptions))
- }
-}
-
-// HTML renders the HTML with default template set.
-func (ctx *Context) HTML(status int, name string, data ...interface{}) {
- ctx.renderHTML(status, DEFAULT_TPL_SET_NAME, name, data...)
-}
-
-// HTMLSet renders the HTML with given template set name.
-func (ctx *Context) HTMLSet(status int, setName, tplName string, data ...interface{}) {
- ctx.renderHTML(status, setName, tplName, data...)
-}
-
-// Redirect sends a redirect response
-func (ctx *Context) Redirect(location string, status ...int) {
- code := http.StatusFound
- if len(status) == 1 {
- code = status[0]
- }
-
- http.Redirect(ctx.Resp, ctx.Req.Request, location, code)
-}
-
-// MaxMemory is the maximum amount of memory to use when parsing a multipart form.
-// Set this to whatever value you prefer; default is 10 MB.
-var MaxMemory = int64(1024 * 1024 * 10)
-
-func (ctx *Context) parseForm() {
- if ctx.Req.Form != nil {
- return
- }
-
- contentType := ctx.Req.Header.Get(_CONTENT_TYPE)
- if (ctx.Req.Method == "POST" || ctx.Req.Method == "PUT") &&
- len(contentType) > 0 && strings.Contains(contentType, "multipart/form-data") {
- _ = ctx.Req.ParseMultipartForm(MaxMemory)
- } else {
- _ = ctx.Req.ParseForm()
- }
-}
-
-// Query querys form parameter.
-func (ctx *Context) Query(name string) string {
- ctx.parseForm()
- return ctx.Req.Form.Get(name)
-}
-
-// QueryTrim querys and trims spaces form parameter.
-func (ctx *Context) QueryTrim(name string) string {
- return strings.TrimSpace(ctx.Query(name))
-}
-
-// QueryStrings returns a list of results by given query name.
-func (ctx *Context) QueryStrings(name string) []string {
- ctx.parseForm()
-
- vals, ok := ctx.Req.Form[name]
- if !ok {
- return []string{}
- }
- return vals
-}
-
-// QueryEscape returns escapred query result.
-func (ctx *Context) QueryEscape(name string) string {
- return template.HTMLEscapeString(ctx.Query(name))
-}
-
-// QueryBool returns query result in bool type.
-func (ctx *Context) QueryBool(name string) bool {
- v, _ := strconv.ParseBool(ctx.Query(name))
- return v
-}
-
-// QueryInt returns query result in int type.
-func (ctx *Context) QueryInt(name string) int {
- return com.StrTo(ctx.Query(name)).MustInt()
-}
-
-// QueryInt64 returns query result in int64 type.
-func (ctx *Context) QueryInt64(name string) int64 {
- return com.StrTo(ctx.Query(name)).MustInt64()
-}
-
-// QueryFloat64 returns query result in float64 type.
-func (ctx *Context) QueryFloat64(name string) float64 {
- v, _ := strconv.ParseFloat(ctx.Query(name), 64)
- return v
-}
-
-// Params returns value of given param name.
-// e.g. ctx.Params(":uid") or ctx.Params("uid")
-func (ctx *Context) Params(name string) string {
- if len(name) == 0 {
- return ""
- }
- if len(name) > 1 && name[0] != ':' {
- name = ":" + name
- }
- return ctx.params[name]
-}
-
-// AllParams returns all params.
-func (ctx *Context) AllParams() Params {
- return ctx.params
-}
-
-// SetParams sets value of param with given name.
-func (ctx *Context) SetParams(name, val string) {
- if name != "*" && !strings.HasPrefix(name, ":") {
- name = ":" + name
- }
- ctx.params[name] = val
-}
-
-// ReplaceAllParams replace all current params with given params
-func (ctx *Context) ReplaceAllParams(params Params) {
- ctx.params = params
-}
-
-// ParamsEscape returns escapred params result.
-// e.g. ctx.ParamsEscape(":uname")
-func (ctx *Context) ParamsEscape(name string) string {
- return template.HTMLEscapeString(ctx.Params(name))
-}
-
-// ParamsInt returns params result in int type.
-// e.g. ctx.ParamsInt(":uid")
-func (ctx *Context) ParamsInt(name string) int {
- return com.StrTo(ctx.Params(name)).MustInt()
-}
-
-// ParamsInt64 returns params result in int64 type.
-// e.g. ctx.ParamsInt64(":uid")
-func (ctx *Context) ParamsInt64(name string) int64 {
- return com.StrTo(ctx.Params(name)).MustInt64()
-}
-
-// ParamsFloat64 returns params result in int64 type.
-// e.g. ctx.ParamsFloat64(":uid")
-func (ctx *Context) ParamsFloat64(name string) float64 {
- v, _ := strconv.ParseFloat(ctx.Params(name), 64)
- return v
-}
-
-// GetFile returns information about user upload file by given form field name.
-func (ctx *Context) GetFile(name string) (multipart.File, *multipart.FileHeader, error) {
- return ctx.Req.FormFile(name)
-}
-
-// SaveToFile reads a file from request by field name and saves to given path.
-func (ctx *Context) SaveToFile(name, savePath string) error {
- fr, _, err := ctx.GetFile(name)
- if err != nil {
- return err
- }
- defer fr.Close()
-
- fw, err := os.OpenFile(savePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
- if err != nil {
- return err
- }
- defer fw.Close()
-
- _, err = io.Copy(fw, fr)
- return err
-}
-
-// SetCookie sets given cookie value to response header.
-// FIXME: IE support? http://golanghome.com/post/620#reply2
-func (ctx *Context) SetCookie(name string, value string, others ...interface{}) {
- cookie := http.Cookie{}
- cookie.Name = name
- cookie.Value = url.QueryEscape(value)
-
- if len(others) > 0 {
- switch v := others[0].(type) {
- case int:
- cookie.MaxAge = v
- case int64:
- cookie.MaxAge = int(v)
- case int32:
- cookie.MaxAge = int(v)
- case func(*http.Cookie):
- v(&cookie)
- }
- }
-
- cookie.Path = "/"
- if len(others) > 1 {
- if v, ok := others[1].(string); ok && len(v) > 0 {
- cookie.Path = v
- } else if v, ok := others[1].(func(*http.Cookie)); ok {
- v(&cookie)
- }
- }
-
- if len(others) > 2 {
- if v, ok := others[2].(string); ok && len(v) > 0 {
- cookie.Domain = v
- } else if v, ok := others[1].(func(*http.Cookie)); ok {
- v(&cookie)
- }
- }
-
- if len(others) > 3 {
- switch v := others[3].(type) {
- case bool:
- cookie.Secure = v
- case func(*http.Cookie):
- v(&cookie)
- default:
- if others[3] != nil {
- cookie.Secure = true
- }
- }
- }
-
- if len(others) > 4 {
- if v, ok := others[4].(bool); ok && v {
- cookie.HttpOnly = true
- } else if v, ok := others[1].(func(*http.Cookie)); ok {
- v(&cookie)
- }
- }
-
- if len(others) > 5 {
- if v, ok := others[5].(time.Time); ok {
- cookie.Expires = v
- cookie.RawExpires = v.Format(time.UnixDate)
- } else if v, ok := others[1].(func(*http.Cookie)); ok {
- v(&cookie)
- }
- }
-
- if len(others) > 6 {
- for _, other := range others[6:] {
- if v, ok := other.(func(*http.Cookie)); ok {
- v(&cookie)
- }
- }
- }
-
- ctx.Resp.Header().Add("Set-Cookie", cookie.String())
-}
-
-// GetCookie returns given cookie value from request header.
-func (ctx *Context) GetCookie(name string) string {
- cookie, err := ctx.Req.Cookie(name)
- if err != nil {
- return ""
- }
- val, _ := url.QueryUnescape(cookie.Value)
- return val
-}
-
-// GetCookieInt returns cookie result in int type.
-func (ctx *Context) GetCookieInt(name string) int {
- return com.StrTo(ctx.GetCookie(name)).MustInt()
-}
-
-// GetCookieInt64 returns cookie result in int64 type.
-func (ctx *Context) GetCookieInt64(name string) int64 {
- return com.StrTo(ctx.GetCookie(name)).MustInt64()
-}
-
-// GetCookieFloat64 returns cookie result in float64 type.
-func (ctx *Context) GetCookieFloat64(name string) float64 {
- v, _ := strconv.ParseFloat(ctx.GetCookie(name), 64)
- return v
-}
-
-var defaultCookieSecret string
-
-// SetDefaultCookieSecret sets global default secure cookie secret.
-func (m *Macaron) SetDefaultCookieSecret(secret string) {
- defaultCookieSecret = secret
-}
-
-// SetSecureCookie sets given cookie value to response header with default secret string.
-func (ctx *Context) SetSecureCookie(name, value string, others ...interface{}) {
- ctx.SetSuperSecureCookie(defaultCookieSecret, name, value, others...)
-}
-
-// GetSecureCookie returns given cookie value from request header with default secret string.
-func (ctx *Context) GetSecureCookie(key string) (string, bool) {
- return ctx.GetSuperSecureCookie(defaultCookieSecret, key)
-}
-
-// SetSuperSecureCookie sets given cookie value to response header with secret string.
-func (ctx *Context) SetSuperSecureCookie(secret, name, value string, others ...interface{}) {
- key := pbkdf2.Key([]byte(secret), []byte(secret), 1000, 16, sha256.New)
- text, err := com.AESGCMEncrypt(key, []byte(value))
- if err != nil {
- panic("error encrypting cookie: " + err.Error())
- }
-
- ctx.SetCookie(name, hex.EncodeToString(text), others...)
-}
-
-// GetSuperSecureCookie returns given cookie value from request header with secret string.
-func (ctx *Context) GetSuperSecureCookie(secret, name string) (string, bool) {
- val := ctx.GetCookie(name)
- if val == "" {
- return "", false
- }
-
- text, err := hex.DecodeString(val)
- if err != nil {
- return "", false
- }
-
- key := pbkdf2.Key([]byte(secret), []byte(secret), 1000, 16, sha256.New)
- text, err = com.AESGCMDecrypt(key, text)
- return string(text), err == nil
-}
-
-func (ctx *Context) setRawContentHeader() {
- ctx.Resp.Header().Set("Content-Description", "Raw content")
- ctx.Resp.Header().Set("Content-Type", "text/plain")
- ctx.Resp.Header().Set("Expires", "0")
- ctx.Resp.Header().Set("Cache-Control", "must-revalidate")
- ctx.Resp.Header().Set("Pragma", "public")
-}
-
-// ServeContent serves given content to response.
-func (ctx *Context) ServeContent(name string, r io.ReadSeeker, params ...interface{}) {
- modtime := time.Now()
- for _, p := range params {
- switch v := p.(type) {
- case time.Time:
- modtime = v
- }
- }
-
- ctx.setRawContentHeader()
- http.ServeContent(ctx.Resp, ctx.Req.Request, name, modtime, r)
-}
-
-// ServeFileContent serves given file as content to response.
-func (ctx *Context) ServeFileContent(file string, names ...string) {
- var name string
- if len(names) > 0 {
- name = names[0]
- } else {
- name = path.Base(file)
- }
-
- f, err := os.Open(file)
- if err != nil {
- if Env == PROD {
- http.Error(ctx.Resp, "Internal Server Error", 500)
- } else {
- http.Error(ctx.Resp, err.Error(), 500)
- }
- return
- }
- defer f.Close()
-
- ctx.setRawContentHeader()
- http.ServeContent(ctx.Resp, ctx.Req.Request, name, time.Now(), f)
-}
-
-// ServeFile serves given file to response.
-func (ctx *Context) ServeFile(file string, names ...string) {
- var name string
- if len(names) > 0 {
- name = names[0]
- } else {
- name = path.Base(file)
- }
- ctx.Resp.Header().Set("Content-Description", "File Transfer")
- ctx.Resp.Header().Set("Content-Type", "application/octet-stream")
- ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+name)
- ctx.Resp.Header().Set("Content-Transfer-Encoding", "binary")
- ctx.Resp.Header().Set("Expires", "0")
- ctx.Resp.Header().Set("Cache-Control", "must-revalidate")
- ctx.Resp.Header().Set("Pragma", "public")
- http.ServeFile(ctx.Resp, ctx.Req.Request, file)
-}
-
-// ChangeStaticPath changes static path from old to new one.
-func (ctx *Context) ChangeStaticPath(oldPath, newPath string) {
- if !filepath.IsAbs(oldPath) {
- oldPath = filepath.Join(Root, oldPath)
- }
- dir := statics.Get(oldPath)
- if dir != nil {
- statics.Delete(oldPath)
-
- if !filepath.IsAbs(newPath) {
- newPath = filepath.Join(Root, newPath)
- }
- *dir = http.Dir(newPath)
- statics.Set(dir)
- }
-}
+++ /dev/null
-module gitea.com/macaron/macaron
-
-go 1.11
-
-require (
- gitea.com/macaron/inject v0.0.0-20190805023432-d4c86e31027a
- github.com/smartystreets/assertions v1.0.1 // indirect
- github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337
- github.com/unknwon/com v1.0.1
- golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4
- gopkg.in/ini.v1 v1.44.0
-)
+++ /dev/null
-gitea.com/macaron/inject v0.0.0-20190805023432-d4c86e31027a h1:aOKEXkDTnh4euoH0so/THLXeHtQuqHmDPb1xEk6Ehok=
-gitea.com/macaron/inject v0.0.0-20190805023432-d4c86e31027a/go.mod h1:h6E4kLao1Yko6DOU6QDnQPcuoNzvbZqzj2mtPcEn1aM=
-github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
-github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE=
-github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
-github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
-github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
-github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
-github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY=
-github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
-github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w=
-github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
-github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w=
-github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
-github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8=
-github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
-github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs=
-github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
-golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-gopkg.in/ini.v1 v1.44.0 h1:YRJzTUp0kSYWUVFF5XAbDFfyiqwsl0Vb9R8TVP5eRi0=
-gopkg.in/ini.v1 v1.44.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+++ /dev/null
-// Copyright 2013 Martini Authors
-// Copyright 2014 The Macaron Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package macaron
-
-import (
- "fmt"
- "log"
- "net/http"
- "reflect"
- "runtime"
- "time"
-)
-
-var (
- ColorLog = true
- LogTimeFormat = "2006-01-02 15:04:05"
-)
-
-func init() {
- ColorLog = runtime.GOOS != "windows"
-}
-
-// LoggerInvoker is an inject.FastInvoker wrapper of func(ctx *Context, log *log.Logger).
-type LoggerInvoker func(ctx *Context, log *log.Logger)
-
-func (invoke LoggerInvoker) Invoke(params []interface{}) ([]reflect.Value, error) {
- invoke(params[0].(*Context), params[1].(*log.Logger))
- return nil, nil
-}
-
-// Logger returns a middleware handler that logs the request as it goes in and the response as it goes out.
-func Logger() Handler {
- return func(ctx *Context, log *log.Logger) {
- start := time.Now()
-
- log.Printf("%s: Started %s %s for %s", time.Now().Format(LogTimeFormat), ctx.Req.Method, ctx.Req.RequestURI, ctx.RemoteAddr())
-
- rw := ctx.Resp.(ResponseWriter)
- ctx.Next()
-
- content := fmt.Sprintf("%s: Completed %s %s %v %s in %v", time.Now().Format(LogTimeFormat), ctx.Req.Method, ctx.Req.RequestURI, rw.Status(), http.StatusText(rw.Status()), time.Since(start))
- if ColorLog {
- switch rw.Status() {
- case 200, 201, 202:
- content = fmt.Sprintf("\033[1;32m%s\033[0m", content)
- case 301, 302:
- content = fmt.Sprintf("\033[1;37m%s\033[0m", content)
- case 304:
- content = fmt.Sprintf("\033[1;33m%s\033[0m", content)
- case 401, 403:
- content = fmt.Sprintf("\033[4;31m%s\033[0m", content)
- case 404:
- content = fmt.Sprintf("\033[1;31m%s\033[0m", content)
- case 500:
- content = fmt.Sprintf("\033[1;36m%s\033[0m", content)
- }
- }
- log.Println(content)
- }
-}
+++ /dev/null
-// Copyright 2014 The Macaron Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-// Package macaron is a high productive and modular web framework in Go.
-package macaron
-
-import (
- "io"
- "log"
- "net/http"
- "os"
- "reflect"
- "strings"
- "sync"
-
- "github.com/unknwon/com"
- "gopkg.in/ini.v1"
-
- "gitea.com/macaron/inject"
-)
-
-const _VERSION = "1.3.2.1216"
-
-func Version() string {
- return _VERSION
-}
-
-// Handler can be any callable function.
-// Macaron attempts to inject services into the handler's argument list,
-// and panics if an argument could not be fullfilled via dependency injection.
-type Handler interface{}
-
-// handlerFuncInvoker is an inject.FastInvoker wrapper of func(http.ResponseWriter, *http.Request).
-type handlerFuncInvoker func(http.ResponseWriter, *http.Request)
-
-func (invoke handlerFuncInvoker) Invoke(params []interface{}) ([]reflect.Value, error) {
- invoke(params[0].(http.ResponseWriter), params[1].(*http.Request))
- return nil, nil
-}
-
-// internalServerErrorInvoker is an inject.FastInvoker wrapper of func(rw http.ResponseWriter, err error).
-type internalServerErrorInvoker func(rw http.ResponseWriter, err error)
-
-func (invoke internalServerErrorInvoker) Invoke(params []interface{}) ([]reflect.Value, error) {
- invoke(params[0].(http.ResponseWriter), params[1].(error))
- return nil, nil
-}
-
-// validateAndWrapHandler makes sure a handler is a callable function, it panics if not.
-// When the handler is also potential to be any built-in inject.FastInvoker,
-// it wraps the handler automatically to have some performance gain.
-func validateAndWrapHandler(h Handler) Handler {
- if reflect.TypeOf(h).Kind() != reflect.Func {
- panic("Macaron handler must be a callable function")
- }
-
- if !inject.IsFastInvoker(h) {
- switch v := h.(type) {
- case func(*Context):
- return ContextInvoker(v)
- case func(*Context, *log.Logger):
- return LoggerInvoker(v)
- case func(http.ResponseWriter, *http.Request):
- return handlerFuncInvoker(v)
- case func(http.ResponseWriter, error):
- return internalServerErrorInvoker(v)
- }
- }
- return h
-}
-
-// validateAndWrapHandlers preforms validation and wrapping for each input handler.
-// It accepts an optional wrapper function to perform custom wrapping on handlers.
-func validateAndWrapHandlers(handlers []Handler, wrappers ...func(Handler) Handler) []Handler {
- var wrapper func(Handler) Handler
- if len(wrappers) > 0 {
- wrapper = wrappers[0]
- }
-
- wrappedHandlers := make([]Handler, len(handlers))
- for i, h := range handlers {
- h = validateAndWrapHandler(h)
- if wrapper != nil && !inject.IsFastInvoker(h) {
- h = wrapper(h)
- }
- wrappedHandlers[i] = h
- }
-
- return wrappedHandlers
-}
-
-// Macaron represents the top level web application.
-// inject.Injector methods can be invoked to map services on a global level.
-type Macaron struct {
- inject.Injector
- befores []BeforeHandler
- handlers []Handler
- action Handler
-
- hasURLPrefix bool
- urlPrefix string // For suburl support.
- *Router
-
- logger *log.Logger
-}
-
-// NewWithLogger creates a bare bones Macaron instance.
-// Use this method if you want to have full control over the middleware that is used.
-// You can specify logger output writer with this function.
-func NewWithLogger(out io.Writer) *Macaron {
- m := &Macaron{
- Injector: inject.New(),
- action: func() {},
- Router: NewRouter(),
- logger: log.New(out, "[Macaron] ", 0),
- }
- m.Router.m = m
- m.Map(m.logger)
- m.Map(defaultReturnHandler())
- m.NotFound(http.NotFound)
- m.InternalServerError(func(rw http.ResponseWriter, err error) {
- http.Error(rw, err.Error(), 500)
- })
- return m
-}
-
-// New creates a bare bones Macaron instance.
-// Use this method if you want to have full control over the middleware that is used.
-func New() *Macaron {
- return NewWithLogger(os.Stdout)
-}
-
-// Classic creates a classic Macaron with some basic default middleware:
-// macaron.Logger, macaron.Recovery and macaron.Static.
-func Classic() *Macaron {
- m := New()
- m.Use(Logger())
- m.Use(Recovery())
- m.Use(Static("public"))
- return m
-}
-
-// Handlers sets the entire middleware stack with the given Handlers.
-// This will clear any current middleware handlers,
-// and panics if any of the handlers is not a callable function
-func (m *Macaron) Handlers(handlers ...Handler) {
- m.handlers = make([]Handler, 0)
- for _, handler := range handlers {
- m.Use(handler)
- }
-}
-
-// Action sets the handler that will be called after all the middleware has been invoked.
-// This is set to macaron.Router in a macaron.Classic().
-func (m *Macaron) Action(handler Handler) {
- handler = validateAndWrapHandler(handler)
- m.action = handler
-}
-
-// BeforeHandler represents a handler executes at beginning of every request.
-// Macaron stops future process when it returns true.
-type BeforeHandler func(rw http.ResponseWriter, req *http.Request) bool
-
-func (m *Macaron) Before(handler BeforeHandler) {
- m.befores = append(m.befores, handler)
-}
-
-// Use adds a middleware Handler to the stack,
-// and panics if the handler is not a callable func.
-// Middleware Handlers are invoked in the order that they are added.
-func (m *Macaron) Use(handler Handler) {
- handler = validateAndWrapHandler(handler)
- m.handlers = append(m.handlers, handler)
-}
-
-func (m *Macaron) createContext(rw http.ResponseWriter, req *http.Request) *Context {
- c := &Context{
- Injector: inject.New(),
- handlers: m.handlers,
- action: m.action,
- index: 0,
- Router: m.Router,
- Req: Request{req},
- Resp: NewResponseWriter(req.Method, rw),
- Render: &DummyRender{rw},
- Data: make(map[string]interface{}),
- }
- c.SetParent(m)
- c.Map(c)
- c.MapTo(c.Resp, (*http.ResponseWriter)(nil))
- c.Map(req)
- return c
-}
-
-// ServeHTTP is the HTTP Entry point for a Macaron instance.
-// Useful if you want to control your own HTTP server.
-// Be aware that none of middleware will run without registering any router.
-func (m *Macaron) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
- if m.hasURLPrefix {
- req.URL.Path = strings.TrimPrefix(req.URL.Path, m.urlPrefix)
- }
- for _, h := range m.befores {
- if h(rw, req) {
- return
- }
- }
- m.Router.ServeHTTP(rw, req)
-}
-
-func GetDefaultListenInfo() (string, int) {
- host := os.Getenv("HOST")
- if len(host) == 0 {
- host = "0.0.0.0"
- }
- port := com.StrTo(os.Getenv("PORT")).MustInt()
- if port == 0 {
- port = 4000
- }
- return host, port
-}
-
-// Run the http server. Listening on os.GetEnv("PORT") or 4000 by default.
-func (m *Macaron) Run(args ...interface{}) {
- host, port := GetDefaultListenInfo()
- if len(args) == 1 {
- switch arg := args[0].(type) {
- case string:
- host = arg
- case int:
- port = arg
- }
- } else if len(args) >= 2 {
- if arg, ok := args[0].(string); ok {
- host = arg
- }
- if arg, ok := args[1].(int); ok {
- port = arg
- }
- }
-
- addr := host + ":" + com.ToStr(port)
- logger := m.GetVal(reflect.TypeOf(m.logger)).Interface().(*log.Logger)
- logger.Printf("listening on %s (%s)\n", addr, safeEnv())
- logger.Fatalln(http.ListenAndServe(addr, m))
-}
-
-// SetURLPrefix sets URL prefix of router layer, so that it support suburl.
-func (m *Macaron) SetURLPrefix(prefix string) {
- m.urlPrefix = prefix
- m.hasURLPrefix = len(m.urlPrefix) > 0
-}
-
-// ____ ____ .__ ___. .__
-// \ \ / /____ _______|__|____ \_ |__ | | ____ ______
-// \ Y /\__ \\_ __ \ \__ \ | __ \| | _/ __ \ / ___/
-// \ / / __ \| | \/ |/ __ \| \_\ \ |_\ ___/ \___ \
-// \___/ (____ /__| |__(____ /___ /____/\___ >____ >
-// \/ \/ \/ \/ \/
-
-const (
- DEV = "development"
- PROD = "production"
- TEST = "test"
-)
-
-var (
- // Env is the environment that Macaron is executing in.
- // The MACARON_ENV is read on initialization to set this variable.
- Env = DEV
- envLock sync.Mutex
-
- // Path of work directory.
- // You must set this value yourself
- Root string
-
- // Flash applies to current request.
- FlashNow bool
-
- // Configuration convention object.
- cfg *ini.File
-)
-
-func setENV(e string) {
- envLock.Lock()
- defer envLock.Unlock()
-
- if len(e) > 0 {
- Env = e
- }
-}
-
-func safeEnv() string {
- envLock.Lock()
- defer envLock.Unlock()
-
- return Env
-}
-
-func init() {
- setENV(os.Getenv("MACARON_ENV"))
-}
-
-// SetConfig sets data sources for configuration.
-func SetConfig(source interface{}, others ...interface{}) (_ *ini.File, err error) {
- cfg, err = ini.Load(source, others...)
- return Config(), err
-}
-
-// Config returns configuration convention object.
-// It returns an empty object if there is no one available.
-func Config() *ini.File {
- if cfg == nil {
- return ini.Empty()
- }
- return cfg
-}
+++ /dev/null
-// Copyright 2013 Martini Authors
-// Copyright 2014 The Macaron Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package macaron
-
-import (
- "bytes"
- "fmt"
- "io/ioutil"
- "log"
- "net/http"
- "runtime"
-
- "gitea.com/macaron/inject"
-)
-
-const (
- panicHtml = `<html>
-<head><title>PANIC: %s</title>
-<meta charset="utf-8" />
-<style type="text/css">
-html, body {
- font-family: "Roboto", sans-serif;
- color: #333333;
- background-color: #ea5343;
- margin: 0px;
-}
-h1 {
- color: #d04526;
- background-color: #ffffff;
- padding: 20px;
- border-bottom: 1px dashed #2b3848;
-}
-pre {
- margin: 20px;
- padding: 20px;
- border: 2px solid #2b3848;
- background-color: #ffffff;
- white-space: pre-wrap; /* css-3 */
- white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
- white-space: -pre-wrap; /* Opera 4-6 */
- white-space: -o-pre-wrap; /* Opera 7 */
- word-wrap: break-word; /* Internet Explorer 5.5+ */
-}
-</style>
-</head><body>
-<h1>PANIC</h1>
-<pre style="font-weight: bold;">%s</pre>
-<pre>%s</pre>
-</body>
-</html>`
-)
-
-var (
- dunno = []byte("???")
- centerDot = []byte("·")
- dot = []byte(".")
- slash = []byte("/")
-)
-
-// stack returns a nicely formated stack frame, skipping skip frames
-func stack(skip int) []byte {
- buf := new(bytes.Buffer) // the returned data
- // As we loop, we open files and read them. These variables record the currently
- // loaded file.
- var lines [][]byte
- var lastFile string
- for i := skip; ; i++ { // Skip the expected number of frames
- pc, file, line, ok := runtime.Caller(i)
- if !ok {
- break
- }
- // Print this much at least. If we can't find the source, it won't show.
- fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc)
- if file != lastFile {
- data, err := ioutil.ReadFile(file)
- if err != nil {
- continue
- }
- lines = bytes.Split(data, []byte{'\n'})
- lastFile = file
- }
- fmt.Fprintf(buf, "\t%s: %s\n", function(pc), source(lines, line))
- }
- return buf.Bytes()
-}
-
-// source returns a space-trimmed slice of the n'th line.
-func source(lines [][]byte, n int) []byte {
- n-- // in stack trace, lines are 1-indexed but our array is 0-indexed
- if n < 0 || n >= len(lines) {
- return dunno
- }
- return bytes.TrimSpace(lines[n])
-}
-
-// function returns, if possible, the name of the function containing the PC.
-func function(pc uintptr) []byte {
- fn := runtime.FuncForPC(pc)
- if fn == nil {
- return dunno
- }
- name := []byte(fn.Name())
- // The name includes the path name to the package, which is unnecessary
- // since the file name is already included. Plus, it has center dots.
- // That is, we see
- // runtime/debug.*T·ptrmethod
- // and want
- // *T.ptrmethod
- // Also the package path might contains dot (e.g. code.google.com/...),
- // so first eliminate the path prefix
- if lastslash := bytes.LastIndex(name, slash); lastslash >= 0 {
- name = name[lastslash+1:]
- }
- if period := bytes.Index(name, dot); period >= 0 {
- name = name[period+1:]
- }
- name = bytes.Replace(name, centerDot, dot, -1)
- return name
-}
-
-// Recovery returns a middleware that recovers from any panics and writes a 500 if there was one.
-// While Martini is in development mode, Recovery will also output the panic as HTML.
-func Recovery() Handler {
- return func(c *Context, log *log.Logger) {
- defer func() {
- if err := recover(); err != nil {
- stack := stack(3)
- log.Printf("PANIC: %s\n%s", err, stack)
-
- // Lookup the current responsewriter
- val := c.GetVal(inject.InterfaceOf((*http.ResponseWriter)(nil)))
- res := val.Interface().(http.ResponseWriter)
-
- // respond with panic message while in development mode
- var body []byte
- if Env == DEV {
- res.Header().Set("Content-Type", "text/html")
- body = []byte(fmt.Sprintf(panicHtml, err, err, stack))
- }
-
- res.WriteHeader(http.StatusInternalServerError)
- if nil != body {
- _, _ = res.Write(body)
- }
- }
- }()
-
- c.Next()
- }
-}
+++ /dev/null
-// Copyright 2013 Martini Authors
-// Copyright 2014 The Macaron Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package macaron
-
-import (
- "bytes"
- "encoding/json"
- "encoding/xml"
- "fmt"
- "html/template"
- "io"
- "io/ioutil"
- "net/http"
- "os"
- "path"
- "path/filepath"
- "strings"
- "sync"
- "time"
-
- "github.com/unknwon/com"
-)
-
-const (
- _CONTENT_TYPE = "Content-Type"
- _CONTENT_BINARY = "application/octet-stream"
- _CONTENT_JSON = "application/json"
- _CONTENT_HTML = "text/html"
- _CONTENT_PLAIN = "text/plain"
- _CONTENT_XHTML = "application/xhtml+xml"
- _CONTENT_XML = "text/xml"
- _DEFAULT_CHARSET = "UTF-8"
-)
-
-var (
- // Provides a temporary buffer to execute templates into and catch errors.
- bufpool = sync.Pool{
- New: func() interface{} { return new(bytes.Buffer) },
- }
-
- // Included helper functions for use when rendering html
- helperFuncs = template.FuncMap{
- "yield": func() (string, error) {
- return "", fmt.Errorf("yield called with no layout defined")
- },
- "current": func() (string, error) {
- return "", nil
- },
- }
-)
-
-type (
- // TemplateFile represents a interface of template file that has name and can be read.
- TemplateFile interface {
- Name() string
- Data() []byte
- Ext() string
- }
- // TemplateFileSystem represents a interface of template file system that able to list all files.
- TemplateFileSystem interface {
- ListFiles() []TemplateFile
- Get(string) (io.Reader, error)
- }
-
- // Delims represents a set of Left and Right delimiters for HTML template rendering
- Delims struct {
- // Left delimiter, defaults to {{
- Left string
- // Right delimiter, defaults to }}
- Right string
- }
-
- // RenderOptions represents a struct for specifying configuration options for the Render middleware.
- RenderOptions struct {
- // Directory to load templates. Default is "templates".
- Directory string
- // Addtional directories to overwite templates.
- AppendDirectories []string
- // Layout template name. Will not render a layout if "". Default is to "".
- Layout string
- // Extensions to parse template files from. Defaults are [".tmpl", ".html"].
- Extensions []string
- // Funcs is a slice of FuncMaps to apply to the template upon compilation. This is useful for helper functions. Default is [].
- Funcs []template.FuncMap
- // Delims sets the action delimiters to the specified strings in the Delims struct.
- Delims Delims
- // Appends the given charset to the Content-Type header. Default is "UTF-8".
- Charset string
- // Outputs human readable JSON.
- IndentJSON bool
- // Outputs human readable XML.
- IndentXML bool
- // Prefixes the JSON output with the given bytes.
- PrefixJSON []byte
- // Prefixes the XML output with the given bytes.
- PrefixXML []byte
- // Allows changing of output to XHTML instead of HTML. Default is "text/html"
- HTMLContentType string
- // TemplateFileSystem is the interface for supporting any implmentation of template file system.
- TemplateFileSystem
- }
-
- // HTMLOptions is a struct for overriding some rendering Options for specific HTML call
- HTMLOptions struct {
- // Layout template name. Overrides Options.Layout.
- Layout string
- }
-
- Render interface {
- http.ResponseWriter
- SetResponseWriter(http.ResponseWriter)
-
- JSON(int, interface{})
- JSONString(interface{}) (string, error)
- RawData(int, []byte) // Serve content as binary
- PlainText(int, []byte) // Serve content as plain text
- HTML(int, string, interface{}, ...HTMLOptions)
- HTMLSet(int, string, string, interface{}, ...HTMLOptions)
- HTMLSetString(string, string, interface{}, ...HTMLOptions) (string, error)
- HTMLString(string, interface{}, ...HTMLOptions) (string, error)
- HTMLSetBytes(string, string, interface{}, ...HTMLOptions) ([]byte, error)
- HTMLBytes(string, interface{}, ...HTMLOptions) ([]byte, error)
- XML(int, interface{})
- Error(int, ...string)
- Status(int)
- SetTemplatePath(string, string)
- HasTemplateSet(string) bool
- }
-)
-
-// TplFile implements TemplateFile interface.
-type TplFile struct {
- name string
- data []byte
- ext string
-}
-
-// NewTplFile cerates new template file with given name and data.
-func NewTplFile(name string, data []byte, ext string) *TplFile {
- return &TplFile{name, data, ext}
-}
-
-func (f *TplFile) Name() string {
- return f.name
-}
-
-func (f *TplFile) Data() []byte {
- return f.data
-}
-
-func (f *TplFile) Ext() string {
- return f.ext
-}
-
-// TplFileSystem implements TemplateFileSystem interface.
-type TplFileSystem struct {
- files []TemplateFile
-}
-
-// NewTemplateFileSystem creates new template file system with given options.
-func NewTemplateFileSystem(opt RenderOptions, omitData bool) TplFileSystem {
- fs := TplFileSystem{}
- fs.files = make([]TemplateFile, 0, 10)
-
- // Directories are composed in reverse order because later one overwrites previous ones,
- // so once found, we can directly jump out of the loop.
- dirs := make([]string, 0, len(opt.AppendDirectories)+1)
- for i := len(opt.AppendDirectories) - 1; i >= 0; i-- {
- dirs = append(dirs, opt.AppendDirectories[i])
- }
- dirs = append(dirs, opt.Directory)
-
- var err error
- for i := range dirs {
- // Skip ones that does not exists for symlink test,
- // but allow non-symlink ones added after start.
- if !com.IsExist(dirs[i]) {
- continue
- }
-
- dirs[i], err = filepath.EvalSymlinks(dirs[i])
- if err != nil {
- panic("EvalSymlinks(" + dirs[i] + "): " + err.Error())
- }
- }
- lastDir := dirs[len(dirs)-1]
-
- // We still walk the last (original) directory because it's non-sense we load templates not exist in original directory.
- if err = filepath.Walk(lastDir, func(path string, info os.FileInfo, _ error) error {
- r, err := filepath.Rel(lastDir, path)
- if err != nil {
- return err
- }
-
- ext := GetExt(r)
-
- for _, extension := range opt.Extensions {
- if ext != extension {
- continue
- }
-
- var data []byte
- if !omitData {
- // Loop over candidates of directory, break out once found.
- // The file always exists because it's inside the walk function,
- // and read original file is the worst case.
- for i := range dirs {
- path = filepath.Join(dirs[i], r)
- if !com.IsFile(path) {
- continue
- }
-
- data, err = ioutil.ReadFile(path)
- if err != nil {
- return err
- }
- break
- }
- }
-
- name := filepath.ToSlash((r[0 : len(r)-len(ext)]))
- fs.files = append(fs.files, NewTplFile(name, data, ext))
- }
-
- return nil
- }); err != nil {
- panic("NewTemplateFileSystem: " + err.Error())
- }
-
- return fs
-}
-
-func (fs TplFileSystem) ListFiles() []TemplateFile {
- return fs.files
-}
-
-func (fs TplFileSystem) Get(name string) (io.Reader, error) {
- for i := range fs.files {
- if fs.files[i].Name()+fs.files[i].Ext() == name {
- return bytes.NewReader(fs.files[i].Data()), nil
- }
- }
- return nil, fmt.Errorf("file '%s' not found", name)
-}
-
-func PrepareCharset(charset string) string {
- if len(charset) != 0 {
- return "; charset=" + charset
- }
-
- return "; charset=" + _DEFAULT_CHARSET
-}
-
-func GetExt(s string) string {
- index := strings.Index(s, ".")
- if index == -1 {
- return ""
- }
- return s[index:]
-}
-
-func compile(opt RenderOptions) *template.Template {
- t := template.New(opt.Directory)
- t.Delims(opt.Delims.Left, opt.Delims.Right)
- // Parse an initial template in case we don't have any.
- template.Must(t.Parse("Macaron"))
-
- if opt.TemplateFileSystem == nil {
- opt.TemplateFileSystem = NewTemplateFileSystem(opt, false)
- }
-
- for _, f := range opt.TemplateFileSystem.ListFiles() {
- tmpl := t.New(f.Name())
- for _, funcs := range opt.Funcs {
- tmpl.Funcs(funcs)
- }
- // Bomb out if parse fails. We don't want any silent server starts.
- template.Must(tmpl.Funcs(helperFuncs).Parse(string(f.Data())))
- }
-
- return t
-}
-
-const (
- DEFAULT_TPL_SET_NAME = "DEFAULT"
-)
-
-// TemplateSet represents a template set of type *template.Template.
-type TemplateSet struct {
- lock sync.RWMutex
- sets map[string]*template.Template
- dirs map[string]string
-}
-
-// NewTemplateSet initializes a new empty template set.
-func NewTemplateSet() *TemplateSet {
- return &TemplateSet{
- sets: make(map[string]*template.Template),
- dirs: make(map[string]string),
- }
-}
-
-func (ts *TemplateSet) Set(name string, opt *RenderOptions) *template.Template {
- t := compile(*opt)
-
- ts.lock.Lock()
- defer ts.lock.Unlock()
-
- ts.sets[name] = t
- ts.dirs[name] = opt.Directory
- return t
-}
-
-func (ts *TemplateSet) Get(name string) *template.Template {
- ts.lock.RLock()
- defer ts.lock.RUnlock()
-
- return ts.sets[name]
-}
-
-func (ts *TemplateSet) GetDir(name string) string {
- ts.lock.RLock()
- defer ts.lock.RUnlock()
-
- return ts.dirs[name]
-}
-
-func prepareRenderOptions(options []RenderOptions) RenderOptions {
- var opt RenderOptions
- if len(options) > 0 {
- opt = options[0]
- }
-
- // Defaults.
- if len(opt.Directory) == 0 {
- opt.Directory = "templates"
- }
- if len(opt.Extensions) == 0 {
- opt.Extensions = []string{".tmpl", ".html"}
- }
- if len(opt.HTMLContentType) == 0 {
- opt.HTMLContentType = _CONTENT_HTML
- }
-
- return opt
-}
-
-func ParseTplSet(tplSet string) (tplName string, tplDir string) {
- tplSet = strings.TrimSpace(tplSet)
- if len(tplSet) == 0 {
- panic("empty template set argument")
- }
- infos := strings.Split(tplSet, ":")
- if len(infos) == 1 {
- tplDir = infos[0]
- tplName = path.Base(tplDir)
- } else {
- tplName = infos[0]
- tplDir = infos[1]
- }
-
- if !com.IsDir(tplDir) {
- panic("template set path does not exist or is not a directory")
- }
- return tplName, tplDir
-}
-
-func renderHandler(opt RenderOptions, tplSets []string) Handler {
- cs := PrepareCharset(opt.Charset)
- ts := NewTemplateSet()
- ts.Set(DEFAULT_TPL_SET_NAME, &opt)
-
- var tmpOpt RenderOptions
- for _, tplSet := range tplSets {
- tplName, tplDir := ParseTplSet(tplSet)
- tmpOpt = opt
- tmpOpt.Directory = tplDir
- ts.Set(tplName, &tmpOpt)
- }
-
- return func(ctx *Context) {
- r := &TplRender{
- ResponseWriter: ctx.Resp,
- TemplateSet: ts,
- Opt: &opt,
- CompiledCharset: cs,
- }
- ctx.Data["TmplLoadTimes"] = func() string {
- if r.startTime.IsZero() {
- return ""
- }
- return fmt.Sprint(time.Since(r.startTime).Nanoseconds()/1e6) + "ms"
- }
-
- ctx.Render = r
- ctx.MapTo(r, (*Render)(nil))
- }
-}
-
-// Renderer is a Middleware that maps a macaron.Render service into the Macaron handler chain.
-// An single variadic macaron.RenderOptions struct can be optionally provided to configure
-// HTML rendering. The default directory for templates is "templates" and the default
-// file extension is ".tmpl" and ".html".
-//
-// If MACARON_ENV is set to "" or "development" then templates will be recompiled on every request. For more performance, set the
-// MACARON_ENV environment variable to "production".
-func Renderer(options ...RenderOptions) Handler {
- return renderHandler(prepareRenderOptions(options), []string{})
-}
-
-func Renderers(options RenderOptions, tplSets ...string) Handler {
- return renderHandler(prepareRenderOptions([]RenderOptions{options}), tplSets)
-}
-
-type TplRender struct {
- http.ResponseWriter
- *TemplateSet
- Opt *RenderOptions
- CompiledCharset string
-
- startTime time.Time
-}
-
-func (r *TplRender) SetResponseWriter(rw http.ResponseWriter) {
- r.ResponseWriter = rw
-}
-
-func (r *TplRender) JSON(status int, v interface{}) {
- var (
- result []byte
- err error
- )
- if r.Opt.IndentJSON {
- result, err = json.MarshalIndent(v, "", " ")
- } else {
- result, err = json.Marshal(v)
- }
- if err != nil {
- http.Error(r, err.Error(), 500)
- return
- }
-
- // json rendered fine, write out the result
- r.Header().Set(_CONTENT_TYPE, _CONTENT_JSON+r.CompiledCharset)
- r.WriteHeader(status)
- if len(r.Opt.PrefixJSON) > 0 {
- _, _ = r.Write(r.Opt.PrefixJSON)
- }
- _, _ = r.Write(result)
-}
-
-func (r *TplRender) JSONString(v interface{}) (string, error) {
- var result []byte
- var err error
- if r.Opt.IndentJSON {
- result, err = json.MarshalIndent(v, "", " ")
- } else {
- result, err = json.Marshal(v)
- }
- if err != nil {
- return "", err
- }
- return string(result), nil
-}
-
-func (r *TplRender) XML(status int, v interface{}) {
- var result []byte
- var err error
- if r.Opt.IndentXML {
- result, err = xml.MarshalIndent(v, "", " ")
- } else {
- result, err = xml.Marshal(v)
- }
- if err != nil {
- http.Error(r, err.Error(), 500)
- return
- }
-
- // XML rendered fine, write out the result
- r.Header().Set(_CONTENT_TYPE, _CONTENT_XML+r.CompiledCharset)
- r.WriteHeader(status)
- if len(r.Opt.PrefixXML) > 0 {
- _, _ = r.Write(r.Opt.PrefixXML)
- }
- _, _ = r.Write(result)
-}
-
-func (r *TplRender) data(status int, contentType string, v []byte) {
- if r.Header().Get(_CONTENT_TYPE) == "" {
- r.Header().Set(_CONTENT_TYPE, contentType)
- }
- r.WriteHeader(status)
- _, _ = r.Write(v)
-}
-
-func (r *TplRender) RawData(status int, v []byte) {
- r.data(status, _CONTENT_BINARY, v)
-}
-
-func (r *TplRender) PlainText(status int, v []byte) {
- r.data(status, _CONTENT_PLAIN, v)
-}
-
-func (r *TplRender) execute(t *template.Template, name string, data interface{}) (*bytes.Buffer, error) {
- buf := bufpool.Get().(*bytes.Buffer)
- return buf, t.ExecuteTemplate(buf, name, data)
-}
-
-func (r *TplRender) addYield(t *template.Template, tplName string, data interface{}) {
- funcs := template.FuncMap{
- "yield": func() (template.HTML, error) {
- buf, err := r.execute(t, tplName, data)
- // return safe html here since we are rendering our own template
- return template.HTML(buf.String()), err
- },
- "current": func() (string, error) {
- return tplName, nil
- },
- }
- t.Funcs(funcs)
-}
-
-func (r *TplRender) renderBytes(setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) (*bytes.Buffer, error) {
- t := r.TemplateSet.Get(setName)
- if Env == DEV {
- opt := *r.Opt
- opt.Directory = r.TemplateSet.GetDir(setName)
- t = r.TemplateSet.Set(setName, &opt)
- }
- if t == nil {
- return nil, fmt.Errorf("html/template: template \"%s\" is undefined", tplName)
- }
-
- opt := r.prepareHTMLOptions(htmlOpt)
-
- if len(opt.Layout) > 0 {
- r.addYield(t, tplName, data)
- tplName = opt.Layout
- }
-
- out, err := r.execute(t, tplName, data)
- if err != nil {
- return nil, err
- }
-
- return out, nil
-}
-
-func (r *TplRender) renderHTML(status int, setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) {
- r.startTime = time.Now()
-
- out, err := r.renderBytes(setName, tplName, data, htmlOpt...)
- if err != nil {
- http.Error(r, err.Error(), http.StatusInternalServerError)
- return
- }
-
- r.Header().Set(_CONTENT_TYPE, r.Opt.HTMLContentType+r.CompiledCharset)
- r.WriteHeader(status)
-
- if _, err := out.WriteTo(r); err != nil {
- out.Reset()
- }
- bufpool.Put(out)
-}
-
-func (r *TplRender) HTML(status int, name string, data interface{}, htmlOpt ...HTMLOptions) {
- r.renderHTML(status, DEFAULT_TPL_SET_NAME, name, data, htmlOpt...)
-}
-
-func (r *TplRender) HTMLSet(status int, setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) {
- r.renderHTML(status, setName, tplName, data, htmlOpt...)
-}
-
-func (r *TplRender) HTMLSetBytes(setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) ([]byte, error) {
- out, err := r.renderBytes(setName, tplName, data, htmlOpt...)
- if err != nil {
- return []byte(""), err
- }
- return out.Bytes(), nil
-}
-
-func (r *TplRender) HTMLBytes(name string, data interface{}, htmlOpt ...HTMLOptions) ([]byte, error) {
- return r.HTMLSetBytes(DEFAULT_TPL_SET_NAME, name, data, htmlOpt...)
-}
-
-func (r *TplRender) HTMLSetString(setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) (string, error) {
- p, err := r.HTMLSetBytes(setName, tplName, data, htmlOpt...)
- return string(p), err
-}
-
-func (r *TplRender) HTMLString(name string, data interface{}, htmlOpt ...HTMLOptions) (string, error) {
- p, err := r.HTMLBytes(name, data, htmlOpt...)
- return string(p), err
-}
-
-// Error writes the given HTTP status to the current ResponseWriter
-func (r *TplRender) Error(status int, message ...string) {
- r.WriteHeader(status)
- if len(message) > 0 {
- _, _ = r.Write([]byte(message[0]))
- }
-}
-
-func (r *TplRender) Status(status int) {
- r.WriteHeader(status)
-}
-
-func (r *TplRender) prepareHTMLOptions(htmlOpt []HTMLOptions) HTMLOptions {
- if len(htmlOpt) > 0 {
- return htmlOpt[0]
- }
-
- return HTMLOptions{
- Layout: r.Opt.Layout,
- }
-}
-
-func (r *TplRender) SetTemplatePath(setName, dir string) {
- if len(setName) == 0 {
- setName = DEFAULT_TPL_SET_NAME
- }
- opt := *r.Opt
- opt.Directory = dir
- r.TemplateSet.Set(setName, &opt)
-}
-
-func (r *TplRender) HasTemplateSet(name string) bool {
- return r.TemplateSet.Get(name) != nil
-}
-
-// DummyRender is used when user does not choose any real render to use.
-// This way, we can print out friendly message which asks them to register one,
-// instead of ugly and confusing 'nil pointer' panic.
-type DummyRender struct {
- http.ResponseWriter
-}
-
-func renderNotRegistered() {
- panic("middleware render hasn't been registered")
-}
-
-func (r *DummyRender) SetResponseWriter(http.ResponseWriter) {
- renderNotRegistered()
-}
-
-func (r *DummyRender) JSON(int, interface{}) {
- renderNotRegistered()
-}
-
-func (r *DummyRender) JSONString(interface{}) (string, error) {
- renderNotRegistered()
- return "", nil
-}
-
-func (r *DummyRender) RawData(int, []byte) {
- renderNotRegistered()
-}
-
-func (r *DummyRender) PlainText(int, []byte) {
- renderNotRegistered()
-}
-
-func (r *DummyRender) HTML(int, string, interface{}, ...HTMLOptions) {
- renderNotRegistered()
-}
-
-func (r *DummyRender) HTMLSet(int, string, string, interface{}, ...HTMLOptions) {
- renderNotRegistered()
-}
-
-func (r *DummyRender) HTMLSetString(string, string, interface{}, ...HTMLOptions) (string, error) {
- renderNotRegistered()
- return "", nil
-}
-
-func (r *DummyRender) HTMLString(string, interface{}, ...HTMLOptions) (string, error) {
- renderNotRegistered()
- return "", nil
-}
-
-func (r *DummyRender) HTMLSetBytes(string, string, interface{}, ...HTMLOptions) ([]byte, error) {
- renderNotRegistered()
- return nil, nil
-}
-
-func (r *DummyRender) HTMLBytes(string, interface{}, ...HTMLOptions) ([]byte, error) {
- renderNotRegistered()
- return nil, nil
-}
-
-func (r *DummyRender) XML(int, interface{}) {
- renderNotRegistered()
-}
-
-func (r *DummyRender) Error(int, ...string) {
- renderNotRegistered()
-}
-
-func (r *DummyRender) Status(int) {
- renderNotRegistered()
-}
-
-func (r *DummyRender) SetTemplatePath(string, string) {
- renderNotRegistered()
-}
-
-func (r *DummyRender) HasTemplateSet(string) bool {
- renderNotRegistered()
- return false
-}
+++ /dev/null
-// Copyright 2013 Martini Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package macaron
-
-import (
- "bufio"
- "errors"
- "net"
- "net/http"
-)
-
-// ResponseWriter is a wrapper around http.ResponseWriter that provides extra information about
-// the response. It is recommended that middleware handlers use this construct to wrap a responsewriter
-// if the functionality calls for it.
-type ResponseWriter interface {
- http.ResponseWriter
- http.Flusher
- http.Pusher
- // Status returns the status code of the response or 0 if the response has not been written.
- Status() int
- // Written returns whether or not the ResponseWriter has been written.
- Written() bool
- // Size returns the size of the response body.
- Size() int
- // Before allows for a function to be called before the ResponseWriter has been written to. This is
- // useful for setting headers or any other operations that must happen before a response has been written.
- Before(BeforeFunc)
-}
-
-// BeforeFunc is a function that is called before the ResponseWriter has been written to.
-type BeforeFunc func(ResponseWriter)
-
-// NewResponseWriter creates a ResponseWriter that wraps an http.ResponseWriter
-func NewResponseWriter(method string, rw http.ResponseWriter) ResponseWriter {
- return &responseWriter{method, rw, 0, 0, nil}
-}
-
-type responseWriter struct {
- method string
- http.ResponseWriter
- status int
- size int
- beforeFuncs []BeforeFunc
-}
-
-func (rw *responseWriter) WriteHeader(s int) {
- rw.callBefore()
- rw.ResponseWriter.WriteHeader(s)
- rw.status = s
-}
-
-func (rw *responseWriter) Write(b []byte) (size int, err error) {
- if !rw.Written() {
- // The status will be StatusOK if WriteHeader has not been called yet
- rw.WriteHeader(http.StatusOK)
- }
- if rw.method != "HEAD" {
- size, err = rw.ResponseWriter.Write(b)
- rw.size += size
- }
- return size, err
-}
-
-func (rw *responseWriter) Status() int {
- return rw.status
-}
-
-func (rw *responseWriter) Size() int {
- return rw.size
-}
-
-func (rw *responseWriter) Written() bool {
- return rw.status != 0
-}
-
-func (rw *responseWriter) Before(before BeforeFunc) {
- rw.beforeFuncs = append(rw.beforeFuncs, before)
-}
-
-func (rw *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
- hijacker, ok := rw.ResponseWriter.(http.Hijacker)
- if !ok {
- return nil, nil, errors.New("the ResponseWriter doesn't support the Hijacker interface")
- }
- return hijacker.Hijack()
-}
-
-//nolint
-func (rw *responseWriter) CloseNotify() <-chan bool {
- return rw.ResponseWriter.(http.CloseNotifier).CloseNotify()
-}
-
-func (rw *responseWriter) callBefore() {
- for i := len(rw.beforeFuncs) - 1; i >= 0; i-- {
- rw.beforeFuncs[i](rw)
- }
-}
-
-func (rw *responseWriter) Flush() {
- flusher, ok := rw.ResponseWriter.(http.Flusher)
- if ok {
- flusher.Flush()
- }
-}
-
-func (rw *responseWriter) Push(target string, opts *http.PushOptions) error {
- pusher, ok := rw.ResponseWriter.(http.Pusher)
- if !ok {
- return errors.New("the ResponseWriter doesn't support the Pusher interface")
- }
- return pusher.Push(target, opts)
-}
+++ /dev/null
-// Copyright 2013 Martini Authors
-// Copyright 2014 The Macaron Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package macaron
-
-import (
- "net/http"
- "reflect"
-
- "gitea.com/macaron/inject"
-)
-
-// ReturnHandler is a service that Martini provides that is called
-// when a route handler returns something. The ReturnHandler is
-// responsible for writing to the ResponseWriter based on the values
-// that are passed into this function.
-type ReturnHandler func(*Context, []reflect.Value)
-
-func canDeref(val reflect.Value) bool {
- return val.Kind() == reflect.Interface || val.Kind() == reflect.Ptr
-}
-
-func isError(val reflect.Value) bool {
- _, ok := val.Interface().(error)
- return ok
-}
-
-func isByteSlice(val reflect.Value) bool {
- return val.Kind() == reflect.Slice && val.Type().Elem().Kind() == reflect.Uint8
-}
-
-func defaultReturnHandler() ReturnHandler {
- return func(ctx *Context, vals []reflect.Value) {
- rv := ctx.GetVal(inject.InterfaceOf((*http.ResponseWriter)(nil)))
- resp := rv.Interface().(http.ResponseWriter)
- var respVal reflect.Value
- if len(vals) > 1 && vals[0].Kind() == reflect.Int {
- resp.WriteHeader(int(vals[0].Int()))
- respVal = vals[1]
- } else if len(vals) > 0 {
- respVal = vals[0]
-
- if isError(respVal) {
- err := respVal.Interface().(error)
- if err != nil {
- ctx.internalServerError(ctx, err)
- }
- return
- } else if canDeref(respVal) {
- if respVal.IsNil() {
- return // Ignore nil error
- }
- }
- }
- if canDeref(respVal) {
- respVal = respVal.Elem()
- }
- if isByteSlice(respVal) {
- _, _ = resp.Write(respVal.Bytes())
- } else {
- _, _ = resp.Write([]byte(respVal.String()))
- }
- }
-}
+++ /dev/null
-// Copyright 2014 The Macaron Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package macaron
-
-import (
- "net/http"
- "strings"
- "sync"
-)
-
-var (
- // Known HTTP methods.
- _HTTP_METHODS = map[string]bool{
- "GET": true,
- "POST": true,
- "PUT": true,
- "DELETE": true,
- "PATCH": true,
- "OPTIONS": true,
- "HEAD": true,
- }
-)
-
-// routeMap represents a thread-safe map for route tree.
-type routeMap struct {
- lock sync.RWMutex
- routes map[string]map[string]*Leaf
-}
-
-// NewRouteMap initializes and returns a new routeMap.
-func NewRouteMap() *routeMap {
- rm := &routeMap{
- routes: make(map[string]map[string]*Leaf),
- }
- for m := range _HTTP_METHODS {
- rm.routes[m] = make(map[string]*Leaf)
- }
- return rm
-}
-
-// getLeaf returns Leaf object if a route has been registered.
-func (rm *routeMap) getLeaf(method, pattern string) *Leaf {
- rm.lock.RLock()
- defer rm.lock.RUnlock()
-
- return rm.routes[method][pattern]
-}
-
-// add adds new route to route tree map.
-func (rm *routeMap) add(method, pattern string, leaf *Leaf) {
- rm.lock.Lock()
- defer rm.lock.Unlock()
-
- rm.routes[method][pattern] = leaf
-}
-
-type group struct {
- pattern string
- handlers []Handler
-}
-
-// Router represents a Macaron router layer.
-type Router struct {
- m *Macaron
- autoHead bool
- routers map[string]*Tree
- *routeMap
- namedRoutes map[string]*Leaf
-
- groups []group
- notFound http.HandlerFunc
- internalServerError func(*Context, error)
-
- // handlerWrapper is used to wrap arbitrary function from Handler to inject.FastInvoker.
- handlerWrapper func(Handler) Handler
-}
-
-func NewRouter() *Router {
- return &Router{
- routers: make(map[string]*Tree),
- routeMap: NewRouteMap(),
- namedRoutes: make(map[string]*Leaf),
- }
-}
-
-// SetAutoHead sets the value who determines whether add HEAD method automatically
-// when GET method is added.
-func (r *Router) SetAutoHead(v bool) {
- r.autoHead = v
-}
-
-type Params map[string]string
-
-// Handle is a function that can be registered to a route to handle HTTP requests.
-// Like http.HandlerFunc, but has a third parameter for the values of wildcards (variables).
-type Handle func(http.ResponseWriter, *http.Request, Params)
-
-// Route represents a wrapper of leaf route and upper level router.
-type Route struct {
- router *Router
- leaf *Leaf
-}
-
-// Name sets name of route.
-func (r *Route) Name(name string) {
- if len(name) == 0 {
- panic("route name cannot be empty")
- } else if r.router.namedRoutes[name] != nil {
- panic("route with given name already exists: " + name)
- }
- r.router.namedRoutes[name] = r.leaf
-}
-
-// handle adds new route to the router tree.
-func (r *Router) handle(method, pattern string, handle Handle) *Route {
- method = strings.ToUpper(method)
-
- var leaf *Leaf
- // Prevent duplicate routes.
- if leaf = r.getLeaf(method, pattern); leaf != nil {
- return &Route{r, leaf}
- }
-
- // Validate HTTP methods.
- if !_HTTP_METHODS[method] && method != "*" {
- panic("unknown HTTP method: " + method)
- }
-
- // Generate methods need register.
- methods := make(map[string]bool)
- if method == "*" {
- for m := range _HTTP_METHODS {
- methods[m] = true
- }
- } else {
- methods[method] = true
- }
-
- // Add to router tree.
- for m := range methods {
- if t, ok := r.routers[m]; ok {
- leaf = t.Add(pattern, handle)
- } else {
- t := NewTree()
- leaf = t.Add(pattern, handle)
- r.routers[m] = t
- }
- r.add(m, pattern, leaf)
- }
- return &Route{r, leaf}
-}
-
-// Handle registers a new request handle with the given pattern, method and handlers.
-func (r *Router) Handle(method string, pattern string, handlers []Handler) *Route {
- if len(r.groups) > 0 {
- groupPattern := ""
- h := make([]Handler, 0)
- for _, g := range r.groups {
- groupPattern += g.pattern
- h = append(h, g.handlers...)
- }
-
- pattern = groupPattern + pattern
- h = append(h, handlers...)
- handlers = h
- }
- handlers = validateAndWrapHandlers(handlers, r.handlerWrapper)
-
- return r.handle(method, pattern, func(resp http.ResponseWriter, req *http.Request, params Params) {
- c := r.m.createContext(resp, req)
- c.params = params
- c.handlers = make([]Handler, 0, len(r.m.handlers)+len(handlers))
- c.handlers = append(c.handlers, r.m.handlers...)
- c.handlers = append(c.handlers, handlers...)
- c.run()
- })
-}
-
-func (r *Router) Group(pattern string, fn func(), h ...Handler) {
- r.groups = append(r.groups, group{pattern, h})
- fn()
- r.groups = r.groups[:len(r.groups)-1]
-}
-
-// Get is a shortcut for r.Handle("GET", pattern, handlers)
-func (r *Router) Get(pattern string, h ...Handler) (leaf *Route) {
- leaf = r.Handle("GET", pattern, h)
- if r.autoHead {
- r.Head(pattern, h...)
- }
- return leaf
-}
-
-// Patch is a shortcut for r.Handle("PATCH", pattern, handlers)
-func (r *Router) Patch(pattern string, h ...Handler) *Route {
- return r.Handle("PATCH", pattern, h)
-}
-
-// Post is a shortcut for r.Handle("POST", pattern, handlers)
-func (r *Router) Post(pattern string, h ...Handler) *Route {
- return r.Handle("POST", pattern, h)
-}
-
-// Put is a shortcut for r.Handle("PUT", pattern, handlers)
-func (r *Router) Put(pattern string, h ...Handler) *Route {
- return r.Handle("PUT", pattern, h)
-}
-
-// Delete is a shortcut for r.Handle("DELETE", pattern, handlers)
-func (r *Router) Delete(pattern string, h ...Handler) *Route {
- return r.Handle("DELETE", pattern, h)
-}
-
-// Options is a shortcut for r.Handle("OPTIONS", pattern, handlers)
-func (r *Router) Options(pattern string, h ...Handler) *Route {
- return r.Handle("OPTIONS", pattern, h)
-}
-
-// Head is a shortcut for r.Handle("HEAD", pattern, handlers)
-func (r *Router) Head(pattern string, h ...Handler) *Route {
- return r.Handle("HEAD", pattern, h)
-}
-
-// Any is a shortcut for r.Handle("*", pattern, handlers)
-func (r *Router) Any(pattern string, h ...Handler) *Route {
- return r.Handle("*", pattern, h)
-}
-
-// Route is a shortcut for same handlers but different HTTP methods.
-//
-// Example:
-// m.Route("/", "GET,POST", h)
-func (r *Router) Route(pattern, methods string, h ...Handler) (route *Route) {
- for _, m := range strings.Split(methods, ",") {
- route = r.Handle(strings.TrimSpace(m), pattern, h)
- }
- return route
-}
-
-// Combo returns a combo router.
-func (r *Router) Combo(pattern string, h ...Handler) *ComboRouter {
- return &ComboRouter{r, pattern, h, map[string]bool{}, nil}
-}
-
-// NotFound configurates http.HandlerFunc which is called when no matching route is
-// found. If it is not set, http.NotFound is used.
-// Be sure to set 404 response code in your handler.
-func (r *Router) NotFound(handlers ...Handler) {
- handlers = validateAndWrapHandlers(handlers)
- r.notFound = func(rw http.ResponseWriter, req *http.Request) {
- c := r.m.createContext(rw, req)
- c.handlers = make([]Handler, 0, len(r.m.handlers)+len(handlers))
- c.handlers = append(c.handlers, r.m.handlers...)
- c.handlers = append(c.handlers, handlers...)
- c.run()
- }
-}
-
-// InternalServerError configurates handler which is called when route handler returns
-// error. If it is not set, default handler is used.
-// Be sure to set 500 response code in your handler.
-func (r *Router) InternalServerError(handlers ...Handler) {
- handlers = validateAndWrapHandlers(handlers)
- r.internalServerError = func(c *Context, err error) {
- c.index = 0
- c.handlers = handlers
- c.Map(err)
- c.run()
- }
-}
-
-// SetHandlerWrapper sets handlerWrapper for the router.
-func (r *Router) SetHandlerWrapper(f func(Handler) Handler) {
- r.handlerWrapper = f
-}
-
-func (r *Router) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
- if t, ok := r.routers[req.Method]; ok {
- // Fast match for static routes
- leaf := r.getLeaf(req.Method, req.URL.Path)
- if leaf != nil {
- leaf.handle(rw, req, nil)
- return
- }
-
- h, p, ok := t.Match(req.URL.EscapedPath())
- if ok {
- if splat, ok := p["*0"]; ok {
- p["*"] = splat // Easy name.
- }
- h(rw, req, p)
- return
- }
- }
-
- r.notFound(rw, req)
-}
-
-// URLFor builds path part of URL by given pair values.
-func (r *Router) URLFor(name string, pairs ...string) string {
- leaf, ok := r.namedRoutes[name]
- if !ok {
- panic("route with given name does not exists: " + name)
- }
- return leaf.URLPath(pairs...)
-}
-
-// ComboRouter represents a combo router.
-type ComboRouter struct {
- router *Router
- pattern string
- handlers []Handler
- methods map[string]bool // Registered methods.
-
- lastRoute *Route
-}
-
-func (cr *ComboRouter) checkMethod(name string) {
- if cr.methods[name] {
- panic("method '" + name + "' has already been registered")
- }
- cr.methods[name] = true
-}
-
-func (cr *ComboRouter) route(fn func(string, ...Handler) *Route, method string, h ...Handler) *ComboRouter {
- cr.checkMethod(method)
- cr.lastRoute = fn(cr.pattern, append(cr.handlers, h...)...)
- return cr
-}
-
-func (cr *ComboRouter) Get(h ...Handler) *ComboRouter {
- if cr.router.autoHead {
- cr.Head(h...)
- }
- return cr.route(cr.router.Get, "GET", h...)
-}
-
-func (cr *ComboRouter) Patch(h ...Handler) *ComboRouter {
- return cr.route(cr.router.Patch, "PATCH", h...)
-}
-
-func (cr *ComboRouter) Post(h ...Handler) *ComboRouter {
- return cr.route(cr.router.Post, "POST", h...)
-}
-
-func (cr *ComboRouter) Put(h ...Handler) *ComboRouter {
- return cr.route(cr.router.Put, "PUT", h...)
-}
-
-func (cr *ComboRouter) Delete(h ...Handler) *ComboRouter {
- return cr.route(cr.router.Delete, "DELETE", h...)
-}
-
-func (cr *ComboRouter) Options(h ...Handler) *ComboRouter {
- return cr.route(cr.router.Options, "OPTIONS", h...)
-}
-
-func (cr *ComboRouter) Head(h ...Handler) *ComboRouter {
- return cr.route(cr.router.Head, "HEAD", h...)
-}
-
-// Name sets name of ComboRouter route.
-func (cr *ComboRouter) Name(name string) {
- if cr.lastRoute == nil {
- panic("no corresponding route to be named")
- }
- cr.lastRoute.Name(name)
-}
+++ /dev/null
-// Copyright 2013 Martini Authors
-// Copyright 2014 The Macaron Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package macaron
-
-import (
- "encoding/base64"
- "fmt"
- "log"
- "net/http"
- "path"
- "path/filepath"
- "strings"
- "sync"
-)
-
-// StaticOptions is a struct for specifying configuration options for the macaron.Static middleware.
-type StaticOptions struct {
- // Prefix is the optional prefix used to serve the static directory content
- Prefix string
- // SkipLogging will disable [Static] log messages when a static file is served.
- SkipLogging bool
- // IndexFile defines which file to serve as index if it exists.
- IndexFile string
- // Expires defines which user-defined function to use for producing a HTTP Expires Header
- // https://developers.google.com/speed/docs/insights/LeverageBrowserCaching
- Expires func() string
- // ETag defines if we should add an ETag header
- // https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching#validating-cached-responses-with-etags
- ETag bool
- // FileSystem is the interface for supporting any implmentation of file system.
- FileSystem http.FileSystem
-}
-
-// FIXME: to be deleted.
-type staticMap struct {
- lock sync.RWMutex
- data map[string]*http.Dir
-}
-
-func (sm *staticMap) Set(dir *http.Dir) {
- sm.lock.Lock()
- defer sm.lock.Unlock()
-
- sm.data[string(*dir)] = dir
-}
-
-func (sm *staticMap) Get(name string) *http.Dir {
- sm.lock.RLock()
- defer sm.lock.RUnlock()
-
- return sm.data[name]
-}
-
-func (sm *staticMap) Delete(name string) {
- sm.lock.Lock()
- defer sm.lock.Unlock()
-
- delete(sm.data, name)
-}
-
-var statics = staticMap{sync.RWMutex{}, map[string]*http.Dir{}}
-
-// staticFileSystem implements http.FileSystem interface.
-type staticFileSystem struct {
- dir *http.Dir
-}
-
-func newStaticFileSystem(directory string) staticFileSystem {
- if !filepath.IsAbs(directory) {
- directory = filepath.Join(Root, directory)
- }
- dir := http.Dir(directory)
- statics.Set(&dir)
- return staticFileSystem{&dir}
-}
-
-func (fs staticFileSystem) Open(name string) (http.File, error) {
- return fs.dir.Open(name)
-}
-
-func prepareStaticOption(dir string, opt StaticOptions) StaticOptions {
- // Defaults
- if len(opt.IndexFile) == 0 {
- opt.IndexFile = "index.html"
- }
- // Normalize the prefix if provided
- if opt.Prefix != "" {
- // Ensure we have a leading '/'
- if opt.Prefix[0] != '/' {
- opt.Prefix = "/" + opt.Prefix
- }
- // Remove any trailing '/'
- opt.Prefix = strings.TrimRight(opt.Prefix, "/")
- }
- if opt.FileSystem == nil {
- opt.FileSystem = newStaticFileSystem(dir)
- }
- return opt
-}
-
-func prepareStaticOptions(dir string, options []StaticOptions) StaticOptions {
- var opt StaticOptions
- if len(options) > 0 {
- opt = options[0]
- }
- return prepareStaticOption(dir, opt)
-}
-
-func staticHandler(ctx *Context, log *log.Logger, opt StaticOptions) bool {
- if ctx.Req.Method != "GET" && ctx.Req.Method != "HEAD" {
- return false
- }
-
- file := ctx.Req.URL.Path
- // if we have a prefix, filter requests by stripping the prefix
- if opt.Prefix != "" {
- if !strings.HasPrefix(file, opt.Prefix) {
- return false
- }
- file = file[len(opt.Prefix):]
- if file != "" && file[0] != '/' {
- return false
- }
- }
-
- f, err := opt.FileSystem.Open(file)
- if err != nil {
- return false
- }
- defer f.Close()
-
- fi, err := f.Stat()
- if err != nil {
- return true // File exists but fail to open.
- }
-
- // Try to serve index file
- if fi.IsDir() {
- redirPath := path.Clean(ctx.Req.URL.Path)
- // path.Clean removes the trailing slash, so we need to add it back when
- // the original path has it.
- if strings.HasSuffix(ctx.Req.URL.Path, "/") {
- redirPath = redirPath + "/"
- }
- // Redirect if missing trailing slash.
- if !strings.HasSuffix(redirPath, "/") {
- http.Redirect(ctx.Resp, ctx.Req.Request, redirPath+"/", http.StatusFound)
- return true
- }
-
- file = path.Join(file, opt.IndexFile)
- f, err = opt.FileSystem.Open(file)
- if err != nil {
- return false // Discard error.
- }
- defer f.Close()
-
- fi, err = f.Stat()
- if err != nil || fi.IsDir() {
- return true
- }
- }
-
- if !opt.SkipLogging {
- log.Println("[Static] Serving " + file)
- }
-
- // Add an Expires header to the static content
- if opt.Expires != nil {
- ctx.Resp.Header().Set("Expires", opt.Expires())
- }
-
- if opt.ETag {
- tag := `"` + GenerateETag(fmt.Sprintf("%d", fi.Size()), fi.Name(), fi.ModTime().UTC().Format(http.TimeFormat)) + `"`
- ctx.Resp.Header().Set("ETag", tag)
- if ctx.Req.Header.Get("If-None-Match") == tag {
- ctx.Resp.WriteHeader(http.StatusNotModified)
- return true
- }
- }
-
- http.ServeContent(ctx.Resp, ctx.Req.Request, file, fi.ModTime(), f)
- return true
-}
-
-// GenerateETag generates an ETag based on size, filename and file modification time
-func GenerateETag(fileSize, fileName, modTime string) string {
- etag := fileSize + fileName + modTime
- return base64.StdEncoding.EncodeToString([]byte(etag))
-}
-
-// Static returns a middleware handler that serves static files in the given directory.
-func Static(directory string, staticOpt ...StaticOptions) Handler {
- opt := prepareStaticOptions(directory, staticOpt)
-
- return func(ctx *Context, log *log.Logger) {
- staticHandler(ctx, log, opt)
- }
-}
-
-// Statics registers multiple static middleware handlers all at once.
-func Statics(opt StaticOptions, dirs ...string) Handler {
- if len(dirs) == 0 {
- panic("no static directory is given")
- }
- opts := make([]StaticOptions, len(dirs))
- for i := range dirs {
- opts[i] = prepareStaticOption(dirs[i], opt)
- }
-
- return func(ctx *Context, log *log.Logger) {
- for i := range opts {
- if staticHandler(ctx, log, opts[i]) {
- return
- }
- }
- }
-}
+++ /dev/null
-// Copyright 2015 The Macaron Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package macaron
-
-import (
- "regexp"
- "strings"
-
- "github.com/unknwon/com"
-)
-
-type patternType int8
-
-const (
- _PATTERN_STATIC patternType = iota // /home
- _PATTERN_REGEXP // /:id([0-9]+)
- _PATTERN_PATH_EXT // /*.*
- _PATTERN_HOLDER // /:user
- _PATTERN_MATCH_ALL // /*
-)
-
-// Leaf represents a leaf route information.
-type Leaf struct {
- parent *Tree
-
- typ patternType
- pattern string
- rawPattern string // Contains wildcard instead of regexp
- wildcards []string
- reg *regexp.Regexp
- optional bool
-
- handle Handle
-}
-
-var wildcardPattern = regexp.MustCompile(`:[a-zA-Z0-9]+`)
-
-func isSpecialRegexp(pattern, regStr string, pos []int) bool {
- return len(pattern) >= pos[1]+len(regStr) && pattern[pos[1]:pos[1]+len(regStr)] == regStr
-}
-
-// getNextWildcard tries to find next wildcard and update pattern with corresponding regexp.
-func getNextWildcard(pattern string) (wildcard, _ string) {
- pos := wildcardPattern.FindStringIndex(pattern)
- if pos == nil {
- return "", pattern
- }
- wildcard = pattern[pos[0]:pos[1]]
-
- // Reach last character or no regexp is given.
- if len(pattern) == pos[1] {
- return wildcard, strings.Replace(pattern, wildcard, `(.+)`, 1)
- } else if pattern[pos[1]] != '(' {
- switch {
- case isSpecialRegexp(pattern, ":int", pos):
- pattern = strings.Replace(pattern, ":int", "([0-9]+)", 1)
- case isSpecialRegexp(pattern, ":string", pos):
- pattern = strings.Replace(pattern, ":string", "([\\w]+)", 1)
- default:
- return wildcard, strings.Replace(pattern, wildcard, `(.+)`, 1)
- }
- }
-
- // Cut out placeholder directly.
- return wildcard, pattern[:pos[0]] + pattern[pos[1]:]
-}
-
-func getWildcards(pattern string) (string, []string) {
- wildcards := make([]string, 0, 2)
-
- // Keep getting next wildcard until nothing is left.
- var wildcard string
- for {
- wildcard, pattern = getNextWildcard(pattern)
- if len(wildcard) > 0 {
- wildcards = append(wildcards, wildcard)
- } else {
- break
- }
- }
-
- return pattern, wildcards
-}
-
-// getRawPattern removes all regexp but keeps wildcards for building URL path.
-func getRawPattern(rawPattern string) string {
- rawPattern = strings.Replace(rawPattern, ":int", "", -1)
- rawPattern = strings.Replace(rawPattern, ":string", "", -1)
-
- for {
- startIdx := strings.Index(rawPattern, "(")
- if startIdx == -1 {
- break
- }
-
- closeIdx := strings.Index(rawPattern, ")")
- if closeIdx > -1 {
- rawPattern = rawPattern[:startIdx] + rawPattern[closeIdx+1:]
- }
- }
- return rawPattern
-}
-
-func checkPattern(pattern string) (typ patternType, rawPattern string, wildcards []string, reg *regexp.Regexp) {
- pattern = strings.TrimLeft(pattern, "?")
- rawPattern = getRawPattern(pattern)
-
- if pattern == "*" {
- typ = _PATTERN_MATCH_ALL
- } else if pattern == "*.*" {
- typ = _PATTERN_PATH_EXT
- } else if strings.Contains(pattern, ":") {
- typ = _PATTERN_REGEXP
- pattern, wildcards = getWildcards(pattern)
- if pattern == "(.+)" {
- typ = _PATTERN_HOLDER
- } else {
- reg = regexp.MustCompile(pattern)
- }
- }
- return typ, rawPattern, wildcards, reg
-}
-
-func NewLeaf(parent *Tree, pattern string, handle Handle) *Leaf {
- typ, rawPattern, wildcards, reg := checkPattern(pattern)
- optional := false
- if len(pattern) > 0 && pattern[0] == '?' {
- optional = true
- }
- return &Leaf{parent, typ, pattern, rawPattern, wildcards, reg, optional, handle}
-}
-
-// URLPath build path part of URL by given pair values.
-func (l *Leaf) URLPath(pairs ...string) string {
- if len(pairs)%2 != 0 {
- panic("number of pairs does not match")
- }
-
- urlPath := l.rawPattern
- parent := l.parent
- for parent != nil {
- urlPath = parent.rawPattern + "/" + urlPath
- parent = parent.parent
- }
- for i := 0; i < len(pairs); i += 2 {
- if len(pairs[i]) == 0 {
- panic("pair value cannot be empty: " + com.ToStr(i))
- } else if pairs[i][0] != ':' && pairs[i] != "*" && pairs[i] != "*.*" {
- pairs[i] = ":" + pairs[i]
- }
- urlPath = strings.Replace(urlPath, pairs[i], pairs[i+1], 1)
- }
- return urlPath
-}
-
-// Tree represents a router tree in Macaron.
-type Tree struct {
- parent *Tree
-
- typ patternType
- pattern string
- rawPattern string
- wildcards []string
- reg *regexp.Regexp
-
- subtrees []*Tree
- leaves []*Leaf
-}
-
-func NewSubtree(parent *Tree, pattern string) *Tree {
- typ, rawPattern, wildcards, reg := checkPattern(pattern)
- return &Tree{parent, typ, pattern, rawPattern, wildcards, reg, make([]*Tree, 0, 5), make([]*Leaf, 0, 5)}
-}
-
-func NewTree() *Tree {
- return NewSubtree(nil, "")
-}
-
-func (t *Tree) addLeaf(pattern string, handle Handle) *Leaf {
- for i := 0; i < len(t.leaves); i++ {
- if t.leaves[i].pattern == pattern {
- return t.leaves[i]
- }
- }
-
- leaf := NewLeaf(t, pattern, handle)
-
- // Add exact same leaf to grandparent/parent level without optional.
- if leaf.optional {
- parent := leaf.parent
- if parent.parent != nil {
- parent.parent.addLeaf(parent.pattern, handle)
- } else {
- parent.addLeaf("", handle) // Root tree can add as empty pattern.
- }
- }
-
- i := 0
- for ; i < len(t.leaves); i++ {
- if leaf.typ < t.leaves[i].typ {
- break
- }
- }
-
- if i == len(t.leaves) {
- t.leaves = append(t.leaves, leaf)
- } else {
- t.leaves = append(t.leaves[:i], append([]*Leaf{leaf}, t.leaves[i:]...)...)
- }
- return leaf
-}
-
-func (t *Tree) addSubtree(segment, pattern string, handle Handle) *Leaf {
- for i := 0; i < len(t.subtrees); i++ {
- if t.subtrees[i].pattern == segment {
- return t.subtrees[i].addNextSegment(pattern, handle)
- }
- }
-
- subtree := NewSubtree(t, segment)
- i := 0
- for ; i < len(t.subtrees); i++ {
- if subtree.typ < t.subtrees[i].typ {
- break
- }
- }
-
- if i == len(t.subtrees) {
- t.subtrees = append(t.subtrees, subtree)
- } else {
- t.subtrees = append(t.subtrees[:i], append([]*Tree{subtree}, t.subtrees[i:]...)...)
- }
- return subtree.addNextSegment(pattern, handle)
-}
-
-func (t *Tree) addNextSegment(pattern string, handle Handle) *Leaf {
- pattern = strings.TrimPrefix(pattern, "/")
-
- i := strings.Index(pattern, "/")
- if i == -1 {
- return t.addLeaf(pattern, handle)
- }
- return t.addSubtree(pattern[:i], pattern[i+1:], handle)
-}
-
-func (t *Tree) Add(pattern string, handle Handle) *Leaf {
- pattern = strings.TrimSuffix(pattern, "/")
- return t.addNextSegment(pattern, handle)
-}
-
-func (t *Tree) matchLeaf(globLevel int, url string, params Params) (Handle, bool) {
- url, err := PathUnescape(url)
- if err != nil {
- return nil, false
- }
- for i := 0; i < len(t.leaves); i++ {
- switch t.leaves[i].typ {
- case _PATTERN_STATIC:
- if t.leaves[i].pattern == url {
- return t.leaves[i].handle, true
- }
- case _PATTERN_REGEXP:
- results := t.leaves[i].reg.FindStringSubmatch(url)
- // Number of results and wildcasrd should be exact same.
- if len(results)-1 != len(t.leaves[i].wildcards) {
- break
- }
-
- for j := 0; j < len(t.leaves[i].wildcards); j++ {
- params[t.leaves[i].wildcards[j]] = results[j+1]
- }
- return t.leaves[i].handle, true
- case _PATTERN_PATH_EXT:
- j := strings.LastIndex(url, ".")
- if j > -1 {
- params[":path"] = url[:j]
- params[":ext"] = url[j+1:]
- } else {
- params[":path"] = url
- }
- return t.leaves[i].handle, true
- case _PATTERN_HOLDER:
- params[t.leaves[i].wildcards[0]] = url
- return t.leaves[i].handle, true
- case _PATTERN_MATCH_ALL:
- params["*"] = url
- params["*"+com.ToStr(globLevel)] = url
- return t.leaves[i].handle, true
- }
- }
- return nil, false
-}
-
-func (t *Tree) matchSubtree(globLevel int, segment, url string, params Params) (Handle, bool) {
- unescapedSegment, err := PathUnescape(segment)
- if err != nil {
- return nil, false
- }
- for i := 0; i < len(t.subtrees); i++ {
- switch t.subtrees[i].typ {
- case _PATTERN_STATIC:
- if t.subtrees[i].pattern == unescapedSegment {
- if handle, ok := t.subtrees[i].matchNextSegment(globLevel, url, params); ok {
- return handle, true
- }
- }
- case _PATTERN_REGEXP:
- results := t.subtrees[i].reg.FindStringSubmatch(unescapedSegment)
- if len(results)-1 != len(t.subtrees[i].wildcards) {
- break
- }
-
- for j := 0; j < len(t.subtrees[i].wildcards); j++ {
- params[t.subtrees[i].wildcards[j]] = results[j+1]
- }
- if handle, ok := t.subtrees[i].matchNextSegment(globLevel, url, params); ok {
- return handle, true
- }
- case _PATTERN_HOLDER:
- if handle, ok := t.subtrees[i].matchNextSegment(globLevel+1, url, params); ok {
- params[t.subtrees[i].wildcards[0]] = unescapedSegment
- return handle, true
- }
- case _PATTERN_MATCH_ALL:
- if handle, ok := t.subtrees[i].matchNextSegment(globLevel+1, url, params); ok {
- params["*"+com.ToStr(globLevel)] = unescapedSegment
- return handle, true
- }
- }
- }
-
- if len(t.leaves) > 0 {
- leaf := t.leaves[len(t.leaves)-1]
- unescapedURL, err := PathUnescape(segment + "/" + url)
- if err != nil {
- return nil, false
- }
- if leaf.typ == _PATTERN_PATH_EXT {
- j := strings.LastIndex(unescapedURL, ".")
- if j > -1 {
- params[":path"] = unescapedURL[:j]
- params[":ext"] = unescapedURL[j+1:]
- } else {
- params[":path"] = unescapedURL
- }
- return leaf.handle, true
- } else if leaf.typ == _PATTERN_MATCH_ALL {
- params["*"] = unescapedURL
- params["*"+com.ToStr(globLevel)] = unescapedURL
- return leaf.handle, true
- }
- }
- return nil, false
-}
-
-func (t *Tree) matchNextSegment(globLevel int, url string, params Params) (Handle, bool) {
- i := strings.Index(url, "/")
- if i == -1 {
- return t.matchLeaf(globLevel, url, params)
- }
- return t.matchSubtree(globLevel, url[:i], url[i+1:], params)
-}
-
-func (t *Tree) Match(url string) (Handle, Params, bool) {
- url = strings.TrimPrefix(url, "/")
- url = strings.TrimSuffix(url, "/")
- params := make(Params)
- handle, ok := t.matchNextSegment(0, url, params)
- return handle, params, ok
-}
-
-// MatchTest returns true if given URL is matched by given pattern.
-func MatchTest(pattern, url string) bool {
- t := NewTree()
- t.Add(pattern, nil)
- _, _, ok := t.Match(url)
- return ok
-}
+++ /dev/null
-// +build !go1.8
-
-// Copyright 2017 The Macaron Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package macaron
-
-import "net/url"
-
-// PathUnescape unescapes a path. Ideally, this function would use
-// url.PathUnescape(..), but the function was not introduced until go1.8.
-func PathUnescape(s string) (string, error) {
- return url.QueryUnescape(s)
-}
+++ /dev/null
-// +build go1.8
-
-// Copyright 2017 The Macaron Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package macaron
-
-import "net/url"
-
-// PathUnescape unescapes a path.
-func PathUnescape(s string) (string, error) {
- return url.PathUnescape(s)
-}
+++ /dev/null
-kind: pipeline
-name: default
-
-steps:
-- name: test
- image: golang:1.12
- environment:
- GOPROXY: https://goproxy.cn
- commands:
- - go build -v
- - go test -v -race -coverprofile=coverage.txt -covermode=atomic
\ No newline at end of file
+++ /dev/null
-ledis/tmp.db
-nodb/tmp.db
-/vendor
-/.idea
+++ /dev/null
-Apache License
-Version 2.0, January 2004
-http://www.apache.org/licenses/
-
-TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-1. Definitions.
-
-"License" shall mean the terms and conditions for use, reproduction, and
-distribution as defined by Sections 1 through 9 of this document.
-
-"Licensor" shall mean the copyright owner or entity authorized by the copyright
-owner that is granting the License.
-
-"Legal Entity" shall mean the union of the acting entity and all other entities
-that control, are controlled by, or are under common control with that entity.
-For the purposes of this definition, "control" means (i) the power, direct or
-indirect, to cause the direction or management of such entity, whether by
-contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
-outstanding shares, or (iii) beneficial ownership of such entity.
-
-"You" (or "Your") shall mean an individual or Legal Entity exercising
-permissions granted by this License.
-
-"Source" form shall mean the preferred form for making modifications, including
-but not limited to software source code, documentation source, and configuration
-files.
-
-"Object" form shall mean any form resulting from mechanical transformation or
-translation of a Source form, including but not limited to compiled object code,
-generated documentation, and conversions to other media types.
-
-"Work" shall mean the work of authorship, whether in Source or Object form, made
-available under the License, as indicated by a copyright notice that is included
-in or attached to the work (an example is provided in the Appendix below).
-
-"Derivative Works" shall mean any work, whether in Source or Object form, that
-is based on (or derived from) the Work and for which the editorial revisions,
-annotations, elaborations, or other modifications represent, as a whole, an
-original work of authorship. For the purposes of this License, Derivative Works
-shall not include works that remain separable from, or merely link (or bind by
-name) to the interfaces of, the Work and Derivative Works thereof.
-
-"Contribution" shall mean any work of authorship, including the original version
-of the Work and any modifications or additions to that Work or Derivative Works
-thereof, that is intentionally submitted to Licensor for inclusion in the Work
-by the copyright owner or by an individual or Legal Entity authorized to submit
-on behalf of the copyright owner. For the purposes of this definition,
-"submitted" means any form of electronic, verbal, or written communication sent
-to the Licensor or its representatives, including but not limited to
-communication on electronic mailing lists, source code control systems, and
-issue tracking systems that are managed by, or on behalf of, the Licensor for
-the purpose of discussing and improving the Work, but excluding communication
-that is conspicuously marked or otherwise designated in writing by the copyright
-owner as "Not a Contribution."
-
-"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
-of whom a Contribution has been received by Licensor and subsequently
-incorporated within the Work.
-
-2. Grant of Copyright License.
-
-Subject to the terms and conditions of this License, each Contributor hereby
-grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
-irrevocable copyright license to reproduce, prepare Derivative Works of,
-publicly display, publicly perform, sublicense, and distribute the Work and such
-Derivative Works in Source or Object form.
-
-3. Grant of Patent License.
-
-Subject to the terms and conditions of this License, each Contributor hereby
-grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
-irrevocable (except as stated in this section) patent license to make, have
-made, use, offer to sell, sell, import, and otherwise transfer the Work, where
-such license applies only to those patent claims licensable by such Contributor
-that are necessarily infringed by their Contribution(s) alone or by combination
-of their Contribution(s) with the Work to which such Contribution(s) was
-submitted. If You institute patent litigation against any entity (including a
-cross-claim or counterclaim in a lawsuit) alleging that the Work or a
-Contribution incorporated within the Work constitutes direct or contributory
-patent infringement, then any patent licenses granted to You under this License
-for that Work shall terminate as of the date such litigation is filed.
-
-4. Redistribution.
-
-You may reproduce and distribute copies of the Work or Derivative Works thereof
-in any medium, with or without modifications, and in Source or Object form,
-provided that You meet the following conditions:
-
-You must give any other recipients of the Work or Derivative Works a copy of
-this License; and
-You must cause any modified files to carry prominent notices stating that You
-changed the files; and
-You must retain, in the Source form of any Derivative Works that You distribute,
-all copyright, patent, trademark, and attribution notices from the Source form
-of the Work, excluding those notices that do not pertain to any part of the
-Derivative Works; and
-If the Work includes a "NOTICE" text file as part of its distribution, then any
-Derivative Works that You distribute must include a readable copy of the
-attribution notices contained within such NOTICE file, excluding those notices
-that do not pertain to any part of the Derivative Works, in at least one of the
-following places: within a NOTICE text file distributed as part of the
-Derivative Works; within the Source form or documentation, if provided along
-with the Derivative Works; or, within a display generated by the Derivative
-Works, if and wherever such third-party notices normally appear. The contents of
-the NOTICE file are for informational purposes only and do not modify the
-License. You may add Your own attribution notices within Derivative Works that
-You distribute, alongside or as an addendum to the NOTICE text from the Work,
-provided that such additional attribution notices cannot be construed as
-modifying the License.
-You may add Your own copyright statement to Your modifications and may provide
-additional or different license terms and conditions for use, reproduction, or
-distribution of Your modifications, or for any such Derivative Works as a whole,
-provided Your use, reproduction, and distribution of the Work otherwise complies
-with the conditions stated in this License.
-
-5. Submission of Contributions.
-
-Unless You explicitly state otherwise, any Contribution intentionally submitted
-for inclusion in the Work by You to the Licensor shall be under the terms and
-conditions of this License, without any additional terms or conditions.
-Notwithstanding the above, nothing herein shall supersede or modify the terms of
-any separate license agreement you may have executed with Licensor regarding
-such Contributions.
-
-6. Trademarks.
-
-This License does not grant permission to use the trade names, trademarks,
-service marks, or product names of the Licensor, except as required for
-reasonable and customary use in describing the origin of the Work and
-reproducing the content of the NOTICE file.
-
-7. Disclaimer of Warranty.
-
-Unless required by applicable law or agreed to in writing, Licensor provides the
-Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
-including, without limitation, any warranties or conditions of TITLE,
-NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
-solely responsible for determining the appropriateness of using or
-redistributing the Work and assume any risks associated with Your exercise of
-permissions under this License.
-
-8. Limitation of Liability.
-
-In no event and under no legal theory, whether in tort (including negligence),
-contract, or otherwise, unless required by applicable law (such as deliberate
-and grossly negligent acts) or agreed to in writing, shall any Contributor be
-liable to You for damages, including any direct, indirect, special, incidental,
-or consequential damages of any character arising as a result of this License or
-out of the use or inability to use the Work (including but not limited to
-damages for loss of goodwill, work stoppage, computer failure or malfunction, or
-any and all other commercial damages or losses), even if such Contributor has
-been advised of the possibility of such damages.
-
-9. Accepting Warranty or Additional Liability.
-
-While redistributing the Work or Derivative Works thereof, You may choose to
-offer, and charge a fee for, acceptance of support, warranty, indemnity, or
-other liability obligations and/or rights consistent with this License. However,
-in accepting such obligations, You may act only on Your own behalf and on Your
-sole responsibility, not on behalf of any other Contributor, and only if You
-agree to indemnify, defend, and hold each Contributor harmless for any liability
-incurred by, or claims asserted against, such Contributor by reason of your
-accepting any such warranty or additional liability.
-
-END OF TERMS AND CONDITIONS
-
-APPENDIX: How to apply the Apache License to your work
-
-To apply the Apache License to your work, attach the following boilerplate
-notice, with the fields enclosed by brackets "[]" replaced with your own
-identifying information. (Don't include the brackets!) The text should be
-enclosed in the appropriate comment syntax for the file format. We also
-recommend that a file or class name and description of purpose be included on
-the same "printed page" as the copyright notice for easier identification within
-third-party archives.
-
- Copyright [yyyy] [name of copyright owner]
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
\ No newline at end of file
+++ /dev/null
-# session
-
-Middleware session provides session management for [Macaron](https://gitea.com/macaron/macaron). It can use many session providers, including memory, file, Redis, Memcache, PostgreSQL, MySQL, Couchbase, Ledis and Nodb.
-
-### Installation
-
-The minimum requirement of Go is 1.11 .
-
- go get gitea.com/macaron/session
-
-## Getting Help
-
-- [API Reference](https://gowalker.org/gitea.com/macaron/session)
-- [Documentation](https://go-macaron.com/docs/middlewares/session)
-
-## Credits
-
-This package is a modified version of [go-macaron/session](github.com/go-macaron/session).
-
-## License
-
-This project is under the Apache License, Version 2.0. See the [LICENSE](LICENSE) file for the full license text.
\ No newline at end of file
+++ /dev/null
-// Copyright 2013 Beego Authors
-// Copyright 2014 The Macaron Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package session
-
-import (
- "strings"
- "sync"
-
- "gitea.com/macaron/session"
- "github.com/couchbase/go-couchbase"
-)
-
-// CouchbaseSessionStore represents a couchbase session store implementation.
-type CouchbaseSessionStore struct {
- b *couchbase.Bucket
- sid string
- lock sync.RWMutex
- data map[interface{}]interface{}
- maxlifetime int64
-}
-
-// Set sets value to given key in session.
-func (s *CouchbaseSessionStore) Set(key, val interface{}) error {
- s.lock.Lock()
- defer s.lock.Unlock()
-
- s.data[key] = val
- return nil
-}
-
-// Get gets value by given key in session.
-func (s *CouchbaseSessionStore) Get(key interface{}) interface{} {
- s.lock.RLock()
- defer s.lock.RUnlock()
-
- return s.data[key]
-}
-
-// Delete delete a key from session.
-func (s *CouchbaseSessionStore) Delete(key interface{}) error {
- s.lock.Lock()
- defer s.lock.Unlock()
-
- delete(s.data, key)
- return nil
-}
-
-// ID returns current session ID.
-func (s *CouchbaseSessionStore) ID() string {
- return s.sid
-}
-
-// Release releases resource and save data to provider.
-func (s *CouchbaseSessionStore) Release() error {
- defer s.b.Close()
-
- // Skip encoding if the data is empty
- if len(s.data) == 0 {
- return nil
- }
-
- data, err := session.EncodeGob(s.data)
- if err != nil {
- return err
- }
-
- return s.b.Set(s.sid, int(s.maxlifetime), data)
-}
-
-// Flush deletes all session data.
-func (s *CouchbaseSessionStore) Flush() error {
- s.lock.Lock()
- defer s.lock.Unlock()
-
- s.data = make(map[interface{}]interface{})
- return nil
-}
-
-// CouchbaseProvider represents a couchbase session provider implementation.
-type CouchbaseProvider struct {
- maxlifetime int64
- connStr string
- pool string
- bucket string
- b *couchbase.Bucket
-}
-
-func (cp *CouchbaseProvider) getBucket() *couchbase.Bucket {
- c, err := couchbase.Connect(cp.connStr)
- if err != nil {
- return nil
- }
-
- pool, err := c.GetPool(cp.pool)
- if err != nil {
- return nil
- }
-
- bucket, err := pool.GetBucket(cp.bucket)
- if err != nil {
- return nil
- }
-
- return bucket
-}
-
-// Init initializes memory session provider.
-// connStr is couchbase server REST/JSON URL
-// e.g. http://host:port/, Pool, Bucket
-func (p *CouchbaseProvider) Init(maxlifetime int64, connStr string) error {
- p.maxlifetime = maxlifetime
- configs := strings.Split(connStr, ",")
- if len(configs) > 0 {
- p.connStr = configs[0]
- }
- if len(configs) > 1 {
- p.pool = configs[1]
- }
- if len(configs) > 2 {
- p.bucket = configs[2]
- }
-
- return nil
-}
-
-// Read returns raw session store by session ID.
-func (p *CouchbaseProvider) Read(sid string) (session.RawStore, error) {
- p.b = p.getBucket()
-
- var doc []byte
-
- err := p.b.Get(sid, &doc)
- var kv map[interface{}]interface{}
- if doc == nil {
- kv = make(map[interface{}]interface{})
- } else {
- kv, err = session.DecodeGob(doc)
- if err != nil {
- return nil, err
- }
- }
-
- cs := &CouchbaseSessionStore{b: p.b, sid: sid, data: kv, maxlifetime: p.maxlifetime}
- return cs, nil
-}
-
-// Exist returns true if session with given ID exists.
-func (p *CouchbaseProvider) Exist(sid string) bool {
- p.b = p.getBucket()
- defer p.b.Close()
-
- var doc []byte
-
- if err := p.b.Get(sid, &doc); err != nil || doc == nil {
- return false
- } else {
- return true
- }
-}
-
-// Destroy deletes a session by session ID.
-func (p *CouchbaseProvider) Destroy(sid string) error {
- p.b = p.getBucket()
- defer p.b.Close()
-
- p.b.Delete(sid)
- return nil
-}
-
-// Regenerate regenerates a session store from old session ID to new one.
-func (p *CouchbaseProvider) Regenerate(oldsid, sid string) (session.RawStore, error) {
- p.b = p.getBucket()
-
- var doc []byte
- if err := p.b.Get(oldsid, &doc); err != nil || doc == nil {
- p.b.Set(sid, int(p.maxlifetime), "")
- } else {
- err := p.b.Delete(oldsid)
- if err != nil {
- return nil, err
- }
- _, _ = p.b.Add(sid, int(p.maxlifetime), doc)
- }
-
- err := p.b.Get(sid, &doc)
- if err != nil {
- return nil, err
- }
- var kv map[interface{}]interface{}
- if doc == nil {
- kv = make(map[interface{}]interface{})
- } else {
- kv, err = session.DecodeGob(doc)
- if err != nil {
- return nil, err
- }
- }
-
- cs := &CouchbaseSessionStore{b: p.b, sid: sid, data: kv, maxlifetime: p.maxlifetime}
- return cs, nil
-}
-
-// Count counts and returns number of sessions.
-func (p *CouchbaseProvider) Count() int {
- // FIXME
- return 0
-}
-
-// GC calls GC to clean expired sessions.
-func (p *CouchbaseProvider) GC() {}
-
-func init() {
- session.Register("couchbase", &CouchbaseProvider{})
-}
+++ /dev/null
-// Copyright 2013 Beego Authors
-// Copyright 2014 The Macaron Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package session
-
-import (
- "fmt"
- "io/ioutil"
- "log"
- "os"
- "path"
- "path/filepath"
- "sync"
- "time"
-
- "github.com/unknwon/com"
-)
-
-// FileStore represents a file session store implementation.
-type FileStore struct {
- p *FileProvider
- sid string
- lock sync.RWMutex
- data map[interface{}]interface{}
-}
-
-// NewFileStore creates and returns a file session store.
-func NewFileStore(p *FileProvider, sid string, kv map[interface{}]interface{}) *FileStore {
- return &FileStore{
- p: p,
- sid: sid,
- data: kv,
- }
-}
-
-// Set sets value to given key in session.
-func (s *FileStore) Set(key, val interface{}) error {
- s.lock.Lock()
- defer s.lock.Unlock()
-
- s.data[key] = val
- return nil
-}
-
-// Get gets value by given key in session.
-func (s *FileStore) Get(key interface{}) interface{} {
- s.lock.RLock()
- defer s.lock.RUnlock()
-
- return s.data[key]
-}
-
-// Delete delete a key from session.
-func (s *FileStore) Delete(key interface{}) error {
- s.lock.Lock()
- defer s.lock.Unlock()
-
- delete(s.data, key)
- return nil
-}
-
-// ID returns current session ID.
-func (s *FileStore) ID() string {
- return s.sid
-}
-
-// Release releases resource and save data to provider.
-func (s *FileStore) Release() error {
- s.p.lock.Lock()
- defer s.p.lock.Unlock()
-
- // Skip encoding if the data is empty
- if len(s.data) == 0 {
- return nil
- }
-
- data, err := EncodeGob(s.data)
- if err != nil {
- return err
- }
-
- return ioutil.WriteFile(s.p.filepath(s.sid), data, 0600)
-}
-
-// Flush deletes all session data.
-func (s *FileStore) Flush() error {
- s.lock.Lock()
- defer s.lock.Unlock()
-
- s.data = make(map[interface{}]interface{})
- return nil
-}
-
-// FileProvider represents a file session provider implementation.
-type FileProvider struct {
- lock sync.RWMutex
- maxlifetime int64
- rootPath string
-}
-
-// Init initializes file session provider with given root path.
-func (p *FileProvider) Init(maxlifetime int64, rootPath string) error {
- p.lock.Lock()
- p.maxlifetime = maxlifetime
- p.rootPath = rootPath
- p.lock.Unlock()
- return nil
-}
-
-func (p *FileProvider) filepath(sid string) string {
- return path.Join(p.rootPath, string(sid[0]), string(sid[1]), sid)
-}
-
-// Read returns raw session store by session ID.
-func (p *FileProvider) Read(sid string) (_ RawStore, err error) {
- filename := p.filepath(sid)
- if err = os.MkdirAll(path.Dir(filename), 0700); err != nil {
- return nil, err
- }
- p.lock.RLock()
- defer p.lock.RUnlock()
-
- var f *os.File
- ok := false
- if com.IsFile(filename) {
- modTime, err := com.FileMTime(filename)
- if err != nil {
- return nil, err
- }
- ok = (modTime + p.maxlifetime) >= time.Now().Unix()
- }
- if ok {
- f, err = os.OpenFile(filename, os.O_RDONLY, 0600)
- } else {
- f, err = os.Create(filename)
- }
- if err != nil {
- return nil, err
- }
- defer f.Close()
-
- if err = os.Chtimes(filename, time.Now(), time.Now()); err != nil {
- return nil, err
- }
-
- var kv map[interface{}]interface{}
- data, err := ioutil.ReadAll(f)
- if err != nil {
- return nil, err
- }
- if len(data) == 0 {
- kv = make(map[interface{}]interface{})
- } else {
- kv, err = DecodeGob(data)
- if err != nil {
- return nil, err
- }
- }
- return NewFileStore(p, sid, kv), nil
-}
-
-// Exist returns true if session with given ID exists.
-func (p *FileProvider) Exist(sid string) bool {
- p.lock.RLock()
- defer p.lock.RUnlock()
- return com.IsFile(p.filepath(sid))
-}
-
-// Destroy deletes a session by session ID.
-func (p *FileProvider) Destroy(sid string) error {
- p.lock.Lock()
- defer p.lock.Unlock()
- return os.Remove(p.filepath(sid))
-}
-
-func (p *FileProvider) regenerate(oldsid, sid string) (err error) {
- p.lock.Lock()
- defer p.lock.Unlock()
-
- filename := p.filepath(sid)
- if com.IsExist(filename) {
- return fmt.Errorf("new sid '%s' already exists", sid)
- }
-
- oldname := p.filepath(oldsid)
- if !com.IsFile(oldname) {
- data, err := EncodeGob(make(map[interface{}]interface{}))
- if err != nil {
- return err
- }
- if err = os.MkdirAll(path.Dir(oldname), 0700); err != nil {
- return err
- }
- if err = ioutil.WriteFile(oldname, data, 0600); err != nil {
- return err
- }
- }
-
- if err = os.MkdirAll(path.Dir(filename), 0700); err != nil {
- return err
- }
- if err = os.Rename(oldname, filename); err != nil {
- return err
- }
- return nil
-}
-
-// Regenerate regenerates a session store from old session ID to new one.
-func (p *FileProvider) Regenerate(oldsid, sid string) (_ RawStore, err error) {
- if err := p.regenerate(oldsid, sid); err != nil {
- return nil, err
- }
-
- return p.Read(sid)
-}
-
-// Count counts and returns number of sessions.
-func (p *FileProvider) Count() int {
- count := 0
- if err := filepath.Walk(p.rootPath, func(path string, fi os.FileInfo, err error) error {
- if err != nil {
- return err
- }
-
- if !fi.IsDir() {
- count++
- }
- return nil
- }); err != nil {
- log.Printf("error counting session files: %v", err)
- return 0
- }
- return count
-}
-
-// GC calls GC to clean expired sessions.
-func (p *FileProvider) GC() {
- p.lock.RLock()
- defer p.lock.RUnlock()
-
- if !com.IsExist(p.rootPath) {
- return
- }
-
- if err := filepath.Walk(p.rootPath, func(path string, fi os.FileInfo, err error) error {
- if err != nil {
- return err
- }
-
- if !fi.IsDir() &&
- (fi.ModTime().Unix()+p.maxlifetime) < time.Now().Unix() {
- return os.Remove(path)
- }
- return nil
- }); err != nil {
- log.Printf("error garbage collecting session files: %v", err)
- }
-}
-
-func init() {
- Register("file", &FileProvider{})
-}
+++ /dev/null
-// Copyright 2018 The Macaron Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package session
-
-import (
- "net/url"
-
- "gitea.com/macaron/macaron"
-)
-
-type Flash struct {
- ctx *macaron.Context
- url.Values
- ErrorMsg, WarningMsg, InfoMsg, SuccessMsg string
-}
-
-func (f *Flash) set(name, msg string, current ...bool) {
- isShow := false
- if (len(current) == 0 && macaron.FlashNow) ||
- (len(current) > 0 && current[0]) {
- isShow = true
- }
-
- if isShow {
- f.ctx.Data["Flash"] = f
- } else {
- f.Set(name, msg)
- }
-}
-
-func (f *Flash) Error(msg string, current ...bool) {
- f.ErrorMsg = msg
- f.set("error", msg, current...)
-}
-
-func (f *Flash) Warning(msg string, current ...bool) {
- f.WarningMsg = msg
- f.set("warning", msg, current...)
-}
-
-func (f *Flash) Info(msg string, current ...bool) {
- f.InfoMsg = msg
- f.set("info", msg, current...)
-}
-
-func (f *Flash) Success(msg string, current ...bool) {
- f.SuccessMsg = msg
- f.set("success", msg, current...)
-}
+++ /dev/null
-module gitea.com/macaron/session
-
-go 1.11
-
-require (
- gitea.com/lunny/nodb v0.0.0-20200923032308-3238c4655727
- gitea.com/macaron/macaron v1.5.0
- github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668
- github.com/couchbase/go-couchbase v0.0.0-20201026062457-7b3be89bbd89
- github.com/couchbase/gomemcached v0.1.0 // indirect
- github.com/couchbase/goutils v0.0.0-20201030094643-5e82bb967e67 // indirect
- github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76 // indirect
- github.com/edsrzf/mmap-go v1.0.0 // indirect
- github.com/go-redis/redis v6.15.2+incompatible
- github.com/go-sql-driver/mysql v1.4.1
- github.com/golang/snappy v0.0.2 // indirect
- github.com/lib/pq v1.2.0
- github.com/pkg/errors v0.8.1 // indirect
- github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726 // indirect
- github.com/siddontang/ledisdb v0.0.0-20190202134119-8ceb77e66a92
- github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d // indirect
- github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337
- github.com/stretchr/testify v1.3.0 // indirect
- github.com/unknwon/com v1.0.1
- golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 // indirect
- golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 // indirect
- google.golang.org/appengine v1.6.1 // indirect
- gopkg.in/ini.v1 v1.62.0
- gopkg.in/yaml.v2 v2.2.2 // indirect
-)
+++ /dev/null
-gitea.com/lunny/log v0.0.0-20190322053110-01b5df579c4e h1:r1en/D7xJmcY24VkHkjkcJFa+7ZWubVWPBrvsHkmHxk=
-gitea.com/lunny/log v0.0.0-20190322053110-01b5df579c4e/go.mod h1:uJEsN4LQpeGYRCjuPXPZBClU7N5pWzGuyF4uqLpE/e0=
-gitea.com/lunny/nodb v0.0.0-20200923032308-3238c4655727 h1:ZF2Bd6rqVlwhIDhYiS0uGYcT+GaVNGjuKVJkTNqWMIs=
-gitea.com/lunny/nodb v0.0.0-20200923032308-3238c4655727/go.mod h1:h0OwsgcpJLSYtHcM5+Xciw9OEeuxi6ty4HDiO8C7aIY=
-gitea.com/macaron/inject v0.0.0-20190805023432-d4c86e31027a/go.mod h1:h6E4kLao1Yko6DOU6QDnQPcuoNzvbZqzj2mtPcEn1aM=
-gitea.com/macaron/macaron v1.5.0/go.mod h1:P7hfDbQjcW22lkYkXlxdRIfWOXxH2+K4EogN4Q0UlLY=
-github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668 h1:U/lr3Dgy4WK+hNk4tyD+nuGjpVLPEHuJSFXMw11/HPA=
-github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
-github.com/couchbase/go-couchbase v0.0.0-20201026062457-7b3be89bbd89 h1:uNLXQ6QO1TocD8BaN/KkRki0Xw0brCM1PKl/ZA5pgfs=
-github.com/couchbase/go-couchbase v0.0.0-20201026062457-7b3be89bbd89/go.mod h1:+/bddYDxXsf9qt0xpDUtRR47A2GjaXmGGAqQ/k3GJ8A=
-github.com/couchbase/gomemcached v0.1.0 h1:whUde87n8CScx8ckMp2En5liqAlcuG3aKy/BQeBPu84=
-github.com/couchbase/gomemcached v0.1.0/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c=
-github.com/couchbase/goutils v0.0.0-20201030094643-5e82bb967e67 h1:NCqJ6fwen6YP0WlV/IyibaT0kPt3JEI1rA62V/UPKT4=
-github.com/couchbase/goutils v0.0.0-20201030094643-5e82bb967e67/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
-github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76 h1:Lgdd/Qp96Qj8jqLpq2cI1I1X7BJnu06efS+XkhRoLUQ=
-github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
-github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw=
-github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
-github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
-github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
-github.com/go-redis/redis v6.15.2+incompatible h1:9SpNVG76gr6InJGxoZ6IuuxaCOQwDAhzyXg+Bs+0Sb4=
-github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
-github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
-github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
-github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
-github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
-github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
-github.com/golang/snappy v0.0.2 h1:aeE13tS0IiQgFjYdoL8qN3K1N2bXXtI6Vi51/y7BpMw=
-github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
-github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
-github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
-github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
-github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
-github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
-github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
-github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
-github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
-github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
-github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q=
-github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
-github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
-github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
-github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
-github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM=
-github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
-github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
-github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
-github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726 h1:xT+JlYxNGqyT+XcU8iUrN18JYed2TvG9yN5ULG2jATM=
-github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
-github.com/siddontang/go-snappy v0.0.0-20140704025258-d8f7bb82a96d h1:qQWKKOvHN7Q9c6GdmUteCef2F9ubxMpxY1IKwpIKz68=
-github.com/siddontang/go-snappy v0.0.0-20140704025258-d8f7bb82a96d/go.mod h1:vq0tzqLRu6TS7Id0wMo2N5QzJoKedVeovOpHjnykSzY=
-github.com/siddontang/ledisdb v0.0.0-20190202134119-8ceb77e66a92 h1:qvsJwGToa8rxb42cDRhkbKeX2H5N8BH+s2aUikGt8mI=
-github.com/siddontang/ledisdb v0.0.0-20190202134119-8ceb77e66a92/go.mod h1:mF1DpOSOUiJRMR+FDqaqu3EBqrybQtrDDszLUZ6oxPg=
-github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d h1:NVwnfyR3rENtlz62bcrkXME3INVUa4lcdGt+opvxExs=
-github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=
-github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
-github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY=
-github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
-github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
-github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
-github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8=
-github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
-github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
-github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
-github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
-github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
-golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
-golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
-golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk=
-golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190606165138-5da285871e9c h1:+EXw7AwNOKzPFXMZ1yNjO40aWCh3PIquJB2fYlv9wcs=
-golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
-golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I=
-google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
-gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
-gopkg.in/ini.v1 v1.44.0 h1:YRJzTUp0kSYWUVFF5XAbDFfyiqwsl0Vb9R8TVP5eRi0=
-gopkg.in/ini.v1 v1.44.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
-gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
-gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
-gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+++ /dev/null
-// Copyright 2013 Beego Authors
-// Copyright 2014 The Macaron Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package session
-
-import (
- "fmt"
- "strings"
- "sync"
-
- "gitea.com/macaron/session"
- "github.com/bradfitz/gomemcache/memcache"
-)
-
-// MemcacheStore represents a memcache session store implementation.
-type MemcacheStore struct {
- c *memcache.Client
- sid string
- expire int32
- lock sync.RWMutex
- data map[interface{}]interface{}
-}
-
-// NewMemcacheStore creates and returns a memcache session store.
-func NewMemcacheStore(c *memcache.Client, sid string, expire int32, kv map[interface{}]interface{}) *MemcacheStore {
- return &MemcacheStore{
- c: c,
- sid: sid,
- expire: expire,
- data: kv,
- }
-}
-
-func NewItem(sid string, data []byte, expire int32) *memcache.Item {
- return &memcache.Item{
- Key: sid,
- Value: data,
- Expiration: expire,
- }
-}
-
-// Set sets value to given key in session.
-func (s *MemcacheStore) Set(key, val interface{}) error {
- s.lock.Lock()
- defer s.lock.Unlock()
-
- s.data[key] = val
- return nil
-}
-
-// Get gets value by given key in session.
-func (s *MemcacheStore) Get(key interface{}) interface{} {
- s.lock.RLock()
- defer s.lock.RUnlock()
-
- return s.data[key]
-}
-
-// Delete delete a key from session.
-func (s *MemcacheStore) Delete(key interface{}) error {
- s.lock.Lock()
- defer s.lock.Unlock()
-
- delete(s.data, key)
- return nil
-}
-
-// ID returns current session ID.
-func (s *MemcacheStore) ID() string {
- return s.sid
-}
-
-// Release releases resource and save data to provider.
-func (s *MemcacheStore) Release() error {
- // Skip encoding if the data is empty
- if len(s.data) == 0 {
- return nil
- }
-
- data, err := session.EncodeGob(s.data)
- if err != nil {
- return err
- }
-
- return s.c.Set(NewItem(s.sid, data, s.expire))
-}
-
-// Flush deletes all session data.
-func (s *MemcacheStore) Flush() error {
- s.lock.Lock()
- defer s.lock.Unlock()
-
- s.data = make(map[interface{}]interface{})
- return nil
-}
-
-// MemcacheProvider represents a memcache session provider implementation.
-type MemcacheProvider struct {
- c *memcache.Client
- expire int32
-}
-
-// Init initializes memcache session provider.
-// connStrs: 127.0.0.1:9090;127.0.0.1:9091
-func (p *MemcacheProvider) Init(expire int64, connStrs string) error {
- p.expire = int32(expire)
- p.c = memcache.New(strings.Split(connStrs, ";")...)
- return nil
-}
-
-// Read returns raw session store by session ID.
-func (p *MemcacheProvider) Read(sid string) (session.RawStore, error) {
- if !p.Exist(sid) {
- if err := p.c.Set(NewItem(sid, []byte(""), p.expire)); err != nil {
- return nil, err
- }
- }
-
- var kv map[interface{}]interface{}
- item, err := p.c.Get(sid)
- if err != nil {
- return nil, err
- }
- if len(item.Value) == 0 {
- kv = make(map[interface{}]interface{})
- } else {
- kv, err = session.DecodeGob(item.Value)
- if err != nil {
- return nil, err
- }
- }
-
- return NewMemcacheStore(p.c, sid, p.expire, kv), nil
-}
-
-// Exist returns true if session with given ID exists.
-func (p *MemcacheProvider) Exist(sid string) bool {
- _, err := p.c.Get(sid)
- return err == nil
-}
-
-// Destroy deletes a session by session ID.
-func (p *MemcacheProvider) Destroy(sid string) error {
- return p.c.Delete(sid)
-}
-
-// Regenerate regenerates a session store from old session ID to new one.
-func (p *MemcacheProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err error) {
- if p.Exist(sid) {
- return nil, fmt.Errorf("new sid '%s' already exists", sid)
- }
-
- item := NewItem(sid, []byte(""), p.expire)
- if p.Exist(oldsid) {
- item, err = p.c.Get(oldsid)
- if err != nil {
- return nil, err
- } else if err = p.c.Delete(oldsid); err != nil {
- return nil, err
- }
- item.Key = sid
- }
- if err = p.c.Set(item); err != nil {
- return nil, err
- }
-
- var kv map[interface{}]interface{}
- if len(item.Value) == 0 {
- kv = make(map[interface{}]interface{})
- } else {
- kv, err = session.DecodeGob(item.Value)
- if err != nil {
- return nil, err
- }
- }
-
- return NewMemcacheStore(p.c, sid, p.expire, kv), nil
-}
-
-// Count counts and returns number of sessions.
-func (p *MemcacheProvider) Count() int {
- // FIXME: how come this library does not have Stats method?
- return -1
-}
-
-// GC calls GC to clean expired sessions.
-func (p *MemcacheProvider) GC() {}
-
-func init() {
- session.Register("memcache", &MemcacheProvider{})
-}
+++ /dev/null
-ignore
\ No newline at end of file
+++ /dev/null
-// Copyright 2013 Beego Authors\r
-// Copyright 2014 The Macaron Authors\r
-//\r
-// Licensed under the Apache License, Version 2.0 (the "License"): you may\r
-// not use this file except in compliance with the License. You may obtain\r
-// a copy of the License at\r
-//\r
-// http://www.apache.org/licenses/LICENSE-2.0\r
-//\r
-// Unless required by applicable law or agreed to in writing, software\r
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT\r
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\r
-// License for the specific language governing permissions and limitations\r
-// under the License.\r
-\r
-package session\r
-\r
-import (\r
- "container/list"\r
- "fmt"\r
- "sync"\r
- "time"\r
-)\r
-\r
-// MemStore represents a in-memory session store implementation.\r
-type MemStore struct {\r
- sid string\r
- lock sync.RWMutex\r
- data map[interface{}]interface{}\r
- lastAccess time.Time\r
-}\r
-\r
-// NewMemStore creates and returns a memory session store.\r
-func NewMemStore(sid string) *MemStore {\r
- return &MemStore{\r
- sid: sid,\r
- data: make(map[interface{}]interface{}),\r
- lastAccess: time.Now(),\r
- }\r
-}\r
-\r
-// Set sets value to given key in session.\r
-func (s *MemStore) Set(key, val interface{}) error {\r
- s.lock.Lock()\r
- defer s.lock.Unlock()\r
-\r
- s.data[key] = val\r
- return nil\r
-}\r
-\r
-// Get gets value by given key in session.\r
-func (s *MemStore) Get(key interface{}) interface{} {\r
- s.lock.RLock()\r
- defer s.lock.RUnlock()\r
-\r
- return s.data[key]\r
-}\r
-\r
-// Delete deletes a key from session.\r
-func (s *MemStore) Delete(key interface{}) error {\r
- s.lock.Lock()\r
- defer s.lock.Unlock()\r
-\r
- delete(s.data, key)\r
- return nil\r
-}\r
-\r
-// ID returns current session ID.\r
-func (s *MemStore) ID() string {\r
- return s.sid\r
-}\r
-\r
-// Release releases resource and save data to provider.\r
-func (_ *MemStore) Release() error {\r
- return nil\r
-}\r
-\r
-// Flush deletes all session data.\r
-func (s *MemStore) Flush() error {\r
- s.lock.Lock()\r
- defer s.lock.Unlock()\r
-\r
- s.data = make(map[interface{}]interface{})\r
- return nil\r
-}\r
-\r
-// MemProvider represents a in-memory session provider implementation.\r
-type MemProvider struct {\r
- lock sync.RWMutex\r
- maxLifetime int64\r
- data map[string]*list.Element\r
- // A priority list whose lastAccess newer gets higer priority.\r
- list *list.List\r
-}\r
-\r
-// Init initializes memory session provider.\r
-func (p *MemProvider) Init(maxLifetime int64, _ string) error {\r
- p.lock.Lock()\r
- p.list = list.New()\r
- p.data = make(map[string]*list.Element)\r
- p.maxLifetime = maxLifetime\r
- p.lock.Unlock()\r
- return nil\r
-}\r
-\r
-// update expands time of session store by given ID.\r
-func (p *MemProvider) update(sid string) error {\r
- p.lock.Lock()\r
- defer p.lock.Unlock()\r
-\r
- if e, ok := p.data[sid]; ok {\r
- e.Value.(*MemStore).lastAccess = time.Now()\r
- p.list.MoveToFront(e)\r
- return nil\r
- }\r
- return nil\r
-}\r
-\r
-// Read returns raw session store by session ID.\r
-func (p *MemProvider) Read(sid string) (_ RawStore, err error) {\r
- p.lock.RLock()\r
- e, ok := p.data[sid]\r
- p.lock.RUnlock()\r
-\r
- // Only restore if the session is still alive.\r
- if ok && (e.Value.(*MemStore).lastAccess.Unix()+p.maxLifetime) >= time.Now().Unix() {\r
- if err = p.update(sid); err != nil {\r
- return nil, err\r
- }\r
- return e.Value.(*MemStore), nil\r
- }\r
-\r
- // Create a new session.\r
- p.lock.Lock()\r
- defer p.lock.Unlock()\r
- if ok {\r
- p.list.Remove(e)\r
- delete(p.data, sid)\r
- }\r
- s := NewMemStore(sid)\r
- p.data[sid] = p.list.PushBack(s)\r
- return s, nil\r
-}\r
-\r
-// Exist returns true if session with given ID exists.\r
-func (p *MemProvider) Exist(sid string) bool {\r
- p.lock.RLock()\r
- defer p.lock.RUnlock()\r
-\r
- _, ok := p.data[sid]\r
- return ok\r
-}\r
-\r
-// Destroy deletes a session by session ID.\r
-func (p *MemProvider) Destroy(sid string) error {\r
- p.lock.Lock()\r
- defer p.lock.Unlock()\r
-\r
- e, ok := p.data[sid]\r
- if !ok {\r
- return nil\r
- }\r
-\r
- p.list.Remove(e)\r
- delete(p.data, sid)\r
- return nil\r
-}\r
-\r
-// Regenerate regenerates a session store from old session ID to new one.\r
-func (p *MemProvider) Regenerate(oldsid, sid string) (RawStore, error) {\r
- if p.Exist(sid) {\r
- return nil, fmt.Errorf("new sid '%s' already exists", sid)\r
- }\r
-\r
- s, err := p.Read(oldsid)\r
- if err != nil {\r
- return nil, err\r
- }\r
-\r
- if err = p.Destroy(oldsid); err != nil {\r
- return nil, err\r
- }\r
-\r
- s.(*MemStore).sid = sid\r
-\r
- p.lock.Lock()\r
- defer p.lock.Unlock()\r
- p.data[sid] = p.list.PushBack(s)\r
- return s, nil\r
-}\r
-\r
-// Count counts and returns number of sessions.\r
-func (p *MemProvider) Count() int {\r
- return p.list.Len()\r
-}\r
-\r
-// GC calls GC to clean expired sessions.\r
-func (p *MemProvider) GC() {\r
- p.lock.RLock()\r
- for {\r
- // No session in the list.\r
- e := p.list.Back()\r
- if e == nil {\r
- break\r
- }\r
-\r
- if (e.Value.(*MemStore).lastAccess.Unix() + p.maxLifetime) < time.Now().Unix() {\r
- p.lock.RUnlock()\r
- p.lock.Lock()\r
- p.list.Remove(e)\r
- delete(p.data, e.Value.(*MemStore).sid)\r
- p.lock.Unlock()\r
- p.lock.RLock()\r
- } else {\r
- break\r
- }\r
- }\r
- p.lock.RUnlock()\r
-}\r
-\r
-func init() {\r
- Register("memory", &MemProvider{})\r
-}\r
+++ /dev/null
-// Copyright 2013 Beego Authors
-// Copyright 2014 The Macaron Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package session
-
-import (
- "database/sql"
- "fmt"
- "log"
- "sync"
- "time"
-
- "gitea.com/macaron/session"
- _ "github.com/go-sql-driver/mysql"
-)
-
-// MysqlStore represents a mysql session store implementation.
-type MysqlStore struct {
- c *sql.DB
- sid string
- lock sync.RWMutex
- data map[interface{}]interface{}
-}
-
-// NewMysqlStore creates and returns a mysql session store.
-func NewMysqlStore(c *sql.DB, sid string, kv map[interface{}]interface{}) *MysqlStore {
- return &MysqlStore{
- c: c,
- sid: sid,
- data: kv,
- }
-}
-
-// Set sets value to given key in session.
-func (s *MysqlStore) Set(key, val interface{}) error {
- s.lock.Lock()
- defer s.lock.Unlock()
-
- s.data[key] = val
- return nil
-}
-
-// Get gets value by given key in session.
-func (s *MysqlStore) Get(key interface{}) interface{} {
- s.lock.RLock()
- defer s.lock.RUnlock()
-
- return s.data[key]
-}
-
-// Delete delete a key from session.
-func (s *MysqlStore) Delete(key interface{}) error {
- s.lock.Lock()
- defer s.lock.Unlock()
-
- delete(s.data, key)
- return nil
-}
-
-// ID returns current session ID.
-func (s *MysqlStore) ID() string {
- return s.sid
-}
-
-// Release releases resource and save data to provider.
-func (s *MysqlStore) Release() error {
- // Skip encoding if the data is empty
- if len(s.data) == 0 {
- return nil
- }
-
- data, err := session.EncodeGob(s.data)
- if err != nil {
- return err
- }
-
- _, err = s.c.Exec("UPDATE session SET data=?, expiry=? WHERE `key`=?",
- data, time.Now().Unix(), s.sid)
- return err
-}
-
-// Flush deletes all session data.
-func (s *MysqlStore) Flush() error {
- s.lock.Lock()
- defer s.lock.Unlock()
-
- s.data = make(map[interface{}]interface{})
- return nil
-}
-
-// MysqlProvider represents a mysql session provider implementation.
-type MysqlProvider struct {
- c *sql.DB
- expire int64
-}
-
-// Init initializes mysql session provider.
-// connStr: username:password@protocol(address)/dbname?param=value
-func (p *MysqlProvider) Init(expire int64, connStr string) (err error) {
- p.expire = expire
-
- p.c, err = sql.Open("mysql", connStr)
- if err != nil {
- return err
- }
- return p.c.Ping()
-}
-
-// Read returns raw session store by session ID.
-func (p *MysqlProvider) Read(sid string) (session.RawStore, error) {
- now := time.Now().Unix()
- var data []byte
- expiry := now
- err := p.c.QueryRow("SELECT data, expiry FROM session WHERE `key`=?", sid).Scan(&data, &expiry)
- if err == sql.ErrNoRows {
- _, err = p.c.Exec("INSERT INTO session(`key`,data,expiry) VALUES(?,?,?)",
- sid, "", now)
- }
- if err != nil {
- return nil, err
- }
-
- var kv map[interface{}]interface{}
- if len(data) == 0 || expiry+p.expire <= now {
- kv = make(map[interface{}]interface{})
- } else {
- kv, err = session.DecodeGob(data)
- if err != nil {
- return nil, err
- }
- }
-
- return NewMysqlStore(p.c, sid, kv), nil
-}
-
-// Exist returns true if session with given ID exists.
-func (p *MysqlProvider) Exist(sid string) bool {
- var data []byte
- err := p.c.QueryRow("SELECT data FROM session WHERE `key`=?", sid).Scan(&data)
- if err != nil && err != sql.ErrNoRows {
- panic("session/mysql: error checking existence: " + err.Error())
- }
- return err != sql.ErrNoRows
-}
-
-// Destroy deletes a session by session ID.
-func (p *MysqlProvider) Destroy(sid string) error {
- _, err := p.c.Exec("DELETE FROM session WHERE `key`=?", sid)
- return err
-}
-
-// Regenerate regenerates a session store from old session ID to new one.
-func (p *MysqlProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err error) {
- if p.Exist(sid) {
- return nil, fmt.Errorf("new sid '%s' already exists", sid)
- }
-
- if !p.Exist(oldsid) {
- if _, err = p.c.Exec("INSERT INTO session(`key`,data,expiry) VALUES(?,?,?)",
- oldsid, "", time.Now().Unix()); err != nil {
- return nil, err
- }
- }
-
- if _, err = p.c.Exec("UPDATE session SET `key`=? WHERE `key`=?", sid, oldsid); err != nil {
- return nil, err
- }
-
- return p.Read(sid)
-}
-
-// Count counts and returns number of sessions.
-func (p *MysqlProvider) Count() (total int) {
- if err := p.c.QueryRow("SELECT COUNT(*) AS NUM FROM session").Scan(&total); err != nil {
- panic("session/mysql: error counting records: " + err.Error())
- }
- return total
-}
-
-// GC calls GC to clean expired sessions.
-func (p *MysqlProvider) GC() {
- if _, err := p.c.Exec("DELETE FROM session WHERE expiry + ? <= UNIX_TIMESTAMP(NOW())", p.expire); err != nil {
- log.Printf("session/mysql: error garbage collecting: %v", err)
- }
-}
-
-func init() {
- session.Register("mysql", &MysqlProvider{})
-}
+++ /dev/null
-ignore
\ No newline at end of file
+++ /dev/null
-// Copyright 2015 The Macaron Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package session
-
-import (
- "fmt"
- "sync"
-
- "gitea.com/macaron/session"
- "gitea.com/lunny/nodb"
- "gitea.com/lunny/nodb/config"
-)
-
-// NodbStore represents a nodb session store implementation.
-type NodbStore struct {
- c *nodb.DB
- sid string
- expire int64
- lock sync.RWMutex
- data map[interface{}]interface{}
-}
-
-// NewNodbStore creates and returns a ledis session store.
-func NewNodbStore(c *nodb.DB, sid string, expire int64, kv map[interface{}]interface{}) *NodbStore {
- return &NodbStore{
- c: c,
- expire: expire,
- sid: sid,
- data: kv,
- }
-}
-
-// Set sets value to given key in session.
-func (s *NodbStore) Set(key, val interface{}) error {
- s.lock.Lock()
- defer s.lock.Unlock()
-
- s.data[key] = val
- return nil
-}
-
-// Get gets value by given key in session.
-func (s *NodbStore) Get(key interface{}) interface{} {
- s.lock.RLock()
- defer s.lock.RUnlock()
-
- return s.data[key]
-}
-
-// Delete delete a key from session.
-func (s *NodbStore) Delete(key interface{}) error {
- s.lock.Lock()
- defer s.lock.Unlock()
-
- delete(s.data, key)
- return nil
-}
-
-// ID returns current session ID.
-func (s *NodbStore) ID() string {
- return s.sid
-}
-
-// Release releases resource and save data to provider.
-func (s *NodbStore) Release() error {
- // Skip encoding if the data is empty
- if len(s.data) == 0 {
- return nil
- }
-
- data, err := session.EncodeGob(s.data)
- if err != nil {
- return err
- }
-
- if err = s.c.Set([]byte(s.sid), data); err != nil {
- return err
- }
- _, err = s.c.Expire([]byte(s.sid), s.expire)
- return err
-}
-
-// Flush deletes all session data.
-func (s *NodbStore) Flush() error {
- s.lock.Lock()
- defer s.lock.Unlock()
-
- s.data = make(map[interface{}]interface{})
- return nil
-}
-
-// NodbProvider represents a ledis session provider implementation.
-type NodbProvider struct {
- c *nodb.DB
- expire int64
-}
-
-// Init initializes nodb session provider.
-func (p *NodbProvider) Init(expire int64, configs string) error {
- p.expire = expire
-
- cfg := new(config.Config)
- cfg.DataDir = configs
- dbs, err := nodb.Open(cfg)
- if err != nil {
- return fmt.Errorf("session/nodb: error opening db: %v", err)
- }
-
- p.c, err = dbs.Select(0)
- return err
-}
-
-// Read returns raw session store by session ID.
-func (p *NodbProvider) Read(sid string) (session.RawStore, error) {
- if !p.Exist(sid) {
- if err := p.c.Set([]byte(sid), []byte("")); err != nil {
- return nil, err
- }
- }
-
- var kv map[interface{}]interface{}
- kvs, err := p.c.Get([]byte(sid))
- if err != nil {
- return nil, err
- }
- if len(kvs) == 0 {
- kv = make(map[interface{}]interface{})
- } else {
- kv, err = session.DecodeGob(kvs)
- if err != nil {
- return nil, err
- }
- }
-
- return NewNodbStore(p.c, sid, p.expire, kv), nil
-}
-
-// Exist returns true if session with given ID exists.
-func (p *NodbProvider) Exist(sid string) bool {
- count, err := p.c.Exists([]byte(sid))
- return err == nil && count > 0
-}
-
-// Destroy deletes a session by session ID.
-func (p *NodbProvider) Destroy(sid string) error {
- _, err := p.c.Del([]byte(sid))
- return err
-}
-
-// Regenerate regenerates a session store from old session ID to new one.
-func (p *NodbProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err error) {
- if p.Exist(sid) {
- return nil, fmt.Errorf("new sid '%s' already exists", sid)
- }
-
- kvs := make([]byte, 0)
- if p.Exist(oldsid) {
- if kvs, err = p.c.Get([]byte(oldsid)); err != nil {
- return nil, err
- } else if _, err = p.c.Del([]byte(oldsid)); err != nil {
- return nil, err
- }
- }
-
- if err = p.c.Set([]byte(sid), kvs); err != nil {
- return nil, err
- } else if _, err = p.c.Expire([]byte(sid), p.expire); err != nil {
- return nil, err
- }
-
- var kv map[interface{}]interface{}
- if len(kvs) == 0 {
- kv = make(map[interface{}]interface{})
- } else {
- kv, err = session.DecodeGob([]byte(kvs))
- if err != nil {
- return nil, err
- }
- }
-
- return NewNodbStore(p.c, sid, p.expire, kv), nil
-}
-
-// Count counts and returns number of sessions.
-func (p *NodbProvider) Count() int {
- // FIXME: how come this library does not have DbSize() method?
- return -1
-}
-
-// GC calls GC to clean expired sessions.
-func (p *NodbProvider) GC() {}
-
-func init() {
- session.Register("nodb", &NodbProvider{})
-}
+++ /dev/null
-ignore
\ No newline at end of file
+++ /dev/null
-// Copyright 2013 Beego Authors
-// Copyright 2014 The Macaron Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package session
-
-import (
- "database/sql"
- "fmt"
- "log"
- "sync"
- "time"
-
- "gitea.com/macaron/session"
- _ "github.com/lib/pq"
-)
-
-// PostgresStore represents a postgres session store implementation.
-type PostgresStore struct {
- c *sql.DB
- sid string
- lock sync.RWMutex
- data map[interface{}]interface{}
-}
-
-// NewPostgresStore creates and returns a postgres session store.
-func NewPostgresStore(c *sql.DB, sid string, kv map[interface{}]interface{}) *PostgresStore {
- return &PostgresStore{
- c: c,
- sid: sid,
- data: kv,
- }
-}
-
-// Set sets value to given key in session.
-func (s *PostgresStore) Set(key, value interface{}) error {
- s.lock.Lock()
- defer s.lock.Unlock()
-
- s.data[key] = value
- return nil
-}
-
-// Get gets value by given key in session.
-func (s *PostgresStore) Get(key interface{}) interface{} {
- s.lock.RLock()
- defer s.lock.RUnlock()
-
- return s.data[key]
-}
-
-// Delete delete a key from session.
-func (s *PostgresStore) Delete(key interface{}) error {
- s.lock.Lock()
- defer s.lock.Unlock()
-
- delete(s.data, key)
- return nil
-}
-
-// ID returns current session ID.
-func (s *PostgresStore) ID() string {
- return s.sid
-}
-
-// save postgres session values to database.
-// must call this method to save values to database.
-func (s *PostgresStore) Release() error {
- // Skip encoding if the data is empty
- if len(s.data) == 0 {
- return nil
- }
-
- data, err := session.EncodeGob(s.data)
- if err != nil {
- return err
- }
-
- _, err = s.c.Exec("UPDATE session SET data=$1, expiry=$2 WHERE key=$3",
- data, time.Now().Unix(), s.sid)
- return err
-}
-
-// Flush deletes all session data.
-func (s *PostgresStore) Flush() error {
- s.lock.Lock()
- defer s.lock.Unlock()
-
- s.data = make(map[interface{}]interface{})
- return nil
-}
-
-// PostgresProvider represents a postgres session provider implementation.
-type PostgresProvider struct {
- c *sql.DB
- maxlifetime int64
-}
-
-// Init initializes postgres session provider.
-// connStr: user=a password=b host=localhost port=5432 dbname=c sslmode=disable
-func (p *PostgresProvider) Init(maxlifetime int64, connStr string) (err error) {
- p.maxlifetime = maxlifetime
-
- p.c, err = sql.Open("postgres", connStr)
- if err != nil {
- return err
- }
- return p.c.Ping()
-}
-
-// Read returns raw session store by session ID.
-func (p *PostgresProvider) Read(sid string) (session.RawStore, error) {
- now := time.Now().Unix()
- var data []byte
- expiry := now
- err := p.c.QueryRow("SELECT data, expiry FROM session WHERE key=$1", sid).Scan(&data, &expiry)
- if err == sql.ErrNoRows {
- _, err = p.c.Exec("INSERT INTO session(key,data,expiry) VALUES($1,$2,$3)",
- sid, "", now)
- }
- if err != nil {
- return nil, err
- }
-
- var kv map[interface{}]interface{}
- if len(data) == 0 || expiry+p.maxlifetime <= now {
- kv = make(map[interface{}]interface{})
- } else {
- kv, err = session.DecodeGob(data)
- if err != nil {
- return nil, err
- }
- }
-
- return NewPostgresStore(p.c, sid, kv), nil
-}
-
-// Exist returns true if session with given ID exists.
-func (p *PostgresProvider) Exist(sid string) bool {
- var data []byte
- err := p.c.QueryRow("SELECT data FROM session WHERE key=$1", sid).Scan(&data)
- if err != nil && err != sql.ErrNoRows {
- panic("session/postgres: error checking existence: " + err.Error())
- }
- return err != sql.ErrNoRows
-}
-
-// Destroy deletes a session by session ID.
-func (p *PostgresProvider) Destroy(sid string) error {
- _, err := p.c.Exec("DELETE FROM session WHERE key=$1", sid)
- return err
-}
-
-// Regenerate regenerates a session store from old session ID to new one.
-func (p *PostgresProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err error) {
- if p.Exist(sid) {
- return nil, fmt.Errorf("new sid '%s' already exists", sid)
- }
-
- if !p.Exist(oldsid) {
- if _, err = p.c.Exec("INSERT INTO session(key,data,expiry) VALUES($1,$2,$3)",
- oldsid, "", time.Now().Unix()); err != nil {
- return nil, err
- }
- }
-
- if _, err = p.c.Exec("UPDATE session SET key=$1 WHERE key=$2", sid, oldsid); err != nil {
- return nil, err
- }
-
- return p.Read(sid)
-}
-
-// Count counts and returns number of sessions.
-func (p *PostgresProvider) Count() (total int) {
- if err := p.c.QueryRow("SELECT COUNT(*) AS NUM FROM session").Scan(&total); err != nil {
- panic("session/postgres: error counting records: " + err.Error())
- }
- return total
-}
-
-// GC calls GC to clean expired sessions.
-func (p *PostgresProvider) GC() {
- if _, err := p.c.Exec("DELETE FROM session WHERE EXTRACT(EPOCH FROM NOW()) - expiry > $1", p.maxlifetime); err != nil {
- log.Printf("session/postgres: error garbage collecting: %v", err)
- }
-}
-
-func init() {
- session.Register("postgres", &PostgresProvider{})
-}
+++ /dev/null
-ignore
\ No newline at end of file
+++ /dev/null
-// Copyright 2013 Beego Authors
-// Copyright 2014 The Macaron Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-// Package session a middleware that provides the session management of Macaron.
-package session
-
-import (
- "encoding/hex"
- "errors"
- "fmt"
- "net/http"
- "net/url"
- "time"
-
- "gitea.com/macaron/macaron"
-)
-
-const _VERSION = "0.6.0"
-
-func Version() string {
- return _VERSION
-}
-
-// RawStore is the interface that operates the session data.
-type RawStore interface {
- // Set sets value to given key in session.
- Set(interface{}, interface{}) error
- // Get gets value by given key in session.
- Get(interface{}) interface{}
- // Delete deletes a key from session.
- Delete(interface{}) error
- // ID returns current session ID.
- ID() string
- // Release releases session resource and save data to provider.
- Release() error
- // Flush deletes all session data.
- Flush() error
-}
-
-// Store is the interface that contains all data for one session process with specific ID.
-type Store interface {
- RawStore
- // Read returns raw session store by session ID.
- Read(string) (RawStore, error)
- // Destroy deletes a session.
- Destroy(*macaron.Context) error
- // RegenerateId regenerates a session store from old session ID to new one.
- RegenerateId(*macaron.Context) (RawStore, error)
- // Count counts and returns number of sessions.
- Count() int
- // GC calls GC to clean expired sessions.
- GC()
-}
-
-type store struct {
- RawStore
- *Manager
-}
-
-var _ Store = &store{}
-
-// Options represents a struct for specifying configuration options for the session middleware.
-type Options struct {
- // Name of provider. Default is "memory".
- Provider string
- // Provider configuration, it's corresponding to provider.
- ProviderConfig string
- // Cookie name to save session ID. Default is "MacaronSession".
- CookieName string
- // Cookie path to store. Default is "/".
- CookiePath string
- // GC interval time in seconds. Default is 3600.
- Gclifetime int64
- // Max life time in seconds. Default is whatever GC interval time is.
- Maxlifetime int64
- // Use HTTPS only. Default is false.
- Secure bool
- // Cookie life time. Default is 0.
- CookieLifeTime int
- // Cookie domain name. Default is empty.
- Domain string
- // Session ID length. Default is 16.
- IDLength int
- // Configuration section name. Default is "session".
- Section string
- // Ignore release for websocket. Default is false.
- IgnoreReleaseForWebSocket bool
-}
-
-func prepareOptions(options []Options) Options {
- var opt Options
- if len(options) > 0 {
- opt = options[0]
- }
- if len(opt.Section) == 0 {
- opt.Section = "session"
- }
- sec := macaron.Config().Section(opt.Section)
-
- if len(opt.Provider) == 0 {
- opt.Provider = sec.Key("PROVIDER").MustString("memory")
- }
- if len(opt.ProviderConfig) == 0 {
- opt.ProviderConfig = sec.Key("PROVIDER_CONFIG").MustString("data/sessions")
- }
- if len(opt.CookieName) == 0 {
- opt.CookieName = sec.Key("COOKIE_NAME").MustString("MacaronSession")
- }
- if len(opt.CookiePath) == 0 {
- opt.CookiePath = sec.Key("COOKIE_PATH").MustString("/")
- }
- if opt.Gclifetime == 0 {
- opt.Gclifetime = sec.Key("GC_INTERVAL_TIME").MustInt64(3600)
- }
- if opt.Maxlifetime == 0 {
- opt.Maxlifetime = sec.Key("MAX_LIFE_TIME").MustInt64(opt.Gclifetime)
- }
- if !opt.Secure {
- opt.Secure = sec.Key("SECURE").MustBool()
- }
- if opt.CookieLifeTime == 0 {
- opt.CookieLifeTime = sec.Key("COOKIE_LIFE_TIME").MustInt()
- }
- if len(opt.Domain) == 0 {
- opt.Domain = sec.Key("DOMAIN").String()
- }
- if opt.IDLength == 0 {
- opt.IDLength = sec.Key("ID_LENGTH").MustInt(16)
- }
- if !opt.IgnoreReleaseForWebSocket {
- opt.IgnoreReleaseForWebSocket = sec.Key("IGNORE_RELEASE_FOR_WEBSOCKET").MustBool()
- }
-
- return opt
-}
-
-// Sessioner is a middleware that maps a session.SessionStore service into the Macaron handler chain.
-// An single variadic session.Options struct can be optionally provided to configure.
-func Sessioner(options ...Options) macaron.Handler {
- opt := prepareOptions(options)
- manager, err := NewManager(opt.Provider, opt)
- if err != nil {
- panic(err)
- }
- go manager.startGC()
-
- return func(ctx *macaron.Context) {
- sess, err := manager.Start(ctx)
- if err != nil {
- panic("session(start): " + err.Error())
- }
-
- // Get flash.
- vals, _ := url.ParseQuery(ctx.GetCookie("macaron_flash"))
- if len(vals) > 0 {
- f := &Flash{Values: vals}
- f.ErrorMsg = f.Get("error")
- f.SuccessMsg = f.Get("success")
- f.InfoMsg = f.Get("info")
- f.WarningMsg = f.Get("warning")
- ctx.Data["Flash"] = f
- ctx.SetCookie("macaron_flash", "", -1, opt.CookiePath)
- }
-
- f := &Flash{ctx, url.Values{}, "", "", "", ""}
- ctx.Resp.Before(func(macaron.ResponseWriter) {
- if flash := f.Encode(); len(flash) > 0 {
- ctx.SetCookie("macaron_flash", flash, 0, opt.CookiePath)
- }
- })
-
- ctx.Map(f)
- s := store{
- RawStore: sess,
- Manager: manager,
- }
-
- ctx.MapTo(s, (*Store)(nil))
-
- ctx.Next()
-
- if manager.opt.IgnoreReleaseForWebSocket && ctx.Req.Header.Get("Upgrade") == "websocket" {
- return
- }
-
- if err = sess.Release(); err != nil {
- panic("session(release): " + err.Error())
- }
- }
-}
-
-// Provider is the interface that provides session manipulations.
-type Provider interface {
- // Init initializes session provider.
- Init(gclifetime int64, config string) error
- // Read returns raw session store by session ID.
- Read(sid string) (RawStore, error)
- // Exist returns true if session with given ID exists.
- Exist(sid string) bool
- // Destroy deletes a session by session ID.
- Destroy(sid string) error
- // Regenerate regenerates a session store from old session ID to new one.
- Regenerate(oldsid, sid string) (RawStore, error)
- // Count counts and returns number of sessions.
- Count() int
- // GC calls GC to clean expired sessions.
- GC()
-}
-
-var providers = make(map[string]Provider)
-
-// Register registers a provider.
-func Register(name string, provider Provider) {
- if provider == nil {
- panic("session: cannot register provider with nil value")
- }
- if _, dup := providers[name]; dup {
- panic(fmt.Errorf("session: cannot register provider '%s' twice", name))
- }
- providers[name] = provider
-}
-
-// _____
-// / \ _____ ____ _____ ____ ___________
-// / \ / \\__ \ / \\__ \ / ___\_/ __ \_ __ \
-// / Y \/ __ \| | \/ __ \_/ /_/ > ___/| | \/
-// \____|__ (____ /___| (____ /\___ / \___ >__|
-// \/ \/ \/ \//_____/ \/
-
-// Manager represents a struct that contains session provider and its configuration.
-type Manager struct {
- provider Provider
- opt Options
-}
-
-// NewManager creates and returns a new session manager by given provider name and configuration.
-// It panics when given provider isn't registered.
-func NewManager(name string, opt Options) (*Manager, error) {
- p, ok := providers[name]
- if !ok {
- return nil, fmt.Errorf("session: unknown provider '%s'(forgotten import?)", name)
- }
- return &Manager{p, opt}, p.Init(opt.Maxlifetime, opt.ProviderConfig)
-}
-
-// sessionID generates a new session ID with rand string, unix nano time, remote addr by hash function.
-func (m *Manager) sessionID() string {
- return hex.EncodeToString(generateRandomKey(m.opt.IDLength / 2))
-}
-
-// validSessionID tests whether a provided session ID is a valid session ID.
-func (m *Manager) validSessionID(sid string) (bool, error) {
- if len(sid) != m.opt.IDLength {
- return false, errors.New("invalid 'sid': " + sid)
- }
-
- for i := range sid {
- switch {
- case '0' <= sid[i] && sid[i] <= '9':
- case 'a' <= sid[i] && sid[i] <= 'f':
- default:
- return false, errors.New("invalid 'sid': " + sid)
- }
- }
- return true, nil
-}
-
-// Start starts a session by generating new one
-// or retrieve existence one by reading session ID from HTTP request if it's valid.
-func (m *Manager) Start(ctx *macaron.Context) (RawStore, error) {
- sid := ctx.GetCookie(m.opt.CookieName)
- valid, _ := m.validSessionID(sid)
- if len(sid) > 0 && valid && m.provider.Exist(sid) {
- return m.provider.Read(sid)
- }
-
- sid = m.sessionID()
- sess, err := m.provider.Read(sid)
- if err != nil {
- return nil, err
- }
-
- cookie := &http.Cookie{
- Name: m.opt.CookieName,
- Value: sid,
- Path: m.opt.CookiePath,
- HttpOnly: true,
- Secure: m.opt.Secure,
- Domain: m.opt.Domain,
- }
- if m.opt.CookieLifeTime >= 0 {
- cookie.MaxAge = m.opt.CookieLifeTime
- }
- http.SetCookie(ctx.Resp, cookie)
- ctx.Req.AddCookie(cookie)
- return sess, nil
-}
-
-// Read returns raw session store by session ID.
-func (m *Manager) Read(sid string) (RawStore, error) {
- // Ensure we're trying to read a valid session ID
- if _, err := m.validSessionID(sid); err != nil {
- return nil, err
- }
-
- return m.provider.Read(sid)
-}
-
-// Destroy deletes a session by given ID.
-func (m *Manager) Destroy(ctx *macaron.Context) error {
- sid := ctx.GetCookie(m.opt.CookieName)
- if len(sid) == 0 {
- return nil
- }
-
- if _, err := m.validSessionID(sid); err != nil {
- return err
- }
-
- if err := m.provider.Destroy(sid); err != nil {
- return err
- }
- cookie := &http.Cookie{
- Name: m.opt.CookieName,
- Path: m.opt.CookiePath,
- HttpOnly: true,
- Expires: time.Now(),
- MaxAge: -1,
- }
- http.SetCookie(ctx.Resp, cookie)
- return nil
-}
-
-// RegenerateId regenerates a session store from old session ID to new one.
-func (m *Manager) RegenerateId(ctx *macaron.Context) (sess RawStore, err error) {
- sid := m.sessionID()
- oldsid := ctx.GetCookie(m.opt.CookieName)
- _, err = m.validSessionID(oldsid)
- if err != nil {
- return nil, err
- }
- sess, err = m.provider.Regenerate(oldsid, sid)
- if err != nil {
- return nil, err
- }
- cookie := &http.Cookie{
- Name: m.opt.CookieName,
- Value: sid,
- Path: m.opt.CookiePath,
- HttpOnly: true,
- Secure: m.opt.Secure,
- Domain: m.opt.Domain,
- }
- if m.opt.CookieLifeTime >= 0 {
- cookie.MaxAge = m.opt.CookieLifeTime
- }
- http.SetCookie(ctx.Resp, cookie)
- ctx.Req.AddCookie(cookie)
- return sess, nil
-}
-
-// Count counts and returns number of sessions.
-func (m *Manager) Count() int {
- return m.provider.Count()
-}
-
-// GC starts GC job in a certain period.
-func (m *Manager) GC() {
- m.provider.GC()
-}
-
-// startGC starts GC job in a certain period.
-func (m *Manager) startGC() {
- m.GC()
- time.AfterFunc(time.Duration(m.opt.Gclifetime)*time.Second, func() { m.startGC() })
-}
-
-// SetSecure indicates whether to set cookie with HTTPS or not.
-func (m *Manager) SetSecure(secure bool) {
- m.opt.Secure = secure
-}
+++ /dev/null
-// Copyright 2013 Beego Authors
-// Copyright 2014 The Macaron Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package session
-
-import (
- "bytes"
- "crypto/rand"
- "encoding/gob"
- "io"
-
- "github.com/unknwon/com"
-)
-
-func init() {
- gob.Register([]interface{}{})
- gob.Register(map[int]interface{}{})
- gob.Register(map[string]interface{}{})
- gob.Register(map[interface{}]interface{}{})
- gob.Register(map[string]string{})
- gob.Register(map[int]string{})
- gob.Register(map[int]int{})
- gob.Register(map[int]int64{})
-}
-
-func EncodeGob(obj map[interface{}]interface{}) ([]byte, error) {
- for _, v := range obj {
- gob.Register(v)
- }
- buf := bytes.NewBuffer(nil)
- err := gob.NewEncoder(buf).Encode(obj)
- return buf.Bytes(), err
-}
-
-func DecodeGob(encoded []byte) (out map[interface{}]interface{}, err error) {
- buf := bytes.NewBuffer(encoded)
- err = gob.NewDecoder(buf).Decode(&out)
- return out, err
-}
-
-// NOTE: A local copy in case of underlying package change
-var alphanum = []byte("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")
-
-// generateRandomKey creates a random key with the given strength.
-func generateRandomKey(strength int) []byte {
- k := make([]byte, strength)
- if n, err := io.ReadFull(rand.Reader, k); n != strength || err != nil {
- return com.RandomCreateBytes(strength, alphanum...)
- }
- return k
-}
+++ /dev/null
-kind: pipeline
-name: default
-
-steps:
-- name: test
- image: golang:1.11
- commands:
- - go build -v
- - go test -v -race -coverprofile=coverage.txt -covermode=atomic
+++ /dev/null
-profile/
-/.idea
+++ /dev/null
-Apache License
-Version 2.0, January 2004
-http://www.apache.org/licenses/
-
-TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-1. Definitions.
-
-"License" shall mean the terms and conditions for use, reproduction, and
-distribution as defined by Sections 1 through 9 of this document.
-
-"Licensor" shall mean the copyright owner or entity authorized by the copyright
-owner that is granting the License.
-
-"Legal Entity" shall mean the union of the acting entity and all other entities
-that control, are controlled by, or are under common control with that entity.
-For the purposes of this definition, "control" means (i) the power, direct or
-indirect, to cause the direction or management of such entity, whether by
-contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
-outstanding shares, or (iii) beneficial ownership of such entity.
-
-"You" (or "Your") shall mean an individual or Legal Entity exercising
-permissions granted by this License.
-
-"Source" form shall mean the preferred form for making modifications, including
-but not limited to software source code, documentation source, and configuration
-files.
-
-"Object" form shall mean any form resulting from mechanical transformation or
-translation of a Source form, including but not limited to compiled object code,
-generated documentation, and conversions to other media types.
-
-"Work" shall mean the work of authorship, whether in Source or Object form, made
-available under the License, as indicated by a copyright notice that is included
-in or attached to the work (an example is provided in the Appendix below).
-
-"Derivative Works" shall mean any work, whether in Source or Object form, that
-is based on (or derived from) the Work and for which the editorial revisions,
-annotations, elaborations, or other modifications represent, as a whole, an
-original work of authorship. For the purposes of this License, Derivative Works
-shall not include works that remain separable from, or merely link (or bind by
-name) to the interfaces of, the Work and Derivative Works thereof.
-
-"Contribution" shall mean any work of authorship, including the original version
-of the Work and any modifications or additions to that Work or Derivative Works
-thereof, that is intentionally submitted to Licensor for inclusion in the Work
-by the copyright owner or by an individual or Legal Entity authorized to submit
-on behalf of the copyright owner. For the purposes of this definition,
-"submitted" means any form of electronic, verbal, or written communication sent
-to the Licensor or its representatives, including but not limited to
-communication on electronic mailing lists, source code control systems, and
-issue tracking systems that are managed by, or on behalf of, the Licensor for
-the purpose of discussing and improving the Work, but excluding communication
-that is conspicuously marked or otherwise designated in writing by the copyright
-owner as "Not a Contribution."
-
-"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
-of whom a Contribution has been received by Licensor and subsequently
-incorporated within the Work.
-
-2. Grant of Copyright License.
-
-Subject to the terms and conditions of this License, each Contributor hereby
-grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
-irrevocable copyright license to reproduce, prepare Derivative Works of,
-publicly display, publicly perform, sublicense, and distribute the Work and such
-Derivative Works in Source or Object form.
-
-3. Grant of Patent License.
-
-Subject to the terms and conditions of this License, each Contributor hereby
-grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
-irrevocable (except as stated in this section) patent license to make, have
-made, use, offer to sell, sell, import, and otherwise transfer the Work, where
-such license applies only to those patent claims licensable by such Contributor
-that are necessarily infringed by their Contribution(s) alone or by combination
-of their Contribution(s) with the Work to which such Contribution(s) was
-submitted. If You institute patent litigation against any entity (including a
-cross-claim or counterclaim in a lawsuit) alleging that the Work or a
-Contribution incorporated within the Work constitutes direct or contributory
-patent infringement, then any patent licenses granted to You under this License
-for that Work shall terminate as of the date such litigation is filed.
-
-4. Redistribution.
-
-You may reproduce and distribute copies of the Work or Derivative Works thereof
-in any medium, with or without modifications, and in Source or Object form,
-provided that You meet the following conditions:
-
-You must give any other recipients of the Work or Derivative Works a copy of
-this License; and
-You must cause any modified files to carry prominent notices stating that You
-changed the files; and
-You must retain, in the Source form of any Derivative Works that You distribute,
-all copyright, patent, trademark, and attribution notices from the Source form
-of the Work, excluding those notices that do not pertain to any part of the
-Derivative Works; and
-If the Work includes a "NOTICE" text file as part of its distribution, then any
-Derivative Works that You distribute must include a readable copy of the
-attribution notices contained within such NOTICE file, excluding those notices
-that do not pertain to any part of the Derivative Works, in at least one of the
-following places: within a NOTICE text file distributed as part of the
-Derivative Works; within the Source form or documentation, if provided along
-with the Derivative Works; or, within a display generated by the Derivative
-Works, if and wherever such third-party notices normally appear. The contents of
-the NOTICE file are for informational purposes only and do not modify the
-License. You may add Your own attribution notices within Derivative Works that
-You distribute, alongside or as an addendum to the NOTICE text from the Work,
-provided that such additional attribution notices cannot be construed as
-modifying the License.
-You may add Your own copyright statement to Your modifications and may provide
-additional or different license terms and conditions for use, reproduction, or
-distribution of Your modifications, or for any such Derivative Works as a whole,
-provided Your use, reproduction, and distribution of the Work otherwise complies
-with the conditions stated in this License.
-
-5. Submission of Contributions.
-
-Unless You explicitly state otherwise, any Contribution intentionally submitted
-for inclusion in the Work by You to the Licensor shall be under the terms and
-conditions of this License, without any additional terms or conditions.
-Notwithstanding the above, nothing herein shall supersede or modify the terms of
-any separate license agreement you may have executed with Licensor regarding
-such Contributions.
-
-6. Trademarks.
-
-This License does not grant permission to use the trade names, trademarks,
-service marks, or product names of the Licensor, except as required for
-reasonable and customary use in describing the origin of the Work and
-reproducing the content of the NOTICE file.
-
-7. Disclaimer of Warranty.
-
-Unless required by applicable law or agreed to in writing, Licensor provides the
-Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
-including, without limitation, any warranties or conditions of TITLE,
-NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
-solely responsible for determining the appropriateness of using or
-redistributing the Work and assume any risks associated with Your exercise of
-permissions under this License.
-
-8. Limitation of Liability.
-
-In no event and under no legal theory, whether in tort (including negligence),
-contract, or otherwise, unless required by applicable law (such as deliberate
-and grossly negligent acts) or agreed to in writing, shall any Contributor be
-liable to You for damages, including any direct, indirect, special, incidental,
-or consequential damages of any character arising as a result of this License or
-out of the use or inability to use the Work (including but not limited to
-damages for loss of goodwill, work stoppage, computer failure or malfunction, or
-any and all other commercial damages or losses), even if such Contributor has
-been advised of the possibility of such damages.
-
-9. Accepting Warranty or Additional Liability.
-
-While redistributing the Work or Derivative Works thereof, You may choose to
-offer, and charge a fee for, acceptance of support, warranty, indemnity, or
-other liability obligations and/or rights consistent with this License. However,
-in accepting such obligations, You may act only on Your own behalf and on Your
-sole responsibility, not on behalf of any other Contributor, and only if You
-agree to indemnify, defend, and hold each Contributor harmless for any liability
-incurred by, or claims asserted against, such Contributor by reason of your
-accepting any such warranty or additional liability.
-
-END OF TERMS AND CONDITIONS
-
-APPENDIX: How to apply the Apache License to your work
-
-To apply the Apache License to your work, attach the following boilerplate
-notice, with the fields enclosed by brackets "[]" replaced with your own
-identifying information. (Don't include the brackets!) The text should be
-enclosed in the appropriate comment syntax for the file format. We also
-recommend that a file or class name and description of purpose be included on
-the same "printed page" as the copyright notice for easier identification within
-third-party archives.
-
- Copyright [yyyy] [name of copyright owner]
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
\ No newline at end of file
+++ /dev/null
-toolbox
-=======
-
-Middleware toolbox provides health chcek, pprof, profile and statistic services for [Macaron](https://gitea.com/macaron/macaron).
-
-[![Build Status](https://drone.gitea.com/api/badges/macaron/toolbox/status.svg)](https://drone.gitea.com/macaron/toolbox)
-[API Reference](https://gowalker.org/gitea.com/macaron/toolbox)
-
-### Installation
-
- go get gitea.com/macaron/toolbox
-
-## Usage
-
-```go
-// main.go
-import (
- "gitea.com/macaron/macaron"
- "gitea.com/macaron/toolbox"
-)
-
-func main() {
- m := macaron.Classic()
- m.Use(toolbox.Toolboxer(m))
- m.Run()
-}
-```
-
-Open your browser and visit `http://localhost:4000/debug` to see the effects.
-
-## Options
-
-`toolbox.Toolboxer` comes with a variety of configuration options:
-
-```go
-type dummyChecker struct {
-}
-
-func (dc *dummyChecker) Desc() string {
- return "Dummy checker"
-}
-
-func (dc *dummyChecker) Check() error {
- return nil
-}
-
-// ...
-m.Use(toolbox.Toolboxer(m, toolbox.Options{
- URLPrefix: "/debug", // URL prefix for toolbox dashboard
- HealthCheckURL: "/healthcheck", // URL for health check request
- HealthCheckers: []HealthChecker{
- new(dummyChecker),
- }, // Health checkers
- HealthCheckFuncs: []*toolbox.HealthCheckFuncDesc{
- &toolbox.HealthCheckFuncDesc{
- Desc: "Database connection",
- Func: func() error { return "OK" },
- },
- }, // Health check functions
- DisableDebug: false, // Turns off all debug functionality when true
- PprofURLPrefix: "/debug/pprof/", // URL prefix of pprof
- ProfileURLPrefix: "/debug/profile/", // URL prefix of profile
- ProfilePath: "profile", // Path store profile files
-}))
-// ...
-```
-
-## Route Statistic
-
-Toolbox also comes with a route call statistic functionality:
-
-```go
-import (
- "os"
- "time"
- //...
- "gitea.com/macaron/toolbox"
-)
-
-func main() {
- //...
- m.Get("/", func(t toolbox.Toolbox) {
- start := time.Now()
-
- // Other operations.
-
- t.AddStatistics("GET", "/", time.Since(start))
- })
-
- m.Get("/dump", func(t toolbox.Toolbox) {
- t.GetMap(os.Stdout)
- })
-}
-```
-
-Output take from test:
-
-```
-+---------------------------------------------------+------------+------------------+------------------+------------------+------------------+------------------+
-| Request URL | Method | Times | Total Used(s) | Max Used(μs) | Min Used(μs) | Avg Used(μs) |
-+---------------------------------------------------+------------+------------------+------------------+------------------+------------------+------------------+
-| /api/user | POST | 2 | 0.000122 | 120.000000 | 2.000000 | 61.000000 |
-| /api/user | GET | 1 | 0.000013 | 13.000000 | 13.000000 | 13.000000 |
-| /api/user | DELETE | 1 | 0.000001 | 1.400000 | 1.400000 | 1.400000 |
-| /api/admin | POST | 1 | 0.000014 | 14.000000 | 14.000000 | 14.000000 |
-| /api/user/unknwon | POST | 1 | 0.000012 | 12.000000 | 12.000000 | 12.000000 |
-+---------------------------------------------------+------------+------------------+------------------+------------------+------------------+------------------+
-```
-
-## License
-
-This project is under Apache v2 License. See the [LICENSE](LICENSE) file for the full license text.
\ No newline at end of file
+++ /dev/null
-module gitea.com/macaron/toolbox
-
-go 1.11
-
-require (
- gitea.com/macaron/macaron v1.3.3-0.20190821202302-9646c0587edb
- github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337
- github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e
-)
+++ /dev/null
-gitea.com/macaron/inject v0.0.0-20190803172902-8375ba841591 h1:UbCTjPcLrNxR9LzKDjQBMT2zoxZuEnca1pZCpgeMuhQ=
-gitea.com/macaron/inject v0.0.0-20190803172902-8375ba841591/go.mod h1:h6E4kLao1Yko6DOU6QDnQPcuoNzvbZqzj2mtPcEn1aM=
-gitea.com/macaron/macaron v1.3.3-0.20190821202302-9646c0587edb h1:amL0md6orTj1tXY16ANzVU9FmzQB+W7aJwp8pVDbrmA=
-gitea.com/macaron/macaron v1.3.3-0.20190821202302-9646c0587edb/go.mod h1:0coI+mSPSwbsyAbOuFllVS38awuk9mevhLD52l50Gjs=
-github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
-github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
-github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
-github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
-github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
-github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY=
-github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
-github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
-github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8=
-github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
-github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e h1:GSGeB9EAKY2spCABz6xOX5DbxZEXolK+nBSvmsQwRjM=
-github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
-golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-gopkg.in/ini.v1 v1.44.0 h1:YRJzTUp0kSYWUVFF5XAbDFfyiqwsl0Vb9R8TVP5eRi0=
-gopkg.in/ini.v1 v1.44.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+++ /dev/null
-// Copyright 2013 Beego Authors
-// Copyright 2014 The Macaron Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package toolbox
-
-import (
- "bytes"
-)
-
-// HealthChecker represents a health check instance.
-type HealthChecker interface {
- Desc() string
- Check() error
-}
-
-// HealthCheckFunc represents a callable function for health check.
-type HealthCheckFunc func() error
-
-// HealthCheckFunc represents a callable function for health check with description.
-type HealthCheckFuncDesc struct {
- Desc string
- Func HealthCheckFunc
-}
-
-type healthCheck struct {
- desc string
- HealthChecker
- check HealthCheckFunc // Not nil if add job as a function.
-}
-
-// AddHealthCheck adds new health check job.
-func (t *toolbox) AddHealthCheck(hc HealthChecker) {
- t.healthCheckJobs = append(t.healthCheckJobs, &healthCheck{
- HealthChecker: hc,
- })
-}
-
-// AddHealthCheckFunc adds a function as a new health check job.
-func (t *toolbox) AddHealthCheckFunc(desc string, fn HealthCheckFunc) {
- t.healthCheckJobs = append(t.healthCheckJobs, &healthCheck{
- desc: desc,
- check: fn,
- })
-}
-
-func (t *toolbox) handleHealthCheck() string {
- if len(t.healthCheckJobs) == 0 {
- return "no health check jobs"
- }
-
- var buf bytes.Buffer
- var err error
- for _, job := range t.healthCheckJobs {
- buf.WriteString("* ")
- if job.check != nil {
- buf.WriteString(job.desc)
- err = job.check()
- } else {
- buf.WriteString(job.Desc())
- err = job.Check()
- }
- buf.WriteString(": ")
- if err == nil {
- buf.WriteString("OK")
- } else {
- buf.WriteString(err.Error())
- }
- buf.WriteString("\n")
- }
- return buf.String()
-}
+++ /dev/null
-// Copyright 2013 Beego Authors
-// Copyright 2014 The Macaron Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package toolbox
-
-import (
- "bytes"
- "errors"
- "fmt"
- "io"
- "os"
- "path"
- "runtime"
- "runtime/debug"
- "runtime/pprof"
- "time"
-
- "gitea.com/macaron/macaron"
- "github.com/unknwon/com"
-)
-
-var (
- profilePath string
- pid int
- startTime = time.Now()
- inCPUProfile bool
-)
-
-// StartCPUProfile starts CPU profile monitor.
-func StartCPUProfile() error {
- if inCPUProfile {
- return errors.New("CPU profile has alreday been started!")
- }
- inCPUProfile = true
-
- os.MkdirAll(profilePath, os.ModePerm)
- f, err := os.Create(path.Join(profilePath, "cpu-"+com.ToStr(pid)+".pprof"))
- if err != nil {
- panic("fail to record CPU profile: " + err.Error())
- }
- pprof.StartCPUProfile(f)
- return nil
-}
-
-// StopCPUProfile stops CPU profile monitor.
-func StopCPUProfile() error {
- if !inCPUProfile {
- return errors.New("CPU profile hasn't been started!")
- }
- pprof.StopCPUProfile()
- inCPUProfile = false
- return nil
-}
-
-func init() {
- pid = os.Getpid()
-}
-
-// DumpMemProf dumps memory profile in pprof.
-func DumpMemProf(w io.Writer) {
- pprof.WriteHeapProfile(w)
-}
-
-func dumpMemProf() {
- os.MkdirAll(profilePath, os.ModePerm)
- f, err := os.Create(path.Join(profilePath, "mem-"+com.ToStr(pid)+".memprof"))
- if err != nil {
- panic("fail to record memory profile: " + err.Error())
- }
- runtime.GC()
- DumpMemProf(f)
- f.Close()
-}
-
-func avg(items []time.Duration) time.Duration {
- var sum time.Duration
- for _, item := range items {
- sum += item
- }
- return time.Duration(int64(sum) / int64(len(items)))
-}
-
-func dumpGC(memStats *runtime.MemStats, gcstats *debug.GCStats, w io.Writer) {
-
- if gcstats.NumGC > 0 {
- lastPause := gcstats.Pause[0]
- elapsed := time.Now().Sub(startTime)
- overhead := float64(gcstats.PauseTotal) / float64(elapsed) * 100
- allocatedRate := float64(memStats.TotalAlloc) / elapsed.Seconds()
-
- fmt.Fprintf(w, "NumGC:%d Pause:%s Pause(Avg):%s Overhead:%3.2f%% Alloc:%s Sys:%s Alloc(Rate):%s/s Histogram:%s %s %s \n",
- gcstats.NumGC,
- com.ToStr(lastPause),
- com.ToStr(avg(gcstats.Pause)),
- overhead,
- com.HumaneFileSize(memStats.Alloc),
- com.HumaneFileSize(memStats.Sys),
- com.HumaneFileSize(uint64(allocatedRate)),
- com.ToStr(gcstats.PauseQuantiles[94]),
- com.ToStr(gcstats.PauseQuantiles[98]),
- com.ToStr(gcstats.PauseQuantiles[99]))
- } else {
- // while GC has disabled
- elapsed := time.Now().Sub(startTime)
- allocatedRate := float64(memStats.TotalAlloc) / elapsed.Seconds()
-
- fmt.Fprintf(w, "Alloc:%s Sys:%s Alloc(Rate):%s/s\n",
- com.HumaneFileSize(memStats.Alloc),
- com.HumaneFileSize(memStats.Sys),
- com.HumaneFileSize(uint64(allocatedRate)))
- }
-}
-
-// DumpGCSummary dumps GC information to io.Writer
-func DumpGCSummary(w io.Writer) {
- memStats := &runtime.MemStats{}
- runtime.ReadMemStats(memStats)
- gcstats := &debug.GCStats{PauseQuantiles: make([]time.Duration, 100)}
- debug.ReadGCStats(gcstats)
-
- dumpGC(memStats, gcstats, w)
-}
-
-func handleProfile(ctx *macaron.Context) string {
- switch ctx.Query("op") {
- case "startcpu":
- if err := StartCPUProfile(); err != nil {
- return err.Error()
- }
- case "stopcpu":
- if err := StopCPUProfile(); err != nil {
- return err.Error()
- }
- case "mem":
- dumpMemProf()
- case "gc":
- var buf bytes.Buffer
- DumpGCSummary(&buf)
- return string(buf.Bytes())
- default:
- return fmt.Sprintf(`<p>Available operations:</p>
-<ol>
- <li><a href="%[1]s?op=startcpu">Start CPU profile</a></li>
- <li><a href="%[1]s?op=stopcpu">Stop CPU profile</a></li>
- <li><a href="%[1]s?op=mem">Dump memory profile</a></li>
- <li><a href="%[1]s?op=gc">Dump GC summary</a></li>
-</ol>`, opt.ProfileURLPrefix)
- }
- ctx.Redirect(opt.ProfileURLPrefix)
- return ""
-}
+++ /dev/null
-// Copyright 2013 Beego Authors
-// Copyright 2014 The Macaron Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package toolbox
-
-import (
- "encoding/json"
- "fmt"
- "io"
- "strings"
- "sync"
- "time"
-)
-
-// Statistics struct
-type Statistics struct {
- RequestUrl string
- RequestNum int64
- MinTime time.Duration
- MaxTime time.Duration
- TotalTime time.Duration
-}
-
-// UrlMap contains several statistics struct to log different data
-type UrlMap struct {
- lock sync.RWMutex
- LengthLimit int // limit the urlmap's length if it's equal to 0 there's no limit
- urlmap map[string]map[string]*Statistics
-}
-
-// add statistics task.
-// it needs request method, request url and statistics time duration
-func (m *UrlMap) AddStatistics(requestMethod, requestUrl string, requesttime time.Duration) {
- m.lock.Lock()
- defer m.lock.Unlock()
-
- if method, ok := m.urlmap[requestUrl]; ok {
- if s, ok := method[requestMethod]; ok {
- s.RequestNum += 1
- if s.MaxTime < requesttime {
- s.MaxTime = requesttime
- }
- if s.MinTime > requesttime {
- s.MinTime = requesttime
- }
- s.TotalTime += requesttime
- } else {
- nb := &Statistics{
- RequestUrl: requestUrl,
- RequestNum: 1,
- MinTime: requesttime,
- MaxTime: requesttime,
- TotalTime: requesttime,
- }
- m.urlmap[requestUrl][requestMethod] = nb
- }
-
- } else {
- if m.LengthLimit > 0 && m.LengthLimit <= len(m.urlmap) {
- return
- }
- methodmap := make(map[string]*Statistics)
- nb := &Statistics{
- RequestUrl: requestUrl,
- RequestNum: 1,
- MinTime: requesttime,
- MaxTime: requesttime,
- TotalTime: requesttime,
- }
- methodmap[requestMethod] = nb
- m.urlmap[requestUrl] = methodmap
- }
-}
-
-// put url statistics result in io.Writer
-func (m *UrlMap) GetMap(w io.Writer) {
- m.lock.RLock()
- defer m.lock.RUnlock()
-
- sep := fmt.Sprintf("+%s+%s+%s+%s+%s+%s+%s+\n", strings.Repeat("-", 51), strings.Repeat("-", 12),
- strings.Repeat("-", 18), strings.Repeat("-", 18), strings.Repeat("-", 18), strings.Repeat("-", 18), strings.Repeat("-", 18))
- fmt.Fprintf(w, sep)
- fmt.Fprintf(w, "| % -50s| % -10s | % -16s | % -16s | % -16s | % -16s | % -16s |\n", "Request URL", "Method", "Times", "Total Used(s)", "Max Used(μs)", "Min Used(μs)", "Avg Used(μs)")
- fmt.Fprintf(w, sep)
-
- for k, v := range m.urlmap {
- for kk, vv := range v {
- fmt.Fprintf(w, "| % -50s| % -10s | % 16d | % 16f | % 16.6f | % 16.6f | % 16.6f |\n", k,
- kk, vv.RequestNum, vv.TotalTime.Seconds(), float64(vv.MaxTime.Nanoseconds())/1000,
- float64(vv.MinTime.Nanoseconds())/1000, float64(time.Duration(int64(vv.TotalTime)/vv.RequestNum).Nanoseconds())/1000,
- )
- }
- }
- fmt.Fprintf(w, sep)
-}
-
-type URLMapInfo struct {
- URL string `json:"url"`
- Method string `json:"method"`
- Times int64 `json:"times"`
- TotalUsed float64 `json:"total_used"`
- MaxUsed float64 `json:"max_used"`
- MinUsed float64 `json:"min_used"`
- AvgUsed float64 `json:"avg_used"`
-}
-
-func (m *UrlMap) JSON(w io.Writer) {
- infos := make([]*URLMapInfo, 0, len(m.urlmap))
- for k, v := range m.urlmap {
- for kk, vv := range v {
- infos = append(infos, &URLMapInfo{
- URL: k,
- Method: kk,
- Times: vv.RequestNum,
- TotalUsed: vv.TotalTime.Seconds(),
- MaxUsed: float64(vv.MaxTime.Nanoseconds()) / 1000,
- MinUsed: float64(vv.MinTime.Nanoseconds()) / 1000,
- AvgUsed: float64(time.Duration(int64(vv.TotalTime)/vv.RequestNum).Nanoseconds()) / 1000,
- })
- }
- }
-
- if err := json.NewEncoder(w).Encode(infos); err != nil {
- panic("URLMap.JSON: " + err.Error())
- }
-}
+++ /dev/null
-// Copyright 2014 The Macaron Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-// Package toolbox is a middleware that provides health check, pprof, profile and statistic services for Macaron.
-package toolbox
-
-import (
- "fmt"
- "io"
- "net/http"
- "net/http/pprof"
- "path"
- "time"
-
- "gitea.com/macaron/macaron"
-)
-
-const _VERSION = "0.1.4"
-
-func Version() string {
- return _VERSION
-}
-
-// Toolbox represents a tool box service for Macaron instance.
-type Toolbox interface {
- AddHealthCheck(HealthChecker)
- AddHealthCheckFunc(string, HealthCheckFunc)
- AddStatistics(string, string, time.Duration)
- GetMap(io.Writer)
- JSON(io.Writer)
-}
-
-type toolbox struct {
- *UrlMap
- healthCheckJobs []*healthCheck
-}
-
-// Options represents a struct for specifying configuration options for the Toolbox middleware.
-type Options struct {
- // URL prefix for toolbox dashboard. Default is "/debug".
- URLPrefix string
- // URL for health check request. Default is "/healthcheck".
- HealthCheckURL string
- // Health checkers.
- HealthCheckers []HealthChecker
- // Health check functions.
- HealthCheckFuncs []*HealthCheckFuncDesc
- // URL for URL map json. Default is "/urlmap.json".
- URLMapPrefix string
- // DisableDebug turns off all debug functionality.
- DisableDebug bool
- // URL prefix of pprof. Default is "/debug/pprof/".
- PprofURLPrefix string
- // URL prefix of profile. Default is "/debug/profile/".
- ProfileURLPrefix string
- // Path store profile files. Default is "profile".
- ProfilePath string
-}
-
-var opt Options
-
-func prepareOptions(options []Options) {
- if len(options) > 0 {
- opt = options[0]
- }
-
- // Defaults.
- if len(opt.URLPrefix) == 0 {
- opt.URLPrefix = "/debug"
- }
- if len(opt.HealthCheckURL) == 0 {
- opt.HealthCheckURL = "/healthcheck"
- }
- if len(opt.URLMapPrefix) == 0 {
- opt.URLMapPrefix = "/urlmap.json"
- }
- if len(opt.PprofURLPrefix) == 0 {
- opt.PprofURLPrefix = "/debug/pprof/"
- } else if opt.PprofURLPrefix[len(opt.PprofURLPrefix)-1] != '/' {
- opt.PprofURLPrefix += "/"
- }
- if len(opt.ProfileURLPrefix) == 0 {
- opt.ProfileURLPrefix = "/debug/profile/"
- } else if opt.ProfileURLPrefix[len(opt.ProfileURLPrefix)-1] != '/' {
- opt.ProfileURLPrefix += "/"
- }
- if len(opt.ProfilePath) == 0 {
- opt.ProfilePath = path.Join(macaron.Root, "profile")
- }
-}
-
-func dashboard() string {
- return fmt.Sprintf(`<p>Toolbox Index:</p>
- <ol>
- <li><a href="%s">Pprof Information</a></li>
- <li><a href="%s">Profile Operations</a></li>
- </ol>`, opt.PprofURLPrefix, opt.ProfileURLPrefix)
-}
-
-var _ Toolbox = &toolbox{}
-
-// Toolboxer is a middleware provides health check, pprof, profile and statistic services for your application.
-func Toolboxer(m *macaron.Macaron, options ...Options) macaron.Handler {
- prepareOptions(options)
- t := &toolbox{
- healthCheckJobs: make([]*healthCheck, 0, len(opt.HealthCheckers)+len(opt.HealthCheckFuncs)),
- }
-
- // Dashboard.
- m.Get(opt.URLPrefix, dashboard)
-
- // Health check.
- for _, hc := range opt.HealthCheckers {
- t.AddHealthCheck(hc)
- }
- for _, fd := range opt.HealthCheckFuncs {
- t.AddHealthCheckFunc(fd.Desc, fd.Func)
- }
- m.Route(opt.HealthCheckURL, "HEAD,GET", t.handleHealthCheck)
-
- // URL map.
- m.Get(opt.URLMapPrefix, func(rw http.ResponseWriter) {
- t.JSON(rw)
- })
-
- if !opt.DisableDebug {
- // Pprof
- m.Any(path.Join(opt.PprofURLPrefix, "cmdline"), pprof.Cmdline)
- m.Any(path.Join(opt.PprofURLPrefix, "profile"), pprof.Profile)
- m.Any(path.Join(opt.PprofURLPrefix, "symbol"), pprof.Symbol)
- m.Any(opt.PprofURLPrefix, pprof.Index)
- m.Any(path.Join(opt.PprofURLPrefix, "*"), pprof.Index)
-
- // Profile
- profilePath = opt.ProfilePath
- m.Get(opt.ProfileURLPrefix, handleProfile)
- }
-
- // Routes statistic.
- t.UrlMap = &UrlMap{
- urlmap: make(map[string]map[string]*Statistics),
- }
-
- return func(ctx *macaron.Context) {
- ctx.MapTo(t, (*Toolbox)(nil))
- }
-}
--- /dev/null
+language: go
+go:
+ - 1.x
+ - tip
+env:
+ - GO111MODULE=on
+install:
+ - go mod download
+script:
+ - go test -race -v
--- /dev/null
+---
+layout: code-of-conduct
+version: v1.0
+---
+
+This code of conduct outlines our expectations for participants within the **NYTimes/gziphandler** community, as well as steps to reporting unacceptable behavior. We are committed to providing a welcoming and inspiring community for all and expect our code of conduct to be honored. Anyone who violates this code of conduct may be banned from the community.
+
+Our open source community strives to:
+
+* **Be friendly and patient.**
+* **Be welcoming**: We strive to be a community that welcomes and supports people of all backgrounds and identities. This includes, but is not limited to members of any race, ethnicity, culture, national origin, colour, immigration status, social and economic class, educational level, sex, sexual orientation, gender identity and expression, age, size, family status, political belief, religion, and mental and physical ability.
+* **Be considerate**: Your work will be used by other people, and you in turn will depend on the work of others. Any decision you take will affect users and colleagues, and you should take those consequences into account when making decisions. Remember that we're a world-wide community, so you might not be communicating in someone else's primary language.
+* **Be respectful**: Not all of us will agree all the time, but disagreement is no excuse for poor behavior and poor manners. We might all experience some frustration now and then, but we cannot allow that frustration to turn into a personal attack. It’s important to remember that a community where people feel uncomfortable or threatened is not a productive one.
+* **Be careful in the words that we choose**: we are a community of professionals, and we conduct ourselves professionally. Be kind to others. Do not insult or put down other participants. Harassment and other exclusionary behavior aren't acceptable.
+* **Try to understand why we disagree**: Disagreements, both social and technical, happen all the time. It is important that we resolve disagreements and differing views constructively. Remember that we’re different. The strength of our community comes from its diversity, people from a wide range of backgrounds. Different people have different perspectives on issues. Being unable to understand why someone holds a viewpoint doesn’t mean that they’re wrong. Don’t forget that it is human to err and blaming each other doesn’t get us anywhere. Instead, focus on helping to resolve issues and learning from mistakes.
+
+## Definitions
+
+Harassment includes, but is not limited to:
+
+- Offensive comments related to gender, gender identity and expression, sexual orientation, disability, mental illness, neuro(a)typicality, physical appearance, body size, race, age, regional discrimination, political or religious affiliation
+- Unwelcome comments regarding a person’s lifestyle choices and practices, including those related to food, health, parenting, drugs, and employment
+- Deliberate misgendering. This includes deadnaming or persistently using a pronoun that does not correctly reflect a person's gender identity. You must address people by the name they give you when not addressing them by their username or handle
+- Physical contact and simulated physical contact (eg, textual descriptions like “*hug*” or “*backrub*”) without consent or after a request to stop
+- Threats of violence, both physical and psychological
+- Incitement of violence towards any individual, including encouraging a person to commit suicide or to engage in self-harm
+- Deliberate intimidation
+- Stalking or following
+- Harassing photography or recording, including logging online activity for harassment purposes
+- Sustained disruption of discussion
+- Unwelcome sexual attention, including gratuitous or off-topic sexual images or behaviour
+- Pattern of inappropriate social contact, such as requesting/assuming inappropriate levels of intimacy with others
+- Continued one-on-one communication after requests to cease
+- Deliberate “outing” of any aspect of a person’s identity without their consent except as necessary to protect others from intentional abuse
+- Publication of non-harassing private communication
+
+Our open source community prioritizes marginalized people’s safety over privileged people’s comfort. We will not act on complaints regarding:
+
+- ‘Reverse’ -isms, including ‘reverse racism,’ ‘reverse sexism,’ and ‘cisphobia’
+- Reasonable communication of boundaries, such as “leave me alone,” “go away,” or “I’m not discussing this with you”
+- Refusal to explain or debate social justice concepts
+- Communicating in a ‘tone’ you don’t find congenial
+- Criticizing racist, sexist, cissexist, or otherwise oppressive behavior or assumptions
+
+
+### Diversity Statement
+
+We encourage everyone to participate and are committed to building a community for all. Although we will fail at times, we seek to treat everyone both as fairly and equally as possible. Whenever a participant has made a mistake, we expect them to take responsibility for it. If someone has been harmed or offended, it is our responsibility to listen carefully and respectfully, and do our best to right the wrong.
+
+Although this list cannot be exhaustive, we explicitly honor diversity in age, gender, gender identity or expression, culture, ethnicity, language, national origin, political beliefs, profession, race, religion, sexual orientation, socioeconomic status, and technical ability. We will not tolerate discrimination based on any of the protected
+characteristics above, including participants with disabilities.
+
+### Reporting Issues
+
+If you experience or witness unacceptable behavior—or have any other concerns—please report it by contacting us via **code@nytimes.com**. All reports will be handled with discretion. In your report please include:
+
+- Your contact information.
+- Names (real, nicknames, or pseudonyms) of any individuals involved. If there are additional witnesses, please
+include them as well. Your account of what occurred, and if you believe the incident is ongoing. If there is a publicly available record (e.g. a mailing list archive or a public IRC logger), please include a link.
+- Any additional information that may be helpful.
+
+After filing a report, a representative will contact you personally, review the incident, follow up with any additional questions, and make a decision as to how to respond. If the person who is harassing you is part of the response team, they will recuse themselves from handling your incident. If the complaint originates from a member of the response team, it will be handled by a different member of the response team. We will respect confidentiality requests for the purpose of protecting victims of abuse.
+
+### Attribution & Acknowledgements
+
+We all stand on the shoulders of giants across many open source communities. We'd like to thank the communities and projects that established code of conducts and diversity statements as our inspiration:
+
+* [Django](https://www.djangoproject.com/conduct/reporting/)
+* [Python](https://www.python.org/community/diversity/)
+* [Ubuntu](http://www.ubuntu.com/about/about-ubuntu/conduct)
+* [Contributor Covenant](http://contributor-covenant.org/)
+* [Geek Feminism](http://geekfeminism.org/about/code-of-conduct/)
+* [Citizen Code of Conduct](http://citizencodeofconduct.org/)
+
+This Code of Conduct was based on https://github.com/todogroup/opencodeofconduct
--- /dev/null
+# Contributing to NYTimes/gziphandler
+
+This is an open source project started by handful of developers at The New York Times and open to the entire Go community.
+
+We really appreciate your help!
+
+## Filing issues
+
+When filing an issue, make sure to answer these five questions:
+
+1. What version of Go are you using (`go version`)?
+2. What operating system and processor architecture are you using?
+3. What did you do?
+4. What did you expect to see?
+5. What did you see instead?
+
+## Contributing code
+
+Before submitting changes, please follow these guidelines:
+
+1. Check the open issues and pull requests for existing discussions.
+2. Open an issue to discuss a new feature.
+3. Write tests.
+4. Make sure code follows the ['Go Code Review Comments'](https://github.com/golang/go/wiki/CodeReviewComments).
+5. Make sure your changes pass `go test`.
+6. Make sure the entire test suite passes locally and on Travis CI.
+7. Open a Pull Request.
+8. [Squash your commits](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html) after receiving feedback and add a [great commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
+
+Unless otherwise noted, the gziphandler source files are distributed under the Apache 2.0-style license found in the LICENSE.md file.
--- /dev/null
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2016-2017 The New York Times Company
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
--- /dev/null
+Gzip Handler
+============
+
+This is a tiny Go package which wraps HTTP handlers to transparently gzip the
+response body, for clients which support it. Although it's usually simpler to
+leave that to a reverse proxy (like nginx or Varnish), this package is useful
+when that's undesirable.
+
+## Install
+```bash
+go get -u github.com/NYTimes/gziphandler
+```
+
+## Usage
+
+Call `GzipHandler` with any handler (an object which implements the
+`http.Handler` interface), and it'll return a new handler which gzips the
+response. For example:
+
+```go
+package main
+
+import (
+ "io"
+ "net/http"
+ "github.com/NYTimes/gziphandler"
+)
+
+func main() {
+ withoutGz := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "text/plain")
+ io.WriteString(w, "Hello, World")
+ })
+
+ withGz := gziphandler.GzipHandler(withoutGz)
+
+ http.Handle("/", withGz)
+ http.ListenAndServe("0.0.0.0:8000", nil)
+}
+```
+
+
+## Documentation
+
+The docs can be found at [godoc.org][docs], as usual.
+
+
+## License
+
+[Apache 2.0][license].
+
+
+
+
+[docs]: https://godoc.org/github.com/NYTimes/gziphandler
+[license]: https://github.com/NYTimes/gziphandler/blob/master/LICENSE
--- /dev/null
+module github.com/NYTimes/gziphandler
+
+go 1.11
+
+require github.com/stretchr/testify v1.3.0
--- /dev/null
+github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
--- /dev/null
+package gziphandler // import "github.com/NYTimes/gziphandler"
+
+import (
+ "bufio"
+ "compress/gzip"
+ "fmt"
+ "io"
+ "mime"
+ "net"
+ "net/http"
+ "strconv"
+ "strings"
+ "sync"
+)
+
+const (
+ vary = "Vary"
+ acceptEncoding = "Accept-Encoding"
+ contentEncoding = "Content-Encoding"
+ contentType = "Content-Type"
+ contentLength = "Content-Length"
+)
+
+type codings map[string]float64
+
+const (
+ // DefaultQValue is the default qvalue to assign to an encoding if no explicit qvalue is set.
+ // This is actually kind of ambiguous in RFC 2616, so hopefully it's correct.
+ // The examples seem to indicate that it is.
+ DefaultQValue = 1.0
+
+ // DefaultMinSize is the default minimum size until we enable gzip compression.
+ // 1500 bytes is the MTU size for the internet since that is the largest size allowed at the network layer.
+ // If you take a file that is 1300 bytes and compress it to 800 bytes, it’s still transmitted in that same 1500 byte packet regardless, so you’ve gained nothing.
+ // That being the case, you should restrict the gzip compression to files with a size greater than a single packet, 1400 bytes (1.4KB) is a safe value.
+ DefaultMinSize = 1400
+)
+
+// gzipWriterPools stores a sync.Pool for each compression level for reuse of
+// gzip.Writers. Use poolIndex to covert a compression level to an index into
+// gzipWriterPools.
+var gzipWriterPools [gzip.BestCompression - gzip.BestSpeed + 2]*sync.Pool
+
+func init() {
+ for i := gzip.BestSpeed; i <= gzip.BestCompression; i++ {
+ addLevelPool(i)
+ }
+ addLevelPool(gzip.DefaultCompression)
+}
+
+// poolIndex maps a compression level to its index into gzipWriterPools. It
+// assumes that level is a valid gzip compression level.
+func poolIndex(level int) int {
+ // gzip.DefaultCompression == -1, so we need to treat it special.
+ if level == gzip.DefaultCompression {
+ return gzip.BestCompression - gzip.BestSpeed + 1
+ }
+ return level - gzip.BestSpeed
+}
+
+func addLevelPool(level int) {
+ gzipWriterPools[poolIndex(level)] = &sync.Pool{
+ New: func() interface{} {
+ // NewWriterLevel only returns error on a bad level, we are guaranteeing
+ // that this will be a valid level so it is okay to ignore the returned
+ // error.
+ w, _ := gzip.NewWriterLevel(nil, level)
+ return w
+ },
+ }
+}
+
+// GzipResponseWriter provides an http.ResponseWriter interface, which gzips
+// bytes before writing them to the underlying response. This doesn't close the
+// writers, so don't forget to do that.
+// It can be configured to skip response smaller than minSize.
+type GzipResponseWriter struct {
+ http.ResponseWriter
+ index int // Index for gzipWriterPools.
+ gw *gzip.Writer
+
+ code int // Saves the WriteHeader value.
+
+ minSize int // Specifed the minimum response size to gzip. If the response length is bigger than this value, it is compressed.
+ buf []byte // Holds the first part of the write before reaching the minSize or the end of the write.
+ ignore bool // If true, then we immediately passthru writes to the underlying ResponseWriter.
+
+ contentTypes []parsedContentType // Only compress if the response is one of these content-types. All are accepted if empty.
+}
+
+type GzipResponseWriterWithCloseNotify struct {
+ *GzipResponseWriter
+}
+
+func (w GzipResponseWriterWithCloseNotify) CloseNotify() <-chan bool {
+ return w.ResponseWriter.(http.CloseNotifier).CloseNotify()
+}
+
+// Write appends data to the gzip writer.
+func (w *GzipResponseWriter) Write(b []byte) (int, error) {
+ // GZIP responseWriter is initialized. Use the GZIP responseWriter.
+ if w.gw != nil {
+ return w.gw.Write(b)
+ }
+
+ // If we have already decided not to use GZIP, immediately passthrough.
+ if w.ignore {
+ return w.ResponseWriter.Write(b)
+ }
+
+ // Save the write into a buffer for later use in GZIP responseWriter (if content is long enough) or at close with regular responseWriter.
+ // On the first write, w.buf changes from nil to a valid slice
+ w.buf = append(w.buf, b...)
+
+ var (
+ cl, _ = strconv.Atoi(w.Header().Get(contentLength))
+ ct = w.Header().Get(contentType)
+ ce = w.Header().Get(contentEncoding)
+ )
+ // Only continue if they didn't already choose an encoding or a known unhandled content length or type.
+ if ce == "" && (cl == 0 || cl >= w.minSize) && (ct == "" || handleContentType(w.contentTypes, ct)) {
+ // If the current buffer is less than minSize and a Content-Length isn't set, then wait until we have more data.
+ if len(w.buf) < w.minSize && cl == 0 {
+ return len(b), nil
+ }
+ // If the Content-Length is larger than minSize or the current buffer is larger than minSize, then continue.
+ if cl >= w.minSize || len(w.buf) >= w.minSize {
+ // If a Content-Type wasn't specified, infer it from the current buffer.
+ if ct == "" {
+ ct = http.DetectContentType(w.buf)
+ w.Header().Set(contentType, ct)
+ }
+ // If the Content-Type is acceptable to GZIP, initialize the GZIP writer.
+ if handleContentType(w.contentTypes, ct) {
+ if err := w.startGzip(); err != nil {
+ return 0, err
+ }
+ return len(b), nil
+ }
+ }
+ }
+ // If we got here, we should not GZIP this response.
+ if err := w.startPlain(); err != nil {
+ return 0, err
+ }
+ return len(b), nil
+}
+
+// startGzip initializes a GZIP writer and writes the buffer.
+func (w *GzipResponseWriter) startGzip() error {
+ // Set the GZIP header.
+ w.Header().Set(contentEncoding, "gzip")
+
+ // if the Content-Length is already set, then calls to Write on gzip
+ // will fail to set the Content-Length header since its already set
+ // See: https://github.com/golang/go/issues/14975.
+ w.Header().Del(contentLength)
+
+ // Write the header to gzip response.
+ if w.code != 0 {
+ w.ResponseWriter.WriteHeader(w.code)
+ // Ensure that no other WriteHeader's happen
+ w.code = 0
+ }
+
+ // Initialize and flush the buffer into the gzip response if there are any bytes.
+ // If there aren't any, we shouldn't initialize it yet because on Close it will
+ // write the gzip header even if nothing was ever written.
+ if len(w.buf) > 0 {
+ // Initialize the GZIP response.
+ w.init()
+ n, err := w.gw.Write(w.buf)
+
+ // This should never happen (per io.Writer docs), but if the write didn't
+ // accept the entire buffer but returned no specific error, we have no clue
+ // what's going on, so abort just to be safe.
+ if err == nil && n < len(w.buf) {
+ err = io.ErrShortWrite
+ }
+ return err
+ }
+ return nil
+}
+
+// startPlain writes to sent bytes and buffer the underlying ResponseWriter without gzip.
+func (w *GzipResponseWriter) startPlain() error {
+ if w.code != 0 {
+ w.ResponseWriter.WriteHeader(w.code)
+ // Ensure that no other WriteHeader's happen
+ w.code = 0
+ }
+ w.ignore = true
+ // If Write was never called then don't call Write on the underlying ResponseWriter.
+ if w.buf == nil {
+ return nil
+ }
+ n, err := w.ResponseWriter.Write(w.buf)
+ w.buf = nil
+ // This should never happen (per io.Writer docs), but if the write didn't
+ // accept the entire buffer but returned no specific error, we have no clue
+ // what's going on, so abort just to be safe.
+ if err == nil && n < len(w.buf) {
+ err = io.ErrShortWrite
+ }
+ return err
+}
+
+// WriteHeader just saves the response code until close or GZIP effective writes.
+func (w *GzipResponseWriter) WriteHeader(code int) {
+ if w.code == 0 {
+ w.code = code
+ }
+}
+
+// init graps a new gzip writer from the gzipWriterPool and writes the correct
+// content encoding header.
+func (w *GzipResponseWriter) init() {
+ // Bytes written during ServeHTTP are redirected to this gzip writer
+ // before being written to the underlying response.
+ gzw := gzipWriterPools[w.index].Get().(*gzip.Writer)
+ gzw.Reset(w.ResponseWriter)
+ w.gw = gzw
+}
+
+// Close will close the gzip.Writer and will put it back in the gzipWriterPool.
+func (w *GzipResponseWriter) Close() error {
+ if w.ignore {
+ return nil
+ }
+
+ if w.gw == nil {
+ // GZIP not triggered yet, write out regular response.
+ err := w.startPlain()
+ // Returns the error if any at write.
+ if err != nil {
+ err = fmt.Errorf("gziphandler: write to regular responseWriter at close gets error: %q", err.Error())
+ }
+ return err
+ }
+
+ err := w.gw.Close()
+ gzipWriterPools[w.index].Put(w.gw)
+ w.gw = nil
+ return err
+}
+
+// Flush flushes the underlying *gzip.Writer and then the underlying
+// http.ResponseWriter if it is an http.Flusher. This makes GzipResponseWriter
+// an http.Flusher.
+func (w *GzipResponseWriter) Flush() {
+ if w.gw == nil && !w.ignore {
+ // Only flush once startGzip or startPlain has been called.
+ //
+ // Flush is thus a no-op until we're certain whether a plain
+ // or gzipped response will be served.
+ return
+ }
+
+ if w.gw != nil {
+ w.gw.Flush()
+ }
+
+ if fw, ok := w.ResponseWriter.(http.Flusher); ok {
+ fw.Flush()
+ }
+}
+
+// Hijack implements http.Hijacker. If the underlying ResponseWriter is a
+// Hijacker, its Hijack method is returned. Otherwise an error is returned.
+func (w *GzipResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
+ if hj, ok := w.ResponseWriter.(http.Hijacker); ok {
+ return hj.Hijack()
+ }
+ return nil, nil, fmt.Errorf("http.Hijacker interface is not supported")
+}
+
+// verify Hijacker interface implementation
+var _ http.Hijacker = &GzipResponseWriter{}
+
+// MustNewGzipLevelHandler behaves just like NewGzipLevelHandler except that in
+// an error case it panics rather than returning an error.
+func MustNewGzipLevelHandler(level int) func(http.Handler) http.Handler {
+ wrap, err := NewGzipLevelHandler(level)
+ if err != nil {
+ panic(err)
+ }
+ return wrap
+}
+
+// NewGzipLevelHandler returns a wrapper function (often known as middleware)
+// which can be used to wrap an HTTP handler to transparently gzip the response
+// body if the client supports it (via the Accept-Encoding header). Responses will
+// be encoded at the given gzip compression level. An error will be returned only
+// if an invalid gzip compression level is given, so if one can ensure the level
+// is valid, the returned error can be safely ignored.
+func NewGzipLevelHandler(level int) (func(http.Handler) http.Handler, error) {
+ return NewGzipLevelAndMinSize(level, DefaultMinSize)
+}
+
+// NewGzipLevelAndMinSize behave as NewGzipLevelHandler except it let the caller
+// specify the minimum size before compression.
+func NewGzipLevelAndMinSize(level, minSize int) (func(http.Handler) http.Handler, error) {
+ return GzipHandlerWithOpts(CompressionLevel(level), MinSize(minSize))
+}
+
+func GzipHandlerWithOpts(opts ...option) (func(http.Handler) http.Handler, error) {
+ c := &config{
+ level: gzip.DefaultCompression,
+ minSize: DefaultMinSize,
+ }
+
+ for _, o := range opts {
+ o(c)
+ }
+
+ if err := c.validate(); err != nil {
+ return nil, err
+ }
+
+ return func(h http.Handler) http.Handler {
+ index := poolIndex(c.level)
+
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Add(vary, acceptEncoding)
+ if acceptsGzip(r) {
+ gw := &GzipResponseWriter{
+ ResponseWriter: w,
+ index: index,
+ minSize: c.minSize,
+ contentTypes: c.contentTypes,
+ }
+ defer gw.Close()
+
+ if _, ok := w.(http.CloseNotifier); ok {
+ gwcn := GzipResponseWriterWithCloseNotify{gw}
+ h.ServeHTTP(gwcn, r)
+ } else {
+ h.ServeHTTP(gw, r)
+ }
+
+ } else {
+ h.ServeHTTP(w, r)
+ }
+ })
+ }, nil
+}
+
+// Parsed representation of one of the inputs to ContentTypes.
+// See https://golang.org/pkg/mime/#ParseMediaType
+type parsedContentType struct {
+ mediaType string
+ params map[string]string
+}
+
+// equals returns whether this content type matches another content type.
+func (pct parsedContentType) equals(mediaType string, params map[string]string) bool {
+ if pct.mediaType != mediaType {
+ return false
+ }
+ // if pct has no params, don't care about other's params
+ if len(pct.params) == 0 {
+ return true
+ }
+
+ // if pct has any params, they must be identical to other's.
+ if len(pct.params) != len(params) {
+ return false
+ }
+ for k, v := range pct.params {
+ if w, ok := params[k]; !ok || v != w {
+ return false
+ }
+ }
+ return true
+}
+
+// Used for functional configuration.
+type config struct {
+ minSize int
+ level int
+ contentTypes []parsedContentType
+}
+
+func (c *config) validate() error {
+ if c.level != gzip.DefaultCompression && (c.level < gzip.BestSpeed || c.level > gzip.BestCompression) {
+ return fmt.Errorf("invalid compression level requested: %d", c.level)
+ }
+
+ if c.minSize < 0 {
+ return fmt.Errorf("minimum size must be more than zero")
+ }
+
+ return nil
+}
+
+type option func(c *config)
+
+func MinSize(size int) option {
+ return func(c *config) {
+ c.minSize = size
+ }
+}
+
+func CompressionLevel(level int) option {
+ return func(c *config) {
+ c.level = level
+ }
+}
+
+// ContentTypes specifies a list of content types to compare
+// the Content-Type header to before compressing. If none
+// match, the response will be returned as-is.
+//
+// Content types are compared in a case-insensitive, whitespace-ignored
+// manner.
+//
+// A MIME type without any other directive will match a content type
+// that has the same MIME type, regardless of that content type's other
+// directives. I.e., "text/html" will match both "text/html" and
+// "text/html; charset=utf-8".
+//
+// A MIME type with any other directive will only match a content type
+// that has the same MIME type and other directives. I.e.,
+// "text/html; charset=utf-8" will only match "text/html; charset=utf-8".
+//
+// By default, responses are gzipped regardless of
+// Content-Type.
+func ContentTypes(types []string) option {
+ return func(c *config) {
+ c.contentTypes = []parsedContentType{}
+ for _, v := range types {
+ mediaType, params, err := mime.ParseMediaType(v)
+ if err == nil {
+ c.contentTypes = append(c.contentTypes, parsedContentType{mediaType, params})
+ }
+ }
+ }
+}
+
+// GzipHandler wraps an HTTP handler, to transparently gzip the response body if
+// the client supports it (via the Accept-Encoding header). This will compress at
+// the default compression level.
+func GzipHandler(h http.Handler) http.Handler {
+ wrapper, _ := NewGzipLevelHandler(gzip.DefaultCompression)
+ return wrapper(h)
+}
+
+// acceptsGzip returns true if the given HTTP request indicates that it will
+// accept a gzipped response.
+func acceptsGzip(r *http.Request) bool {
+ acceptedEncodings, _ := parseEncodings(r.Header.Get(acceptEncoding))
+ return acceptedEncodings["gzip"] > 0.0
+}
+
+// returns true if we've been configured to compress the specific content type.
+func handleContentType(contentTypes []parsedContentType, ct string) bool {
+ // If contentTypes is empty we handle all content types.
+ if len(contentTypes) == 0 {
+ return true
+ }
+
+ mediaType, params, err := mime.ParseMediaType(ct)
+ if err != nil {
+ return false
+ }
+
+ for _, c := range contentTypes {
+ if c.equals(mediaType, params) {
+ return true
+ }
+ }
+
+ return false
+}
+
+// parseEncodings attempts to parse a list of codings, per RFC 2616, as might
+// appear in an Accept-Encoding header. It returns a map of content-codings to
+// quality values, and an error containing the errors encountered. It's probably
+// safe to ignore those, because silently ignoring errors is how the internet
+// works.
+//
+// See: http://tools.ietf.org/html/rfc2616#section-14.3.
+func parseEncodings(s string) (codings, error) {
+ c := make(codings)
+ var e []string
+
+ for _, ss := range strings.Split(s, ",") {
+ coding, qvalue, err := parseCoding(ss)
+
+ if err != nil {
+ e = append(e, err.Error())
+ } else {
+ c[coding] = qvalue
+ }
+ }
+
+ // TODO (adammck): Use a proper multi-error struct, so the individual errors
+ // can be extracted if anyone cares.
+ if len(e) > 0 {
+ return c, fmt.Errorf("errors while parsing encodings: %s", strings.Join(e, ", "))
+ }
+
+ return c, nil
+}
+
+// parseCoding parses a single conding (content-coding with an optional qvalue),
+// as might appear in an Accept-Encoding header. It attempts to forgive minor
+// formatting errors.
+func parseCoding(s string) (coding string, qvalue float64, err error) {
+ for n, part := range strings.Split(s, ";") {
+ part = strings.TrimSpace(part)
+ qvalue = DefaultQValue
+
+ if n == 0 {
+ coding = strings.ToLower(part)
+ } else if strings.HasPrefix(part, "q=") {
+ qvalue, err = strconv.ParseFloat(strings.TrimPrefix(part, "q="), 64)
+
+ if qvalue < 0.0 {
+ qvalue = 0.0
+ } else if qvalue > 1.0 {
+ qvalue = 1.0
+ }
+ }
+ }
+
+ if coding == "" {
+ err = fmt.Errorf("empty content-coding")
+ }
+
+ return
+}
--- /dev/null
+// +build go1.8
+
+package gziphandler
+
+import "net/http"
+
+// Push initiates an HTTP/2 server push.
+// Push returns ErrNotSupported if the client has disabled push or if push
+// is not supported on the underlying connection.
+func (w *GzipResponseWriter) Push(target string, opts *http.PushOptions) error {
+ pusher, ok := w.ResponseWriter.(http.Pusher)
+ if ok && pusher != nil {
+ return pusher.Push(target, setAcceptEncodingForPushOptions(opts))
+ }
+ return http.ErrNotSupported
+}
+
+// setAcceptEncodingForPushOptions sets "Accept-Encoding" : "gzip" for PushOptions without overriding existing headers.
+func setAcceptEncodingForPushOptions(opts *http.PushOptions) *http.PushOptions {
+
+ if opts == nil {
+ opts = &http.PushOptions{
+ Header: http.Header{
+ acceptEncoding: []string{"gzip"},
+ },
+ }
+ return opts
+ }
+
+ if opts.Header == nil {
+ opts.Header = http.Header{
+ acceptEncoding: []string{"gzip"},
+ }
+ return opts
+ }
+
+ if encoding := opts.Header.Get(acceptEncoding); encoding == "" {
+ opts.Header.Add(acceptEncoding, "gzip")
+ return opts
+ }
+
+ return opts
+}
--- /dev/null
+Copyright (c) 2014 Olivier Poitrey <rs@dailymotion.com>
+Copyright (c) 2016-Present https://github.com/go-chi authors
+
+MIT License
+
+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.
--- /dev/null
+# CORS net/http middleware
+
+[go-chi/cors](https://github.com/go-chi/cors) is a fork of [github.com/rs/cors](https://github.com/rs/cors) that
+provides a `net/http` compatible middleware for performing preflight CORS checks on the server side. These headers
+are required for using the browser native [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API).
+
+This middleware is designed to be used as a top-level middleware on the [chi](https://github.com/go-chi/chi) router.
+Applying with within a `r.Group()` or using `With()` will not work without routes matching `OPTIONS` added.
+
+## Usage
+
+```go
+func main() {
+ r := chi.NewRouter()
+
+ // Basic CORS
+ // for more ideas, see: https://developer.github.com/v3/#cross-origin-resource-sharing
+ r.Use(cors.Handler(cors.Options{
+ // AllowedOrigins: []string{"https://foo.com"}, // Use this to allow specific origin hosts
+ AllowedOrigins: []string{"*"},
+ // AllowOriginFunc: func(r *http.Request, origin string) bool { return true },
+ AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
+ AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
+ ExposedHeaders: []string{"Link"},
+ AllowCredentials: false,
+ MaxAge: 300, // Maximum value not ignored by any of major browsers
+ }))
+
+ r.Get("/", func(w http.ResponseWriter, r *http.Request) {
+ w.Write([]byte("welcome"))
+ })
+
+ http.ListenAndServe(":3000", r)
+}
+```
+
+## Credits
+
+All credit for the original work of this middleware goes out to [github.com/rs](github.com/rs).
--- /dev/null
+// cors package is net/http handler to handle CORS related requests
+// as defined by http://www.w3.org/TR/cors/
+//
+// You can configure it by passing an option struct to cors.New:
+//
+// c := cors.New(cors.Options{
+// AllowedOrigins: []string{"foo.com"},
+// AllowedMethods: []string{"GET", "POST", "DELETE"},
+// AllowCredentials: true,
+// })
+//
+// Then insert the handler in the chain:
+//
+// handler = c.Handler(handler)
+//
+// See Options documentation for more options.
+//
+// The resulting handler is a standard net/http handler.
+package cors
+
+import (
+ "log"
+ "net/http"
+ "os"
+ "strconv"
+ "strings"
+)
+
+// Options is a configuration container to setup the CORS middleware.
+type Options struct {
+ // AllowedOrigins is a list of origins a cross-domain request can be executed from.
+ // If the special "*" value is present in the list, all origins will be allowed.
+ // An origin may contain a wildcard (*) to replace 0 or more characters
+ // (i.e.: http://*.domain.com). Usage of wildcards implies a small performance penalty.
+ // Only one wildcard can be used per origin.
+ // Default value is ["*"]
+ AllowedOrigins []string
+
+ // AllowOriginFunc is a custom function to validate the origin. It takes the origin
+ // as argument and returns true if allowed or false otherwise. If this option is
+ // set, the content of AllowedOrigins is ignored.
+ AllowOriginFunc func(r *http.Request, origin string) bool
+
+ // AllowedMethods is a list of methods the client is allowed to use with
+ // cross-domain requests. Default value is simple methods (HEAD, GET and POST).
+ AllowedMethods []string
+
+ // AllowedHeaders is list of non simple headers the client is allowed to use with
+ // cross-domain requests.
+ // If the special "*" value is present in the list, all headers will be allowed.
+ // Default value is [] but "Origin" is always appended to the list.
+ AllowedHeaders []string
+
+ // ExposedHeaders indicates which headers are safe to expose to the API of a CORS
+ // API specification
+ ExposedHeaders []string
+
+ // AllowCredentials indicates whether the request can include user credentials like
+ // cookies, HTTP authentication or client side SSL certificates.
+ AllowCredentials bool
+
+ // MaxAge indicates how long (in seconds) the results of a preflight request
+ // can be cached
+ MaxAge int
+
+ // OptionsPassthrough instructs preflight to let other potential next handlers to
+ // process the OPTIONS method. Turn this on if your application handles OPTIONS.
+ OptionsPassthrough bool
+
+ // Debugging flag adds additional output to debug server side CORS issues
+ Debug bool
+}
+
+// Logger generic interface for logger
+type Logger interface {
+ Printf(string, ...interface{})
+}
+
+// Cors http handler
+type Cors struct {
+ // Debug logger
+ Log Logger
+
+ // Normalized list of plain allowed origins
+ allowedOrigins []string
+
+ // List of allowed origins containing wildcards
+ allowedWOrigins []wildcard
+
+ // Optional origin validator function
+ allowOriginFunc func(r *http.Request, origin string) bool
+
+ // Normalized list of allowed headers
+ allowedHeaders []string
+
+ // Normalized list of allowed methods
+ allowedMethods []string
+
+ // Normalized list of exposed headers
+ exposedHeaders []string
+ maxAge int
+
+ // Set to true when allowed origins contains a "*"
+ allowedOriginsAll bool
+
+ // Set to true when allowed headers contains a "*"
+ allowedHeadersAll bool
+
+ allowCredentials bool
+ optionPassthrough bool
+}
+
+// New creates a new Cors handler with the provided options.
+func New(options Options) *Cors {
+ c := &Cors{
+ exposedHeaders: convert(options.ExposedHeaders, http.CanonicalHeaderKey),
+ allowOriginFunc: options.AllowOriginFunc,
+ allowCredentials: options.AllowCredentials,
+ maxAge: options.MaxAge,
+ optionPassthrough: options.OptionsPassthrough,
+ }
+ if options.Debug && c.Log == nil {
+ c.Log = log.New(os.Stdout, "[cors] ", log.LstdFlags)
+ }
+
+ // Normalize options
+ // Note: for origins and methods matching, the spec requires a case-sensitive matching.
+ // As it may error prone, we chose to ignore the spec here.
+
+ // Allowed Origins
+ if len(options.AllowedOrigins) == 0 {
+ if options.AllowOriginFunc == nil {
+ // Default is all origins
+ c.allowedOriginsAll = true
+ }
+ } else {
+ c.allowedOrigins = []string{}
+ c.allowedWOrigins = []wildcard{}
+ for _, origin := range options.AllowedOrigins {
+ // Normalize
+ origin = strings.ToLower(origin)
+ if origin == "*" {
+ // If "*" is present in the list, turn the whole list into a match all
+ c.allowedOriginsAll = true
+ c.allowedOrigins = nil
+ c.allowedWOrigins = nil
+ break
+ } else if i := strings.IndexByte(origin, '*'); i >= 0 {
+ // Split the origin in two: start and end string without the *
+ w := wildcard{origin[0:i], origin[i+1:]}
+ c.allowedWOrigins = append(c.allowedWOrigins, w)
+ } else {
+ c.allowedOrigins = append(c.allowedOrigins, origin)
+ }
+ }
+ }
+
+ // Allowed Headers
+ if len(options.AllowedHeaders) == 0 {
+ // Use sensible defaults
+ c.allowedHeaders = []string{"Origin", "Accept", "Content-Type"}
+ } else {
+ // Origin is always appended as some browsers will always request for this header at preflight
+ c.allowedHeaders = convert(append(options.AllowedHeaders, "Origin"), http.CanonicalHeaderKey)
+ for _, h := range options.AllowedHeaders {
+ if h == "*" {
+ c.allowedHeadersAll = true
+ c.allowedHeaders = nil
+ break
+ }
+ }
+ }
+
+ // Allowed Methods
+ if len(options.AllowedMethods) == 0 {
+ // Default is spec's "simple" methods
+ c.allowedMethods = []string{http.MethodGet, http.MethodPost, http.MethodHead}
+ } else {
+ c.allowedMethods = convert(options.AllowedMethods, strings.ToUpper)
+ }
+
+ return c
+}
+
+// Handler creates a new Cors handler with passed options.
+func Handler(options Options) func(next http.Handler) http.Handler {
+ c := New(options)
+ return c.Handler
+}
+
+// AllowAll create a new Cors handler with permissive configuration allowing all
+// origins with all standard methods with any header and credentials.
+func AllowAll() *Cors {
+ return New(Options{
+ AllowedOrigins: []string{"*"},
+ AllowedMethods: []string{
+ http.MethodHead,
+ http.MethodGet,
+ http.MethodPost,
+ http.MethodPut,
+ http.MethodPatch,
+ http.MethodDelete,
+ },
+ AllowedHeaders: []string{"*"},
+ AllowCredentials: false,
+ })
+}
+
+// Handler apply the CORS specification on the request, and add relevant CORS headers
+// as necessary.
+func (c *Cors) Handler(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if r.Method == http.MethodOptions && r.Header.Get("Access-Control-Request-Method") != "" {
+ c.logf("Handler: Preflight request")
+ c.handlePreflight(w, r)
+ // Preflight requests are standalone and should stop the chain as some other
+ // middleware may not handle OPTIONS requests correctly. One typical example
+ // is authentication middleware ; OPTIONS requests won't carry authentication
+ // headers (see #1)
+ if c.optionPassthrough {
+ next.ServeHTTP(w, r)
+ } else {
+ w.WriteHeader(http.StatusOK)
+ }
+ } else {
+ c.logf("Handler: Actual request")
+ c.handleActualRequest(w, r)
+ next.ServeHTTP(w, r)
+ }
+ })
+}
+
+// handlePreflight handles pre-flight CORS requests
+func (c *Cors) handlePreflight(w http.ResponseWriter, r *http.Request) {
+ headers := w.Header()
+ origin := r.Header.Get("Origin")
+
+ if r.Method != http.MethodOptions {
+ c.logf("Preflight aborted: %s!=OPTIONS", r.Method)
+ return
+ }
+ // Always set Vary headers
+ // see https://github.com/rs/cors/issues/10,
+ // https://github.com/rs/cors/commit/dbdca4d95feaa7511a46e6f1efb3b3aa505bc43f#commitcomment-12352001
+ headers.Add("Vary", "Origin")
+ headers.Add("Vary", "Access-Control-Request-Method")
+ headers.Add("Vary", "Access-Control-Request-Headers")
+
+ if origin == "" {
+ c.logf("Preflight aborted: empty origin")
+ return
+ }
+ if !c.isOriginAllowed(r, origin) {
+ c.logf("Preflight aborted: origin '%s' not allowed", origin)
+ return
+ }
+
+ reqMethod := r.Header.Get("Access-Control-Request-Method")
+ if !c.isMethodAllowed(reqMethod) {
+ c.logf("Preflight aborted: method '%s' not allowed", reqMethod)
+ return
+ }
+ reqHeaders := parseHeaderList(r.Header.Get("Access-Control-Request-Headers"))
+ if !c.areHeadersAllowed(reqHeaders) {
+ c.logf("Preflight aborted: headers '%v' not allowed", reqHeaders)
+ return
+ }
+ if c.allowedOriginsAll {
+ headers.Set("Access-Control-Allow-Origin", "*")
+ } else {
+ headers.Set("Access-Control-Allow-Origin", origin)
+ }
+ // Spec says: Since the list of methods can be unbounded, simply returning the method indicated
+ // by Access-Control-Request-Method (if supported) can be enough
+ headers.Set("Access-Control-Allow-Methods", strings.ToUpper(reqMethod))
+ if len(reqHeaders) > 0 {
+
+ // Spec says: Since the list of headers can be unbounded, simply returning supported headers
+ // from Access-Control-Request-Headers can be enough
+ headers.Set("Access-Control-Allow-Headers", strings.Join(reqHeaders, ", "))
+ }
+ if c.allowCredentials {
+ headers.Set("Access-Control-Allow-Credentials", "true")
+ }
+ if c.maxAge > 0 {
+ headers.Set("Access-Control-Max-Age", strconv.Itoa(c.maxAge))
+ }
+ c.logf("Preflight response headers: %v", headers)
+}
+
+// handleActualRequest handles simple cross-origin requests, actual request or redirects
+func (c *Cors) handleActualRequest(w http.ResponseWriter, r *http.Request) {
+ headers := w.Header()
+ origin := r.Header.Get("Origin")
+
+ // Always set Vary, see https://github.com/rs/cors/issues/10
+ headers.Add("Vary", "Origin")
+ if origin == "" {
+ c.logf("Actual request no headers added: missing origin")
+ return
+ }
+ if !c.isOriginAllowed(r, origin) {
+ c.logf("Actual request no headers added: origin '%s' not allowed", origin)
+ return
+ }
+
+ // Note that spec does define a way to specifically disallow a simple method like GET or
+ // POST. Access-Control-Allow-Methods is only used for pre-flight requests and the
+ // spec doesn't instruct to check the allowed methods for simple cross-origin requests.
+ // We think it's a nice feature to be able to have control on those methods though.
+ if !c.isMethodAllowed(r.Method) {
+ c.logf("Actual request no headers added: method '%s' not allowed", r.Method)
+
+ return
+ }
+ if c.allowedOriginsAll {
+ headers.Set("Access-Control-Allow-Origin", "*")
+ } else {
+ headers.Set("Access-Control-Allow-Origin", origin)
+ }
+ if len(c.exposedHeaders) > 0 {
+ headers.Set("Access-Control-Expose-Headers", strings.Join(c.exposedHeaders, ", "))
+ }
+ if c.allowCredentials {
+ headers.Set("Access-Control-Allow-Credentials", "true")
+ }
+ c.logf("Actual response added headers: %v", headers)
+}
+
+// convenience method. checks if a logger is set.
+func (c *Cors) logf(format string, a ...interface{}) {
+ if c.Log != nil {
+ c.Log.Printf(format, a...)
+ }
+}
+
+// isOriginAllowed checks if a given origin is allowed to perform cross-domain requests
+// on the endpoint
+func (c *Cors) isOriginAllowed(r *http.Request, origin string) bool {
+ if c.allowOriginFunc != nil {
+ return c.allowOriginFunc(r, origin)
+ }
+ if c.allowedOriginsAll {
+ return true
+ }
+ origin = strings.ToLower(origin)
+ for _, o := range c.allowedOrigins {
+ if o == origin {
+ return true
+ }
+ }
+ for _, w := range c.allowedWOrigins {
+ if w.match(origin) {
+ return true
+ }
+ }
+ return false
+}
+
+// isMethodAllowed checks if a given method can be used as part of a cross-domain request
+// on the endpoint
+func (c *Cors) isMethodAllowed(method string) bool {
+ if len(c.allowedMethods) == 0 {
+ // If no method allowed, always return false, even for preflight request
+ return false
+ }
+ method = strings.ToUpper(method)
+ if method == http.MethodOptions {
+ // Always allow preflight requests
+ return true
+ }
+ for _, m := range c.allowedMethods {
+ if m == method {
+ return true
+ }
+ }
+ return false
+}
+
+// areHeadersAllowed checks if a given list of headers are allowed to used within
+// a cross-domain request.
+func (c *Cors) areHeadersAllowed(requestedHeaders []string) bool {
+ if c.allowedHeadersAll || len(requestedHeaders) == 0 {
+ return true
+ }
+ for _, header := range requestedHeaders {
+ header = http.CanonicalHeaderKey(header)
+ found := false
+ for _, h := range c.allowedHeaders {
+ if h == header {
+ found = true
+ break
+ }
+ }
+ if !found {
+ return false
+ }
+ }
+ return true
+}
--- /dev/null
+package cors
+
+import "strings"
+
+const toLower = 'a' - 'A'
+
+type converter func(string) string
+
+type wildcard struct {
+ prefix string
+ suffix string
+}
+
+func (w wildcard) match(s string) bool {
+ return len(s) >= len(w.prefix+w.suffix) && strings.HasPrefix(s, w.prefix) && strings.HasSuffix(s, w.suffix)
+}
+
+// convert converts a list of string using the passed converter function
+func convert(s []string, c converter) []string {
+ out := []string{}
+ for _, i := range s {
+ out = append(out, c(i))
+ }
+ return out
+}
+
+// parseHeaderList tokenize + normalize a string containing a list of headers
+func parseHeaderList(headerList string) []string {
+ l := len(headerList)
+ h := make([]byte, 0, l)
+ upper := true
+ // Estimate the number headers in order to allocate the right splice size
+ t := 0
+ for i := 0; i < l; i++ {
+ if headerList[i] == ',' {
+ t++
+ }
+ }
+ headers := make([]string, 0, t)
+ for i := 0; i < l; i++ {
+ b := headerList[i]
+ if b >= 'a' && b <= 'z' {
+ if upper {
+ h = append(h, b-toLower)
+ } else {
+ h = append(h, b)
+ }
+ } else if b >= 'A' && b <= 'Z' {
+ if !upper {
+ h = append(h, b+toLower)
+ } else {
+ h = append(h, b)
+ }
+ } else if b == '-' || (b >= '0' && b <= '9') {
+ h = append(h, b)
+ }
+
+ if b == ' ' || b == ',' || i == l-1 {
+ if len(h) > 0 {
+ // Flush the found header
+ headers = append(headers, string(h))
+ h = h[:0]
+ upper = true
+ }
+ } else {
+ upper = b == '-'
+ }
+ }
+ return headers
+}
+++ /dev/null
-# This is the official list of Snappy-Go authors for copyright purposes.
-# This file is distinct from the CONTRIBUTORS files.
-# See the latter for an explanation.
-
-# Names should be added to this file as
-# Name or Organization <email address>
-# The email address is not required for organizations.
-
-# Please keep the list sorted.
-
-Google Inc.
-Jan Mercl <0xjnml@gmail.com>
+++ /dev/null
-# This is the official list of people who can contribute
-# (and typically have contributed) code to the Snappy-Go repository.
-# The AUTHORS file lists the copyright holders; this file
-# lists people. For example, Google employees are listed here
-# but not in AUTHORS, because Google holds the copyright.
-#
-# The submission process automatically checks to make sure
-# that people submitting code are listed in this file (by email address).
-#
-# Names should be added to this file only after verifying that
-# the individual or the individual's organization has agreed to
-# the appropriate Contributor License Agreement, found here:
-#
-# http://code.google.com/legal/individual-cla-v1.0.html
-# http://code.google.com/legal/corporate-cla-v1.0.html
-#
-# The agreement for individuals can be filled out on the web.
-#
-# When adding J Random Contributor's name to this file,
-# either J's name or J's organization's name should be
-# added to the AUTHORS file, depending on whether the
-# individual or corporate CLA was used.
-
-# Names should be added to this file like so:
-# Name <email address>
-
-# Please keep the list sorted.
-
-Jan Mercl <0xjnml@gmail.com>
-Kai Backman <kaib@golang.org>
-Marc-Antoine Ruel <maruel@chromium.org>
-Nigel Tao <nigeltao@golang.org>
-Rob Pike <r@golang.org>
-Russ Cox <rsc@golang.org>
+++ /dev/null
-Copyright (c) 2011 The Snappy-Go Authors. All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
-
- * Redistributions of source code must retain the above copyright
-notice, this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above
-copyright notice, this list of conditions and the following disclaimer
-in the documentation and/or other materials provided with the
-distribution.
- * Neither the name of Google Inc. nor the names of its
-contributors may be used to endorse or promote products derived from
-this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+++ /dev/null
-// Copyright 2011 The Snappy-Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package snappy
-
-import (
- "encoding/binary"
- "errors"
-)
-
-// ErrCorrupt reports that the input is invalid.
-var ErrCorrupt = errors.New("snappy: corrupt input")
-
-// DecodedLen returns the length of the decoded block.
-func DecodedLen(src []byte) (int, error) {
- v, _, err := decodedLen(src)
- return v, err
-}
-
-// decodedLen returns the length of the decoded block and the number of bytes
-// that the length header occupied.
-func decodedLen(src []byte) (blockLen, headerLen int, err error) {
- v, n := binary.Uvarint(src)
- if n == 0 {
- return 0, 0, ErrCorrupt
- }
- if uint64(int(v)) != v {
- return 0, 0, errors.New("snappy: decoded block is too large")
- }
- return int(v), n, nil
-}
-
-// Decode returns the decoded form of src. The returned slice may be a sub-
-// slice of dst if dst was large enough to hold the entire decoded block.
-// Otherwise, a newly allocated slice will be returned.
-// It is valid to pass a nil dst.
-func Decode(dst, src []byte) ([]byte, error) {
- dLen, s, err := decodedLen(src)
- if err != nil {
- return nil, err
- }
- if len(dst) < dLen {
- dst = make([]byte, dLen)
- }
-
- var d, offset, length int
- for s < len(src) {
- switch src[s] & 0x03 {
- case tagLiteral:
- x := uint(src[s] >> 2)
- switch {
- case x < 60:
- s += 1
- case x == 60:
- s += 2
- if s > len(src) {
- return nil, ErrCorrupt
- }
- x = uint(src[s-1])
- case x == 61:
- s += 3
- if s > len(src) {
- return nil, ErrCorrupt
- }
- x = uint(src[s-2]) | uint(src[s-1])<<8
- case x == 62:
- s += 4
- if s > len(src) {
- return nil, ErrCorrupt
- }
- x = uint(src[s-3]) | uint(src[s-2])<<8 | uint(src[s-1])<<16
- case x == 63:
- s += 5
- if s > len(src) {
- return nil, ErrCorrupt
- }
- x = uint(src[s-4]) | uint(src[s-3])<<8 | uint(src[s-2])<<16 | uint(src[s-1])<<24
- }
- length = int(x + 1)
- if length <= 0 {
- return nil, errors.New("snappy: unsupported literal length")
- }
- if length > len(dst)-d || length > len(src)-s {
- return nil, ErrCorrupt
- }
- copy(dst[d:], src[s:s+length])
- d += length
- s += length
- continue
-
- case tagCopy1:
- s += 2
- if s > len(src) {
- return nil, ErrCorrupt
- }
- length = 4 + int(src[s-2])>>2&0x7
- offset = int(src[s-2])&0xe0<<3 | int(src[s-1])
-
- case tagCopy2:
- s += 3
- if s > len(src) {
- return nil, ErrCorrupt
- }
- length = 1 + int(src[s-3])>>2
- offset = int(src[s-2]) | int(src[s-1])<<8
-
- case tagCopy4:
- return nil, errors.New("snappy: unsupported COPY_4 tag")
- }
-
- end := d + length
- if offset > d || end > len(dst) {
- return nil, ErrCorrupt
- }
- for ; d < end; d++ {
- dst[d] = dst[d-offset]
- }
- }
- if d != dLen {
- return nil, ErrCorrupt
- }
- return dst[:d], nil
-}
+++ /dev/null
-// Copyright 2011 The Snappy-Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package snappy
-
-import (
- "encoding/binary"
-)
-
-// We limit how far copy back-references can go, the same as the C++ code.
-const maxOffset = 1 << 15
-
-// emitLiteral writes a literal chunk and returns the number of bytes written.
-func emitLiteral(dst, lit []byte) int {
- i, n := 0, uint(len(lit)-1)
- switch {
- case n < 60:
- dst[0] = uint8(n)<<2 | tagLiteral
- i = 1
- case n < 1<<8:
- dst[0] = 60<<2 | tagLiteral
- dst[1] = uint8(n)
- i = 2
- case n < 1<<16:
- dst[0] = 61<<2 | tagLiteral
- dst[1] = uint8(n)
- dst[2] = uint8(n >> 8)
- i = 3
- case n < 1<<24:
- dst[0] = 62<<2 | tagLiteral
- dst[1] = uint8(n)
- dst[2] = uint8(n >> 8)
- dst[3] = uint8(n >> 16)
- i = 4
- case int64(n) < 1<<32:
- dst[0] = 63<<2 | tagLiteral
- dst[1] = uint8(n)
- dst[2] = uint8(n >> 8)
- dst[3] = uint8(n >> 16)
- dst[4] = uint8(n >> 24)
- i = 5
- default:
- panic("snappy: source buffer is too long")
- }
- if copy(dst[i:], lit) != len(lit) {
- panic("snappy: destination buffer is too short")
- }
- return i + len(lit)
-}
-
-// emitCopy writes a copy chunk and returns the number of bytes written.
-func emitCopy(dst []byte, offset, length int) int {
- i := 0
- for length > 0 {
- x := length - 4
- if 0 <= x && x < 1<<3 && offset < 1<<11 {
- dst[i+0] = uint8(offset>>8)&0x07<<5 | uint8(x)<<2 | tagCopy1
- dst[i+1] = uint8(offset)
- i += 2
- break
- }
-
- x = length
- if x > 1<<6 {
- x = 1 << 6
- }
- dst[i+0] = uint8(x-1)<<2 | tagCopy2
- dst[i+1] = uint8(offset)
- dst[i+2] = uint8(offset >> 8)
- i += 3
- length -= x
- }
- return i
-}
-
-// Encode returns the encoded form of src. The returned slice may be a sub-
-// slice of dst if dst was large enough to hold the entire encoded block.
-// Otherwise, a newly allocated slice will be returned.
-// It is valid to pass a nil dst.
-func Encode(dst, src []byte) ([]byte, error) {
- if n := MaxEncodedLen(len(src)); len(dst) < n {
- dst = make([]byte, n)
- }
-
- // The block starts with the varint-encoded length of the decompressed bytes.
- d := binary.PutUvarint(dst, uint64(len(src)))
-
- // Return early if src is short.
- if len(src) <= 4 {
- if len(src) != 0 {
- d += emitLiteral(dst[d:], src)
- }
- return dst[:d], nil
- }
-
- // Initialize the hash table. Its size ranges from 1<<8 to 1<<14 inclusive.
- const maxTableSize = 1 << 14
- shift, tableSize := uint(32-8), 1<<8
- for tableSize < maxTableSize && tableSize < len(src) {
- shift--
- tableSize *= 2
- }
- var table [maxTableSize]int
-
- // Iterate over the source bytes.
- var (
- s int // The iterator position.
- t int // The last position with the same hash as s.
- lit int // The start position of any pending literal bytes.
- )
- for s+3 < len(src) {
- // Update the hash table.
- b0, b1, b2, b3 := src[s], src[s+1], src[s+2], src[s+3]
- h := uint32(b0) | uint32(b1)<<8 | uint32(b2)<<16 | uint32(b3)<<24
- p := &table[(h*0x1e35a7bd)>>shift]
- // We need to to store values in [-1, inf) in table. To save
- // some initialization time, (re)use the table's zero value
- // and shift the values against this zero: add 1 on writes,
- // subtract 1 on reads.
- t, *p = *p-1, s+1
- // If t is invalid or src[s:s+4] differs from src[t:t+4], accumulate a literal byte.
- if t < 0 || s-t >= maxOffset || b0 != src[t] || b1 != src[t+1] || b2 != src[t+2] || b3 != src[t+3] {
- s++
- continue
- }
- // Otherwise, we have a match. First, emit any pending literal bytes.
- if lit != s {
- d += emitLiteral(dst[d:], src[lit:s])
- }
- // Extend the match to be as long as possible.
- s0 := s
- s, t = s+4, t+4
- for s < len(src) && src[s] == src[t] {
- s++
- t++
- }
- // Emit the copied bytes.
- d += emitCopy(dst[d:], s-t, s-s0)
- lit = s
- }
-
- // Emit any final pending literal bytes and return.
- if lit != len(src) {
- d += emitLiteral(dst[d:], src[lit:])
- }
- return dst[:d], nil
-}
-
-// MaxEncodedLen returns the maximum length of a snappy block, given its
-// uncompressed length.
-func MaxEncodedLen(srcLen int) int {
- // Compressed data can be defined as:
- // compressed := item* literal*
- // item := literal* copy
- //
- // The trailing literal sequence has a space blowup of at most 62/60
- // since a literal of length 60 needs one tag byte + one extra byte
- // for length information.
- //
- // Item blowup is trickier to measure. Suppose the "copy" op copies
- // 4 bytes of data. Because of a special check in the encoding code,
- // we produce a 4-byte copy only if the offset is < 65536. Therefore
- // the copy op takes 3 bytes to encode, and this type of item leads
- // to at most the 62/60 blowup for representing literals.
- //
- // Suppose the "copy" op copies 5 bytes of data. If the offset is big
- // enough, it will take 5 bytes to encode the copy op. Therefore the
- // worst case here is a one-byte literal followed by a five-byte copy.
- // That is, 6 bytes of input turn into 7 bytes of "compressed" data.
- //
- // This last factor dominates the blowup, so the final estimate is:
- return 32 + srcLen + srcLen/6
-}
+++ /dev/null
-// Copyright 2011 The Snappy-Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Package snappy implements the snappy block-based compression format.
-// It aims for very high speeds and reasonable compression.
-//
-// The C++ snappy implementation is at http://code.google.com/p/snappy/
-package snappy
-
-/*
-Each encoded block begins with the varint-encoded length of the decoded data,
-followed by a sequence of chunks. Chunks begin and end on byte boundaries. The
-first byte of each chunk is broken into its 2 least and 6 most significant bits
-called l and m: l ranges in [0, 4) and m ranges in [0, 64). l is the chunk tag.
-Zero means a literal tag. All other values mean a copy tag.
-
-For literal tags:
- - If m < 60, the next 1 + m bytes are literal bytes.
- - Otherwise, let n be the little-endian unsigned integer denoted by the next
- m - 59 bytes. The next 1 + n bytes after that are literal bytes.
-
-For copy tags, length bytes are copied from offset bytes ago, in the style of
-Lempel-Ziv compression algorithms. In particular:
- - For l == 1, the offset ranges in [0, 1<<11) and the length in [4, 12).
- The length is 4 + the low 3 bits of m. The high 3 bits of m form bits 8-10
- of the offset. The next byte is bits 0-7 of the offset.
- - For l == 2, the offset ranges in [0, 1<<16) and the length in [1, 65).
- The length is 1 + m. The offset is the little-endian unsigned integer
- denoted by the next 2 bytes.
- - For l == 3, this tag is a legacy format that is no longer supported.
-*/
-const (
- tagLiteral = 0x00
- tagCopy1 = 0x01
- tagCopy2 = 0x02
- tagCopy4 = 0x03
-)
# code.gitea.io/sdk/gitea v0.13.1
## explicit
code.gitea.io/sdk/gitea
+# gitea.com/go-chi/binding v0.0.0-20210113025129-03f1d313373c
+## explicit
+gitea.com/go-chi/binding
+# gitea.com/go-chi/cache v0.0.0-20210110083709-82c4c9ce2d5e
+## explicit
+gitea.com/go-chi/cache
+gitea.com/go-chi/cache/memcache
+# gitea.com/go-chi/captcha v0.0.0-20210110083842-e7696c336a1e
+## explicit
+gitea.com/go-chi/captcha
# gitea.com/go-chi/session v0.0.0-20210108030337-0cb48c5ba8ee
## explicit
gitea.com/go-chi/session
# gitea.com/lunny/levelqueue v0.3.0
## explicit
gitea.com/lunny/levelqueue
-# gitea.com/lunny/log v0.0.0-20190322053110-01b5df579c4e
-gitea.com/lunny/log
-# gitea.com/lunny/nodb v0.0.0-20200923032308-3238c4655727
-gitea.com/lunny/nodb
-gitea.com/lunny/nodb/config
-gitea.com/lunny/nodb/store
-gitea.com/lunny/nodb/store/driver
-gitea.com/lunny/nodb/store/goleveldb
-# gitea.com/macaron/binding v0.0.0-20190822013154-a5f53841ed2b
-## explicit
-gitea.com/macaron/binding
-# gitea.com/macaron/cache v0.0.0-20200924044943-905232fba10b
-## explicit
-gitea.com/macaron/cache
-gitea.com/macaron/cache/memcache
-# gitea.com/macaron/captcha v0.0.0-20200825161008-e8597820aaca
-## explicit
-gitea.com/macaron/captcha
-# gitea.com/macaron/cors v0.0.0-20190826180238-95aec09ea8b4
-## explicit
-gitea.com/macaron/cors
-# gitea.com/macaron/csrf v0.0.0-20190822024205-3dc5a4474439
-## explicit
-gitea.com/macaron/csrf
-# gitea.com/macaron/gzip v0.0.0-20200827120000-efa5e8477cf5
-## explicit
-gitea.com/macaron/gzip
-# gitea.com/macaron/i18n v0.0.0-20200911004404-4ca3dd0cbd60
-## explicit
-gitea.com/macaron/i18n
-# gitea.com/macaron/inject v0.0.0-20190805023432-d4c86e31027a
-## explicit
-gitea.com/macaron/inject
-# gitea.com/macaron/macaron v1.5.1-0.20201027213641-0db5d4584804
-## explicit
-gitea.com/macaron/macaron
-# gitea.com/macaron/session v0.0.0-20201103015045-a177a2701dee
-## explicit
-gitea.com/macaron/session
-gitea.com/macaron/session/couchbase
-gitea.com/macaron/session/memcache
-gitea.com/macaron/session/mysql
-gitea.com/macaron/session/nodb
-gitea.com/macaron/session/postgres
-# gitea.com/macaron/toolbox v0.0.0-20190822013122-05ff0fc766b7
-## explicit
-gitea.com/macaron/toolbox
# github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c
github.com/Azure/go-ntlmssp
+# github.com/NYTimes/gziphandler v1.1.1
+## explicit
+github.com/NYTimes/gziphandler
# github.com/PuerkitoBio/goquery v1.5.1
## explicit
github.com/PuerkitoBio/goquery
## explicit
github.com/go-chi/chi
github.com/go-chi/chi/middleware
+# github.com/go-chi/cors v1.1.1
+## explicit
+github.com/go-chi/cors
# github.com/go-enry/go-enry/v2 v2.6.0
## explicit
github.com/go-enry/go-enry/v2
# github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546
## explicit
github.com/shurcooL/vfsgen
-# github.com/siddontang/go-snappy v0.0.0-20140704025258-d8f7bb82a96d
-github.com/siddontang/go-snappy/snappy
# github.com/spf13/afero v1.3.2
github.com/spf13/afero
github.com/spf13/afero/mem