| @@ -1049,6 +1049,9 @@ RESULT_BACKEND = redis://localhost:6379 | |||||
| HOST = http://192.168.204.24 | HOST = http://192.168.204.24 | ||||
| USERNAME = | USERNAME = | ||||
| PASSWORD = | PASSWORD = | ||||
| ; cloudbrain visit opendata | |||||
| USER = cW4cMtH24eoWPE7X | |||||
| PWD = 4BPmgvK2hb2Eywwyp4YZRY4B7yQf4DAC | |||||
| [decompress] | [decompress] | ||||
| HOST = http://192.168.207.34:39987 | HOST = http://192.168.207.34:39987 | ||||
| @@ -42,6 +42,7 @@ require ( | |||||
| github.com/go-git/go-billy/v5 v5.0.0 | github.com/go-git/go-billy/v5 v5.0.0 | ||||
| github.com/go-git/go-git/v5 v5.0.0 | github.com/go-git/go-git/v5 v5.0.0 | ||||
| github.com/go-ini/ini v1.56.0 // indirect | github.com/go-ini/ini v1.56.0 // indirect | ||||
| github.com/go-macaron/auth v0.0.0-20161228062157-884c0e6c9b92 | |||||
| github.com/go-openapi/jsonreference v0.19.3 // indirect | github.com/go-openapi/jsonreference v0.19.3 // indirect | ||||
| github.com/go-redis/redis v6.15.2+incompatible | github.com/go-redis/redis v6.15.2+incompatible | ||||
| github.com/go-resty/resty/v2 v2.3.0 | github.com/go-resty/resty/v2 v2.3.0 | ||||
| @@ -121,6 +122,7 @@ require ( | |||||
| gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df | gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df | ||||
| gopkg.in/ini.v1 v1.52.0 | gopkg.in/ini.v1 v1.52.0 | ||||
| gopkg.in/ldap.v3 v3.0.2 | gopkg.in/ldap.v3 v3.0.2 | ||||
| gopkg.in/macaron.v1 v1.3.9 // indirect | |||||
| gopkg.in/testfixtures.v2 v2.5.0 | gopkg.in/testfixtures.v2 v2.5.0 | ||||
| gopkg.in/yaml.v2 v2.2.8 | gopkg.in/yaml.v2 v2.2.8 | ||||
| mvdan.cc/xurls/v2 v2.1.0 | mvdan.cc/xurls/v2 v2.1.0 | ||||
| @@ -234,6 +234,10 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 | |||||
| github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= | ||||
| github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= | ||||
| github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= | github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= | ||||
| github.com/go-macaron/auth v0.0.0-20161228062157-884c0e6c9b92 h1:71YL0o1ch/APk7bmW9jWKBY7ibd9U8NQX2bQ25EJMHI= | |||||
| github.com/go-macaron/auth v0.0.0-20161228062157-884c0e6c9b92/go.mod h1:saPJfEeea+kiiZJF4GG7TSOao0T/Yrm5rpsqYglss6k= | |||||
| github.com/go-macaron/inject v0.0.0-20160627170012-d8a0b8677191 h1:NjHlg70DuOkcAMqgt0+XA+NHwtu66MkTVVgR4fFWbcI= | |||||
| github.com/go-macaron/inject v0.0.0-20160627170012-d8a0b8677191/go.mod h1:VFI2o2q9kYsC4o7VP1HrEVosiZZTd+MVT3YZx4gqvJw= | |||||
| github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= | github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= | ||||
| github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= | github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= | ||||
| github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= | github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= | ||||
| @@ -1032,6 +1036,8 @@ gopkg.in/ini.v1 v1.52.0 h1:j+Lt/M1oPPejkniCg1TkWE2J3Eh1oZTsHSXzMTzUXn4= | |||||
| gopkg.in/ini.v1 v1.52.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= | gopkg.in/ini.v1 v1.52.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= | ||||
| gopkg.in/ldap.v3 v3.0.2 h1:R6RBtabK6e1GO0eQKtkyOFbAHO73QesLzI2w2DZ6b9w= | gopkg.in/ldap.v3 v3.0.2 h1:R6RBtabK6e1GO0eQKtkyOFbAHO73QesLzI2w2DZ6b9w= | ||||
| gopkg.in/ldap.v3 v3.0.2/go.mod h1:oxD7NyBuxchC+SgJDE1Q5Od05eGt29SDQVBmV+HYbzw= | gopkg.in/ldap.v3 v3.0.2/go.mod h1:oxD7NyBuxchC+SgJDE1Q5Od05eGt29SDQVBmV+HYbzw= | ||||
| gopkg.in/macaron.v1 v1.3.9 h1:Dw+DDRYdXgQyEsPlfAfKz+UA5qVUrH3KPD7JhmZ9MFc= | |||||
| gopkg.in/macaron.v1 v1.3.9/go.mod h1:uMZCFccv9yr5TipIalVOyAyZQuOH3OkmXvgcWwhJuP4= | |||||
| gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= | ||||
| gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= | gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= | ||||
| gopkg.in/testfixtures.v2 v2.5.0 h1:N08B7l2GzFQenyYbzqthDnKAA+cmb17iAZhhFxr7JHw= | gopkg.in/testfixtures.v2 v2.5.0 h1:N08B7l2GzFQenyYbzqthDnKAA+cmb17iAZhhFxr7JHw= | ||||
| @@ -346,3 +346,12 @@ func getUnDecompressAttachments(e Engine) ([]*Attachment, error) { | |||||
| attachments := make([]*Attachment, 0, 10) | attachments := make([]*Attachment, 0, 10) | ||||
| return attachments, e.Where("decompress_state = ? and dataset_id != 0 and name like '%.zip'", DecompressStateInit).Find(&attachments) | return attachments, e.Where("decompress_state = ? and dataset_id != 0 and name like '%.zip'", DecompressStateInit).Find(&attachments) | ||||
| } | } | ||||
| func GetAllPublicAttachments() ([]*Attachment, error) { | |||||
| return getAllPublicAttachments(x) | |||||
| } | |||||
| func getAllPublicAttachments(e Engine) ([]*Attachment, error) { | |||||
| attachments := make([]*Attachment, 0, 10) | |||||
| return attachments, e.Where("is_private = true ").Find(&attachments) | |||||
| } | |||||
| @@ -10,9 +10,13 @@ import ( | |||||
| "code.gitea.io/gitea/modules/auth" | "code.gitea.io/gitea/modules/auth" | ||||
| "code.gitea.io/gitea/modules/log" | "code.gitea.io/gitea/modules/log" | ||||
| "code.gitea.io/gitea/modules/setting" | "code.gitea.io/gitea/modules/setting" | ||||
| "encoding/base64" | |||||
| "net/http" | |||||
| "gitea.com/macaron/csrf" | "gitea.com/macaron/csrf" | ||||
| "gitea.com/macaron/macaron" | "gitea.com/macaron/macaron" | ||||
| marc_auth "github.com/go-macaron/auth" | |||||
| ) | ) | ||||
| // ToggleOptions contains required or check options | // ToggleOptions contains required or check options | ||||
| @@ -21,6 +25,7 @@ type ToggleOptions struct { | |||||
| SignOutRequired bool | SignOutRequired bool | ||||
| AdminRequired bool | AdminRequired bool | ||||
| DisableCSRF bool | DisableCSRF bool | ||||
| BasicAuthRequired bool | |||||
| } | } | ||||
| // Toggle returns toggle options as middleware | // Toggle returns toggle options as middleware | ||||
| @@ -130,5 +135,29 @@ func Toggle(options *ToggleOptions) macaron.Handler { | |||||
| } | } | ||||
| ctx.Data["PageIsAdmin"] = true | ctx.Data["PageIsAdmin"] = true | ||||
| } | } | ||||
| if options.BasicAuthRequired { | |||||
| if !basicAuth(ctx) { | |||||
| basicUnauthorized(ctx.Resp) | |||||
| return | |||||
| } | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| func basicAuth(ctx *Context) bool { | |||||
| var siteAuth = base64.StdEncoding.EncodeToString([]byte(setting.CBAuthUser + ":" + setting.CBAuthPassword)) | |||||
| auth := ctx.Req.Header.Get("Authorization") | |||||
| if !marc_auth.SecureCompare(auth, "Basic " + siteAuth) { | |||||
| return false | |||||
| } | |||||
| return true | |||||
| } | |||||
| func basicUnauthorized(res http.ResponseWriter) { | |||||
| res.Header().Set("WWW-Authenticate", "Basic realm=\"" + marc_auth.BasicRealm + "\"") | |||||
| http.Error(res, "Not Authorized", http.StatusUnauthorized) | |||||
| } | |||||
| @@ -430,6 +430,10 @@ var ( | |||||
| DecompressAddress string | DecompressAddress string | ||||
| AuthUser string | AuthUser string | ||||
| AuthPassword string | AuthPassword string | ||||
| //cloudbrain config | |||||
| CBAuthUser string | |||||
| CBAuthPassword string | |||||
| ) | ) | ||||
| // DateLang transforms standard language locale name to corresponding value in datetime plugin. | // DateLang transforms standard language locale name to corresponding value in datetime plugin. | ||||
| @@ -1097,6 +1101,10 @@ func NewContext() { | |||||
| DecompressAddress = sec.Key("HOST").MustString("http://192.168.207.34:39987") | DecompressAddress = sec.Key("HOST").MustString("http://192.168.207.34:39987") | ||||
| AuthUser = sec.Key("USER").MustString("cW4cMtH24eoWPE7X") | AuthUser = sec.Key("USER").MustString("cW4cMtH24eoWPE7X") | ||||
| AuthPassword = sec.Key("PASSWORD").MustString("4BPmgvK2hb2Eywwyp4YZRY4B7yQf4DAC") | AuthPassword = sec.Key("PASSWORD").MustString("4BPmgvK2hb2Eywwyp4YZRY4B7yQf4DAC") | ||||
| sec = Cfg.Section("cloudbrain") | |||||
| CBAuthUser = sec.Key("USER").MustString("cW4cMtH24eoWPE7X") | |||||
| CBAuthPassword = sec.Key("PWD").MustString("4BPmgvK2hb2Eywwyp4YZRY4B7yQf4DAC") | |||||
| } | } | ||||
| func loadInternalToken(sec *ini.Section) string { | func loadInternalToken(sec *ini.Section) string { | ||||
| @@ -6,6 +6,7 @@ package repo | |||||
| import ( | import ( | ||||
| contexExt "context" | contexExt "context" | ||||
| "encoding/json" | |||||
| "fmt" | "fmt" | ||||
| "net/http" | "net/http" | ||||
| "strconv" | "strconv" | ||||
| @@ -29,6 +30,11 @@ const ( | |||||
| DecompressFailed = "1" | DecompressFailed = "1" | ||||
| ) | ) | ||||
| type PublicDataset struct { | |||||
| Name string `json:"name"` | |||||
| Path string `json:"path"` | |||||
| } | |||||
| func RenderAttachmentSettings(ctx *context.Context) { | func RenderAttachmentSettings(ctx *context.Context) { | ||||
| renderAttachmentSettings(ctx) | renderAttachmentSettings(ctx) | ||||
| } | } | ||||
| @@ -374,7 +380,7 @@ func GetSuccessChunks(ctx *context.Context) { | |||||
| chunks, err = storage.GetPartInfos(fileChunk.UUID, fileChunk.UploadID) | chunks, err = storage.GetPartInfos(fileChunk.UUID, fileChunk.UploadID) | ||||
| if err != nil { | if err != nil { | ||||
| ctx.ServerError("json.Marshal failed", err) | |||||
| ctx.ServerError("GetPartInfos failed", err) | |||||
| return | return | ||||
| } | } | ||||
| } | } | ||||
| @@ -601,3 +607,39 @@ func HandleUnDecompressAttachment() { | |||||
| return | return | ||||
| } | } | ||||
| func QueryAllPublicDataset(ctx *context.Context){ | |||||
| log.Info("QueryAllPublicDataset") | |||||
| attachs, err := models.GetAllPublicAttachments() | |||||
| if err != nil { | |||||
| ctx.JSON(200, map[string]string{ | |||||
| "result_code": "-1", | |||||
| "data": "", | |||||
| }) | |||||
| return | |||||
| } | |||||
| var publicDatasets []PublicDataset | |||||
| for _, attch := range attachs { | |||||
| publicDatasets = append(publicDatasets, PublicDataset{attch.Name, | |||||
| models.AttachmentRelativePath(attch.UUID)}) | |||||
| } | |||||
| data,err := json.Marshal(publicDatasets) | |||||
| if err != nil { | |||||
| log.Error("json.Marshal failed:", err.Error()) | |||||
| ctx.JSON(200, map[string]string{ | |||||
| "result_code": "-1", | |||||
| "data": "", | |||||
| }) | |||||
| return | |||||
| } | |||||
| log.Info(string(data)) | |||||
| ctx.JSON(200, map[string]string{ | |||||
| "result_code": "0", | |||||
| "data": string(data), | |||||
| }) | |||||
| } | |||||
| @@ -245,6 +245,7 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||
| ignSignIn := context.Toggle(&context.ToggleOptions{SignInRequired: setting.Service.RequireSignInView}) | ignSignIn := context.Toggle(&context.ToggleOptions{SignInRequired: setting.Service.RequireSignInView}) | ||||
| ignSignInAndCsrf := context.Toggle(&context.ToggleOptions{DisableCSRF: true}) | ignSignInAndCsrf := context.Toggle(&context.ToggleOptions{DisableCSRF: true}) | ||||
| reqSignOut := context.Toggle(&context.ToggleOptions{SignOutRequired: true}) | reqSignOut := context.Toggle(&context.ToggleOptions{SignOutRequired: true}) | ||||
| reqBasicAuth := context.Toggle(&context.ToggleOptions{BasicAuthRequired:true}) | |||||
| bindIgnErr := binding.BindIgnErr | bindIgnErr := binding.BindIgnErr | ||||
| validation.AddBindingRules() | validation.AddBindingRules() | ||||
| @@ -533,6 +534,10 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||
| m.Post("/decompress_done_notify", repo.UpdateAttachmentDecompressState) | m.Post("/decompress_done_notify", repo.UpdateAttachmentDecompressState) | ||||
| }) | }) | ||||
| m.Group("/attachments/public", func() { | |||||
| m.Get("/query", repo.QueryAllPublicDataset) | |||||
| }, reqBasicAuth) | |||||
| m.Group("/:username", func() { | m.Group("/:username", func() { | ||||
| m.Post("/action/:action", user.Action) | m.Post("/action/:action", user.Action) | ||||
| }, reqSignIn) | }, reqSignIn) | ||||
| @@ -0,0 +1,20 @@ | |||||
| The MIT License (MIT) | |||||
| Copyright (c) 2013 Jeremy Saenz | |||||
| 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. | |||||
| @@ -0,0 +1,68 @@ | |||||
| # auth | |||||
| Macaron middleware/handler for http basic authentication. Modified from <https://github.com/martini-contrib/auth> | |||||
| [API Reference](http://godoc.org/github.com/go-macaron/auth) | |||||
| ## Simple Usage | |||||
| Use `auth.Basic` to authenticate against a pre-defined username and password: | |||||
| ~~~ go | |||||
| import ( | |||||
| "gopkg.in/macaron.v1" | |||||
| "github.com/go-macaron/auth" | |||||
| ) | |||||
| func main() { | |||||
| m := macaron.Classic() | |||||
| // authenticate every request | |||||
| m.Use(auth.Basic("username", "secretpassword")) | |||||
| m.Run() | |||||
| } | |||||
| ~~~ | |||||
| ## Advanced Usage | |||||
| Using `auth.BasicFunc` lets you authenticate on a per-user level, by checking | |||||
| the username and password in the callback function: | |||||
| ~~~ go | |||||
| import ( | |||||
| "gopkg.in/macaron.v1" | |||||
| "github.com/go-macaron/auth" | |||||
| ) | |||||
| func main() { | |||||
| m := macaron.Classic() | |||||
| // authenticate every request | |||||
| m.Use(auth.BasicFunc(func(username, password string) bool { | |||||
| return username == "admin" && password == "guessme" | |||||
| })) | |||||
| m.Run() | |||||
| } | |||||
| ~~~ | |||||
| Note that checking usernames and passwords with string comparison might be | |||||
| susceptible to timing attacks. To avoid that, use `auth.SecureCompare` instead: | |||||
| ~~~ go | |||||
| m.Use(auth.BasicFunc(func(username, password string) bool { | |||||
| return auth.SecureCompare(username, "admin") && auth.SecureCompare(password, "guessme") | |||||
| })) | |||||
| } | |||||
| ~~~ | |||||
| Upon successful authentication, the username is available to all subsequent | |||||
| handlers via the `auth.User` type: | |||||
| ~~~ go | |||||
| m.Get("/", func(user auth.User) string { | |||||
| return "Welcome, " + string(user) | |||||
| }) | |||||
| } | |||||
| ~~~ | |||||
| ## Authors | |||||
| * [Jeremy Saenz](https://github.com/codegangsta) | |||||
| * [Brendon Murphy](https://github.com/bemurphy) | |||||
| * [codeskyblue](https://github.com/codeskyblue) | |||||
| @@ -0,0 +1,59 @@ | |||||
| package auth | |||||
| import ( | |||||
| "encoding/base64" | |||||
| "net/http" | |||||
| "strings" | |||||
| "gopkg.in/macaron.v1" | |||||
| ) | |||||
| // User is the authenticated username that was extracted from the request. | |||||
| type User string | |||||
| // BasicRealm is used when setting the WWW-Authenticate response header. | |||||
| var BasicRealm = "Authorization Required" | |||||
| var basicPrefix = "Basic " | |||||
| // Basic returns a Handler that authenticates via Basic Auth. Writes a http.StatusUnauthorized | |||||
| // if authentication fails. | |||||
| func Basic(username string, password string) macaron.Handler { | |||||
| var siteAuth = base64.StdEncoding.EncodeToString([]byte(username + ":" + password)) | |||||
| return func(res http.ResponseWriter, req *http.Request, c *macaron.Context) { | |||||
| auth := req.Header.Get("Authorization") | |||||
| if !SecureCompare(auth, basicPrefix+siteAuth) { | |||||
| basicUnauthorized(res) | |||||
| return | |||||
| } | |||||
| c.Map(User(username)) | |||||
| } | |||||
| } | |||||
| // BasicFunc returns a Handler that authenticates via Basic Auth using the provided function. | |||||
| // The function should return true for a valid username/password combination. | |||||
| func BasicFunc(authfn func(string, string) bool) macaron.Handler { | |||||
| return func(res http.ResponseWriter, req *http.Request, c *macaron.Context) { | |||||
| auth := req.Header.Get("Authorization") | |||||
| n := len(basicPrefix) | |||||
| if len(auth) < n || auth[:n] != basicPrefix { | |||||
| basicUnauthorized(res) | |||||
| return | |||||
| } | |||||
| b, err := base64.StdEncoding.DecodeString(auth[n:]) | |||||
| if err != nil { | |||||
| basicUnauthorized(res) | |||||
| return | |||||
| } | |||||
| tokens := strings.SplitN(string(b), ":", 2) | |||||
| if len(tokens) != 2 || !authfn(tokens[0], tokens[1]) { | |||||
| basicUnauthorized(res) | |||||
| return | |||||
| } | |||||
| c.Map(User(tokens[0])) | |||||
| } | |||||
| } | |||||
| func basicUnauthorized(res http.ResponseWriter) { | |||||
| res.Header().Set("WWW-Authenticate", "Basic realm=\""+BasicRealm+"\"") | |||||
| http.Error(res, "Not Authorized", http.StatusUnauthorized) | |||||
| } | |||||
| @@ -0,0 +1,44 @@ | |||||
| package auth | |||||
| import ( | |||||
| "net/http" | |||||
| "gopkg.in/macaron.v1" | |||||
| ) | |||||
| var bearerPrefix = "Bearer " | |||||
| // Bearer returns a Handler that authenticates via Bearer Auth. Writes a http.StatusUnauthorized | |||||
| // if authentication fails. | |||||
| func Bearer(token string) macaron.Handler { | |||||
| return func(res http.ResponseWriter, req *http.Request, c *macaron.Context) { | |||||
| auth := req.Header.Get("Authorization") | |||||
| if !SecureCompare(auth, bearerPrefix+token) { | |||||
| bearerUnauthorized(res) | |||||
| return | |||||
| } | |||||
| c.Map(User("")) | |||||
| } | |||||
| } | |||||
| // BearerFunc returns a Handler that authenticates via Bearer Auth using the provided function. | |||||
| // The function should return true for a valid bearer token. | |||||
| func BearerFunc(authfn func(string) bool) macaron.Handler { | |||||
| return func(res http.ResponseWriter, req *http.Request, c *macaron.Context) { | |||||
| auth := req.Header.Get("Authorization") | |||||
| n := len(bearerPrefix) | |||||
| if len(auth) < n || auth[:n] != bearerPrefix { | |||||
| bearerUnauthorized(res) | |||||
| return | |||||
| } | |||||
| if !authfn(auth[n:]) { | |||||
| bearerUnauthorized(res) | |||||
| return | |||||
| } | |||||
| c.Map(User("")) | |||||
| } | |||||
| } | |||||
| func bearerUnauthorized(res http.ResponseWriter) { | |||||
| http.Error(res, "Not Authorized", http.StatusUnauthorized) | |||||
| } | |||||
| @@ -0,0 +1,14 @@ | |||||
| package auth | |||||
| import ( | |||||
| "crypto/sha512" | |||||
| "crypto/subtle" | |||||
| ) | |||||
| // SecureCompare performs a constant time compare of two strings to limit timing attacks. | |||||
| func SecureCompare(given string, actual string) bool { | |||||
| givenSha := sha512.Sum512([]byte(given)) | |||||
| actualSha := sha512.Sum512([]byte(actual)) | |||||
| return subtle.ConstantTimeCompare(givenSha[:], actualSha[:]) == 1 | |||||
| } | |||||
| @@ -0,0 +1 @@ | |||||
| box: wercker/golang@1.1.1 | |||||
| @@ -0,0 +1,14 @@ | |||||
| sudo: false | |||||
| language: go | |||||
| go: | |||||
| - 1.3 | |||||
| - 1.4 | |||||
| - 1.5 | |||||
| - tip | |||||
| script: go test -v -cover -race | |||||
| notifications: | |||||
| email: | |||||
| - u@gogs.io | |||||
| @@ -0,0 +1,191 @@ | |||||
| 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. | |||||
| @@ -0,0 +1,11 @@ | |||||
| # inject [](https://travis-ci.org/go-macaron/inject) [](http://gocover.io/github.com/go-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. | |||||
| @@ -0,0 +1,262 @@ | |||||
| // 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 | |||||
| } | |||||
| @@ -0,0 +1,3 @@ | |||||
| macaron.sublime-project | |||||
| macaron.sublime-workspace | |||||
| .idea | |||||
| @@ -0,0 +1,191 @@ | |||||
| 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. | |||||
| @@ -0,0 +1,96 @@ | |||||
| # Macaron | |||||
| [](https://github.com/go-macaron/macaron/actions?query=workflow%3AGo) | |||||
| [](https://codecov.io/gh/go-macaron/macaron) | |||||
| [](https://pkg.go.dev/gopkg.in/macaron.v1?tab=doc) | |||||
| [](https://sourcegraph.com/github.com/go-macaron/macaron) | |||||
|  | |||||
| 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 gopkg.in/macaron.v1 | |||||
| The very basic usage of Macaron: | |||||
| ```go | |||||
| package main | |||||
| import "gopkg.in/macaron.v1" | |||||
| 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 | |||||
| - [Critical Stack Intel](https://intel.criticalstack.com/): A 100% free intel marketplace from Critical Stack, Inc. | |||||
| ## Getting Help | |||||
| - [API Reference](https://gowalker.org/gopkg.in/macaron.v1) | |||||
| - [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. | |||||
| @@ -0,0 +1,9 @@ | |||||
| coverage: | |||||
| range: "60...95" | |||||
| status: | |||||
| project: | |||||
| default: | |||||
| threshold: 1% | |||||
| comment: | |||||
| layout: 'diff, files' | |||||
| @@ -0,0 +1,537 @@ | |||||
| // 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 ( | |||||
| "crypto/sha256" | |||||
| "encoding/hex" | |||||
| "html/template" | |||||
| "io" | |||||
| "io/ioutil" | |||||
| "mime/multipart" | |||||
| "net/http" | |||||
| "net/url" | |||||
| "os" | |||||
| "path" | |||||
| "path/filepath" | |||||
| "reflect" | |||||
| "strconv" | |||||
| "strings" | |||||
| "time" | |||||
| "github.com/go-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 | |||||
| } | |||||
| 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) | |||||
| 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 (c *Context) handler() Handler { | |||||
| if c.index < len(c.handlers) { | |||||
| return c.handlers[c.index] | |||||
| } | |||||
| if c.index == len(c.handlers) { | |||||
| return c.action | |||||
| } | |||||
| panic("invalid index for context handler") | |||||
| } | |||||
| func (c *Context) Next() { | |||||
| c.index += 1 | |||||
| c.run() | |||||
| } | |||||
| func (c *Context) Written() bool { | |||||
| return c.Resp.Written() | |||||
| } | |||||
| func (c *Context) run() { | |||||
| for c.index <= len(c.handlers) { | |||||
| vals, err := c.Invoke(c.handler()) | |||||
| if err != nil { | |||||
| panic(err) | |||||
| } | |||||
| c.index += 1 | |||||
| // if the handler returned something, write it to the http response | |||||
| if len(vals) > 0 { | |||||
| ev := c.GetVal(reflect.TypeOf(ReturnHandler(nil))) | |||||
| handleReturn := ev.Interface().(ReturnHandler) | |||||
| handleReturn(c, vals) | |||||
| } | |||||
| if c.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...) | |||||
| } | |||||
| 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) | |||||
| } | |||||
| // 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) | |||||
| } | |||||
| } | |||||
| cookie.Path = "/" | |||||
| if len(others) > 1 { | |||||
| if v, ok := others[1].(string); ok && len(v) > 0 { | |||||
| cookie.Path = v | |||||
| } | |||||
| } | |||||
| if len(others) > 2 { | |||||
| if v, ok := others[2].(string); ok && len(v) > 0 { | |||||
| cookie.Domain = v | |||||
| } | |||||
| } | |||||
| if len(others) > 3 { | |||||
| switch v := others[3].(type) { | |||||
| case bool: | |||||
| cookie.Secure = v | |||||
| default: | |||||
| if others[3] != nil { | |||||
| cookie.Secure = true | |||||
| } | |||||
| } | |||||
| } | |||||
| if len(others) > 4 { | |||||
| if v, ok := others[4].(bool); ok && v { | |||||
| cookie.HttpOnly = true | |||||
| } | |||||
| } | |||||
| if len(others) > 5 { | |||||
| if v, ok := others[5].(time.Time); ok { | |||||
| cookie.Expires = v | |||||
| cookie.RawExpires = v.Format(time.UnixDate) | |||||
| } | |||||
| } | |||||
| 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) | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,13 @@ | |||||
| module gopkg.in/macaron.v1 | |||||
| go 1.12 | |||||
| require ( | |||||
| github.com/go-macaron/inject v0.0.0-20160627170012-d8a0b8677191 | |||||
| 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 | |||||
| golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 | |||||
| gopkg.in/ini.v1 v1.46.0 | |||||
| ) | |||||
| @@ -0,0 +1,32 @@ | |||||
| github.com/go-macaron/inject v0.0.0-20160627170012-d8a0b8677191 h1:NjHlg70DuOkcAMqgt0+XA+NHwtu66MkTVVgR4fFWbcI= | |||||
| github.com/go-macaron/inject v0.0.0-20160627170012-d8a0b8677191/go.mod h1:VFI2o2q9kYsC4o7VP1HrEVosiZZTd+MVT3YZx4gqvJw= | |||||
| 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/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 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-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.46.0 h1:VeDZbLYGaupuvIrsYCEOe/L/2Pcs5n7hdO1ZTjporag= | |||||
| gopkg.in/ini.v1 v1.46.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= | |||||
| @@ -0,0 +1,73 @@ | |||||
| // 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) | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,334 @@ | |||||
| // +build go1.3 | |||||
| // 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 "gopkg.in/macaron.v1" | |||||
| import ( | |||||
| "io" | |||||
| "log" | |||||
| "net/http" | |||||
| "os" | |||||
| "reflect" | |||||
| "strings" | |||||
| "sync" | |||||
| "github.com/unknwon/com" | |||||
| "gopkg.in/ini.v1" | |||||
| "github.com/go-macaron/inject" | |||||
| ) | |||||
| const _VERSION = "1.3.4.0805" | |||||
| 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. | |||||
| 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")) | |||||
| var err error | |||||
| Root, err = os.Getwd() | |||||
| if err != nil { | |||||
| panic("error getting work directory: " + err.Error()) | |||||
| } | |||||
| } | |||||
| // 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 | |||||
| } | |||||
| @@ -0,0 +1,163 @@ | |||||
| // 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" | |||||
| "github.com/go-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() | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,724 @@ | |||||
| // 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 | |||||
| } | |||||
| @@ -0,0 +1,124 @@ | |||||
| // 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) | |||||
| } | |||||
| @@ -0,0 +1,76 @@ | |||||
| // 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" | |||||
| "github.com/go-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())) | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,380 @@ | |||||
| // 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) | |||||
| } | |||||
| @@ -0,0 +1,230 @@ | |||||
| // 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" | |||||
| "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(string(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 | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,390 @@ | |||||
| // 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 | |||||
| } | |||||
| @@ -0,0 +1,25 @@ | |||||
| // +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) | |||||
| } | |||||
| @@ -0,0 +1,24 @@ | |||||
| // +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) | |||||
| } | |||||
| @@ -1,5 +1,4 @@ | |||||
| # cloud.google.com/go v0.45.0 | # cloud.google.com/go v0.45.0 | ||||
| ## explicit | |||||
| cloud.google.com/go/compute/metadata | cloud.google.com/go/compute/metadata | ||||
| cloud.google.com/go/iam | cloud.google.com/go/iam | ||||
| cloud.google.com/go/internal/optional | cloud.google.com/go/internal/optional | ||||
| @@ -8,43 +7,31 @@ cloud.google.com/go/pubsub | |||||
| cloud.google.com/go/pubsub/apiv1 | cloud.google.com/go/pubsub/apiv1 | ||||
| cloud.google.com/go/pubsub/internal/distribution | cloud.google.com/go/pubsub/internal/distribution | ||||
| # gitea.com/jolheiser/gitea-vet v0.1.0 | # gitea.com/jolheiser/gitea-vet v0.1.0 | ||||
| ## explicit | |||||
| gitea.com/jolheiser/gitea-vet | gitea.com/jolheiser/gitea-vet | ||||
| gitea.com/jolheiser/gitea-vet/checks | gitea.com/jolheiser/gitea-vet/checks | ||||
| # gitea.com/lunny/levelqueue v0.3.0 | # gitea.com/lunny/levelqueue v0.3.0 | ||||
| ## explicit | |||||
| gitea.com/lunny/levelqueue | gitea.com/lunny/levelqueue | ||||
| # gitea.com/macaron/binding v0.0.0-20190822013154-a5f53841ed2b | # gitea.com/macaron/binding v0.0.0-20190822013154-a5f53841ed2b | ||||
| ## explicit | |||||
| gitea.com/macaron/binding | gitea.com/macaron/binding | ||||
| # gitea.com/macaron/cache v0.0.0-20190822004001-a6e7fee4ee76 | # gitea.com/macaron/cache v0.0.0-20190822004001-a6e7fee4ee76 | ||||
| ## explicit | |||||
| gitea.com/macaron/cache | gitea.com/macaron/cache | ||||
| gitea.com/macaron/cache/memcache | gitea.com/macaron/cache/memcache | ||||
| gitea.com/macaron/cache/redis | gitea.com/macaron/cache/redis | ||||
| # gitea.com/macaron/captcha v0.0.0-20190822015246-daa973478bae | # gitea.com/macaron/captcha v0.0.0-20190822015246-daa973478bae | ||||
| ## explicit | |||||
| gitea.com/macaron/captcha | gitea.com/macaron/captcha | ||||
| # gitea.com/macaron/cors v0.0.0-20190826180238-95aec09ea8b4 | # gitea.com/macaron/cors v0.0.0-20190826180238-95aec09ea8b4 | ||||
| ## explicit | |||||
| gitea.com/macaron/cors | gitea.com/macaron/cors | ||||
| # gitea.com/macaron/csrf v0.0.0-20190822024205-3dc5a4474439 | # gitea.com/macaron/csrf v0.0.0-20190822024205-3dc5a4474439 | ||||
| ## explicit | |||||
| gitea.com/macaron/csrf | gitea.com/macaron/csrf | ||||
| # gitea.com/macaron/gzip v0.0.0-20191118041502-506895b47aae | # gitea.com/macaron/gzip v0.0.0-20191118041502-506895b47aae | ||||
| ## explicit | |||||
| gitea.com/macaron/gzip | gitea.com/macaron/gzip | ||||
| # gitea.com/macaron/i18n v0.0.0-20190822004228-474e714e2223 | # gitea.com/macaron/i18n v0.0.0-20190822004228-474e714e2223 | ||||
| ## explicit | |||||
| gitea.com/macaron/i18n | gitea.com/macaron/i18n | ||||
| # gitea.com/macaron/inject v0.0.0-20190805023432-d4c86e31027a | # gitea.com/macaron/inject v0.0.0-20190805023432-d4c86e31027a | ||||
| ## explicit | |||||
| gitea.com/macaron/inject | gitea.com/macaron/inject | ||||
| # gitea.com/macaron/macaron v1.4.0 | # gitea.com/macaron/macaron v1.4.0 | ||||
| ## explicit | |||||
| gitea.com/macaron/macaron | gitea.com/macaron/macaron | ||||
| # gitea.com/macaron/session v0.0.0-20191207215012-613cebf0674d | # gitea.com/macaron/session v0.0.0-20191207215012-613cebf0674d | ||||
| ## explicit | |||||
| gitea.com/macaron/session | gitea.com/macaron/session | ||||
| gitea.com/macaron/session/couchbase | gitea.com/macaron/session/couchbase | ||||
| gitea.com/macaron/session/memcache | gitea.com/macaron/session/memcache | ||||
| @@ -53,13 +40,10 @@ gitea.com/macaron/session/nodb | |||||
| gitea.com/macaron/session/postgres | gitea.com/macaron/session/postgres | ||||
| gitea.com/macaron/session/redis | gitea.com/macaron/session/redis | ||||
| # gitea.com/macaron/toolbox v0.0.0-20190822013122-05ff0fc766b7 | # gitea.com/macaron/toolbox v0.0.0-20190822013122-05ff0fc766b7 | ||||
| ## explicit | |||||
| gitea.com/macaron/toolbox | gitea.com/macaron/toolbox | ||||
| # github.com/BurntSushi/toml v0.3.1 | # github.com/BurntSushi/toml v0.3.1 | ||||
| ## explicit | |||||
| github.com/BurntSushi/toml | github.com/BurntSushi/toml | ||||
| # github.com/PuerkitoBio/goquery v1.5.0 | # github.com/PuerkitoBio/goquery v1.5.0 | ||||
| ## explicit | |||||
| github.com/PuerkitoBio/goquery | github.com/PuerkitoBio/goquery | ||||
| # github.com/PuerkitoBio/purell v1.1.1 | # github.com/PuerkitoBio/purell v1.1.1 | ||||
| github.com/PuerkitoBio/purell | github.com/PuerkitoBio/purell | ||||
| @@ -68,7 +52,6 @@ github.com/PuerkitoBio/urlesc | |||||
| # github.com/RichardKnop/logging v0.0.0-20181101035820-b1d5d44c82d6 | # github.com/RichardKnop/logging v0.0.0-20181101035820-b1d5d44c82d6 | ||||
| github.com/RichardKnop/logging | github.com/RichardKnop/logging | ||||
| # github.com/RichardKnop/machinery v1.6.9 | # github.com/RichardKnop/machinery v1.6.9 | ||||
| ## explicit | |||||
| github.com/RichardKnop/machinery/v1 | github.com/RichardKnop/machinery/v1 | ||||
| github.com/RichardKnop/machinery/v1/backends/amqp | github.com/RichardKnop/machinery/v1/backends/amqp | ||||
| github.com/RichardKnop/machinery/v1/backends/dynamodb | github.com/RichardKnop/machinery/v1/backends/dynamodb | ||||
| @@ -95,7 +78,6 @@ github.com/RichardKnop/machinery/v1/tracing | |||||
| # github.com/RichardKnop/redsync v1.2.0 | # github.com/RichardKnop/redsync v1.2.0 | ||||
| github.com/RichardKnop/redsync | github.com/RichardKnop/redsync | ||||
| # github.com/RoaringBitmap/roaring v0.4.23 | # github.com/RoaringBitmap/roaring v0.4.23 | ||||
| ## explicit | |||||
| github.com/RoaringBitmap/roaring | github.com/RoaringBitmap/roaring | ||||
| # github.com/andybalholm/cascadia v1.0.0 | # github.com/andybalholm/cascadia v1.0.0 | ||||
| github.com/andybalholm/cascadia | github.com/andybalholm/cascadia | ||||
| @@ -147,10 +129,7 @@ github.com/aws/aws-sdk-go/service/sts/stsiface | |||||
| github.com/aymerick/douceur/css | github.com/aymerick/douceur/css | ||||
| # github.com/beorn7/perks v1.0.1 | # github.com/beorn7/perks v1.0.1 | ||||
| github.com/beorn7/perks/quantile | github.com/beorn7/perks/quantile | ||||
| # github.com/bgentry/speakeasy v0.1.0 | |||||
| ## explicit | |||||
| # github.com/blevesearch/bleve v1.0.7 | # github.com/blevesearch/bleve v1.0.7 | ||||
| ## explicit | |||||
| github.com/blevesearch/bleve | github.com/blevesearch/bleve | ||||
| github.com/blevesearch/bleve/analysis | github.com/blevesearch/bleve/analysis | ||||
| github.com/blevesearch/bleve/analysis/analyzer/custom | github.com/blevesearch/bleve/analysis/analyzer/custom | ||||
| @@ -212,7 +191,6 @@ github.com/bradfitz/gomemcache/memcache | |||||
| # github.com/chris-ramon/douceur v0.2.0 | # github.com/chris-ramon/douceur v0.2.0 | ||||
| github.com/chris-ramon/douceur/parser | github.com/chris-ramon/douceur/parser | ||||
| # github.com/couchbase/gomemcached v0.0.0-20191004160342-7b5da2ec40b2 | # github.com/couchbase/gomemcached v0.0.0-20191004160342-7b5da2ec40b2 | ||||
| ## explicit | |||||
| github.com/couchbase/gomemcached | github.com/couchbase/gomemcached | ||||
| github.com/couchbase/gomemcached/client | github.com/couchbase/gomemcached/client | ||||
| # github.com/couchbase/goutils v0.0.0-20191018232750-b49639060d85 | # github.com/couchbase/goutils v0.0.0-20191018232750-b49639060d85 | ||||
| @@ -227,31 +205,20 @@ github.com/couchbase/vellum/utf8 | |||||
| github.com/couchbaselabs/go-couchbase | github.com/couchbaselabs/go-couchbase | ||||
| # github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d | # github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d | ||||
| github.com/cpuguy83/go-md2man/v2/md2man | github.com/cpuguy83/go-md2man/v2/md2man | ||||
| # github.com/cznic/b v0.0.0-20181122101859-a26611c4d92d | |||||
| ## explicit | |||||
| # github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548 | |||||
| ## explicit | |||||
| # github.com/cznic/strutil v0.0.0-20181122101858-275e90344537 | |||||
| ## explicit | |||||
| # github.com/davecgh/go-spew v1.1.1 | # github.com/davecgh/go-spew v1.1.1 | ||||
| github.com/davecgh/go-spew/spew | github.com/davecgh/go-spew/spew | ||||
| # github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc | # github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc | ||||
| ## explicit | |||||
| github.com/denisenkom/go-mssqldb | github.com/denisenkom/go-mssqldb | ||||
| github.com/denisenkom/go-mssqldb/internal/cp | github.com/denisenkom/go-mssqldb/internal/cp | ||||
| github.com/denisenkom/go-mssqldb/internal/decimal | github.com/denisenkom/go-mssqldb/internal/decimal | ||||
| github.com/denisenkom/go-mssqldb/internal/querytext | github.com/denisenkom/go-mssqldb/internal/querytext | ||||
| # github.com/dgrijalva/jwt-go v3.2.0+incompatible | # github.com/dgrijalva/jwt-go v3.2.0+incompatible | ||||
| ## explicit | |||||
| github.com/dgrijalva/jwt-go | github.com/dgrijalva/jwt-go | ||||
| # github.com/dustin/go-humanize v1.0.0 | # github.com/dustin/go-humanize v1.0.0 | ||||
| ## explicit | |||||
| github.com/dustin/go-humanize | github.com/dustin/go-humanize | ||||
| # github.com/editorconfig/editorconfig-core-go/v2 v2.1.1 | # github.com/editorconfig/editorconfig-core-go/v2 v2.1.1 | ||||
| ## explicit | |||||
| github.com/editorconfig/editorconfig-core-go/v2 | github.com/editorconfig/editorconfig-core-go/v2 | ||||
| # github.com/emirpasic/gods v1.12.0 | # github.com/emirpasic/gods v1.12.0 | ||||
| ## explicit | |||||
| github.com/emirpasic/gods/containers | github.com/emirpasic/gods/containers | ||||
| github.com/emirpasic/gods/lists | github.com/emirpasic/gods/lists | ||||
| github.com/emirpasic/gods/lists/arraylist | github.com/emirpasic/gods/lists/arraylist | ||||
| @@ -259,14 +226,7 @@ github.com/emirpasic/gods/trees | |||||
| github.com/emirpasic/gods/trees/binaryheap | github.com/emirpasic/gods/trees/binaryheap | ||||
| github.com/emirpasic/gods/utils | github.com/emirpasic/gods/utils | ||||
| # github.com/ethantkoenig/rupture v0.0.0-20180203182544-0a76f03a811a | # github.com/ethantkoenig/rupture v0.0.0-20180203182544-0a76f03a811a | ||||
| ## explicit | |||||
| github.com/ethantkoenig/rupture | github.com/ethantkoenig/rupture | ||||
| # github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 | |||||
| ## explicit | |||||
| # github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 | |||||
| ## explicit | |||||
| # github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 | |||||
| ## explicit | |||||
| # github.com/fatih/color v1.9.0 | # github.com/fatih/color v1.9.0 | ||||
| github.com/fatih/color | github.com/fatih/color | ||||
| # github.com/fatih/structtag v1.2.0 | # github.com/fatih/structtag v1.2.0 | ||||
| @@ -274,13 +234,10 @@ github.com/fatih/structtag | |||||
| # github.com/fsnotify/fsnotify v1.4.7 | # github.com/fsnotify/fsnotify v1.4.7 | ||||
| github.com/fsnotify/fsnotify | github.com/fsnotify/fsnotify | ||||
| # github.com/gliderlabs/ssh v0.2.2 | # github.com/gliderlabs/ssh v0.2.2 | ||||
| ## explicit | |||||
| github.com/gliderlabs/ssh | github.com/gliderlabs/ssh | ||||
| # github.com/glycerine/go-unsnap-stream v0.0.0-20190901134440-81cf024a9e0a | # github.com/glycerine/go-unsnap-stream v0.0.0-20190901134440-81cf024a9e0a | ||||
| ## explicit | |||||
| github.com/glycerine/go-unsnap-stream | github.com/glycerine/go-unsnap-stream | ||||
| # github.com/go-enry/go-enry/v2 v2.3.0 | # github.com/go-enry/go-enry/v2 v2.3.0 | ||||
| ## explicit | |||||
| github.com/go-enry/go-enry/v2 | github.com/go-enry/go-enry/v2 | ||||
| github.com/go-enry/go-enry/v2/data | github.com/go-enry/go-enry/v2/data | ||||
| github.com/go-enry/go-enry/v2/data/rule | github.com/go-enry/go-enry/v2/data/rule | ||||
| @@ -295,14 +252,12 @@ github.com/go-git/gcfg/scanner | |||||
| github.com/go-git/gcfg/token | github.com/go-git/gcfg/token | ||||
| github.com/go-git/gcfg/types | github.com/go-git/gcfg/types | ||||
| # github.com/go-git/go-billy/v5 v5.0.0 | # github.com/go-git/go-billy/v5 v5.0.0 | ||||
| ## explicit | |||||
| github.com/go-git/go-billy/v5 | github.com/go-git/go-billy/v5 | ||||
| github.com/go-git/go-billy/v5/helper/chroot | github.com/go-git/go-billy/v5/helper/chroot | ||||
| github.com/go-git/go-billy/v5/helper/polyfill | github.com/go-git/go-billy/v5/helper/polyfill | ||||
| github.com/go-git/go-billy/v5/osfs | github.com/go-git/go-billy/v5/osfs | ||||
| github.com/go-git/go-billy/v5/util | github.com/go-git/go-billy/v5/util | ||||
| # github.com/go-git/go-git/v5 v5.0.0 | # github.com/go-git/go-git/v5 v5.0.0 | ||||
| ## explicit | |||||
| github.com/go-git/go-git/v5 | github.com/go-git/go-git/v5 | ||||
| github.com/go-git/go-git/v5/config | github.com/go-git/go-git/v5/config | ||||
| github.com/go-git/go-git/v5/internal/revision | github.com/go-git/go-git/v5/internal/revision | ||||
| @@ -347,8 +302,11 @@ github.com/go-git/go-git/v5/utils/merkletrie/index | |||||
| github.com/go-git/go-git/v5/utils/merkletrie/internal/frame | github.com/go-git/go-git/v5/utils/merkletrie/internal/frame | ||||
| github.com/go-git/go-git/v5/utils/merkletrie/noder | github.com/go-git/go-git/v5/utils/merkletrie/noder | ||||
| # github.com/go-ini/ini v1.56.0 | # github.com/go-ini/ini v1.56.0 | ||||
| ## explicit | |||||
| github.com/go-ini/ini | github.com/go-ini/ini | ||||
| # github.com/go-macaron/auth v0.0.0-20161228062157-884c0e6c9b92 | |||||
| github.com/go-macaron/auth | |||||
| # github.com/go-macaron/inject v0.0.0-20160627170012-d8a0b8677191 | |||||
| github.com/go-macaron/inject | |||||
| # github.com/go-openapi/analysis v0.19.5 | # github.com/go-openapi/analysis v0.19.5 | ||||
| github.com/go-openapi/analysis | github.com/go-openapi/analysis | ||||
| github.com/go-openapi/analysis/internal | github.com/go-openapi/analysis/internal | ||||
| @@ -359,7 +317,6 @@ github.com/go-openapi/inflect | |||||
| # github.com/go-openapi/jsonpointer v0.19.3 | # github.com/go-openapi/jsonpointer v0.19.3 | ||||
| github.com/go-openapi/jsonpointer | github.com/go-openapi/jsonpointer | ||||
| # github.com/go-openapi/jsonreference v0.19.3 | # github.com/go-openapi/jsonreference v0.19.3 | ||||
| ## explicit | |||||
| github.com/go-openapi/jsonreference | github.com/go-openapi/jsonreference | ||||
| # github.com/go-openapi/loads v0.19.3 | # github.com/go-openapi/loads v0.19.3 | ||||
| github.com/go-openapi/loads | github.com/go-openapi/loads | ||||
| @@ -381,7 +338,6 @@ github.com/go-openapi/swag | |||||
| # github.com/go-openapi/validate v0.19.3 | # github.com/go-openapi/validate v0.19.3 | ||||
| github.com/go-openapi/validate | github.com/go-openapi/validate | ||||
| # github.com/go-redis/redis v6.15.2+incompatible | # github.com/go-redis/redis v6.15.2+incompatible | ||||
| ## explicit | |||||
| github.com/go-redis/redis | github.com/go-redis/redis | ||||
| github.com/go-redis/redis/internal | github.com/go-redis/redis/internal | ||||
| github.com/go-redis/redis/internal/consistenthash | github.com/go-redis/redis/internal/consistenthash | ||||
| @@ -390,15 +346,12 @@ github.com/go-redis/redis/internal/pool | |||||
| github.com/go-redis/redis/internal/proto | github.com/go-redis/redis/internal/proto | ||||
| github.com/go-redis/redis/internal/util | github.com/go-redis/redis/internal/util | ||||
| # github.com/go-resty/resty/v2 v2.3.0 | # github.com/go-resty/resty/v2 v2.3.0 | ||||
| ## explicit | |||||
| github.com/go-resty/resty/v2 | github.com/go-resty/resty/v2 | ||||
| # github.com/go-sql-driver/mysql v1.4.1 | # github.com/go-sql-driver/mysql v1.4.1 | ||||
| ## explicit | |||||
| github.com/go-sql-driver/mysql | github.com/go-sql-driver/mysql | ||||
| # github.com/go-stack/stack v1.8.0 | # github.com/go-stack/stack v1.8.0 | ||||
| github.com/go-stack/stack | github.com/go-stack/stack | ||||
| # github.com/go-swagger/go-swagger v0.21.0 | # github.com/go-swagger/go-swagger v0.21.0 | ||||
| ## explicit | |||||
| github.com/go-swagger/go-swagger/cmd/swagger | github.com/go-swagger/go-swagger/cmd/swagger | ||||
| github.com/go-swagger/go-swagger/cmd/swagger/commands | github.com/go-swagger/go-swagger/cmd/swagger/commands | ||||
| github.com/go-swagger/go-swagger/cmd/swagger/commands/diff | github.com/go-swagger/go-swagger/cmd/swagger/commands/diff | ||||
| @@ -408,7 +361,6 @@ github.com/go-swagger/go-swagger/codescan | |||||
| github.com/go-swagger/go-swagger/generator | github.com/go-swagger/go-swagger/generator | ||||
| github.com/go-swagger/go-swagger/scan | github.com/go-swagger/go-swagger/scan | ||||
| # github.com/gobwas/glob v0.2.3 | # github.com/gobwas/glob v0.2.3 | ||||
| ## explicit | |||||
| github.com/gobwas/glob | github.com/gobwas/glob | ||||
| github.com/gobwas/glob/compiler | github.com/gobwas/glob/compiler | ||||
| github.com/gobwas/glob/match | github.com/gobwas/glob/match | ||||
| @@ -418,17 +370,14 @@ github.com/gobwas/glob/syntax/lexer | |||||
| github.com/gobwas/glob/util/runes | github.com/gobwas/glob/util/runes | ||||
| github.com/gobwas/glob/util/strings | github.com/gobwas/glob/util/strings | ||||
| # github.com/gogs/chardet v0.0.0-20191104214054-4b6791f73a28 | # github.com/gogs/chardet v0.0.0-20191104214054-4b6791f73a28 | ||||
| ## explicit | |||||
| github.com/gogs/chardet | github.com/gogs/chardet | ||||
| # github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14 | # github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14 | ||||
| ## explicit | |||||
| github.com/gogs/cron | github.com/gogs/cron | ||||
| # github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe | # github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe | ||||
| github.com/golang-sql/civil | github.com/golang-sql/civil | ||||
| # github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 | # github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 | ||||
| github.com/golang/groupcache/lru | github.com/golang/groupcache/lru | ||||
| # github.com/golang/protobuf v1.4.1 | # github.com/golang/protobuf v1.4.1 | ||||
| ## explicit | |||||
| github.com/golang/protobuf/proto | github.com/golang/protobuf/proto | ||||
| github.com/golang/protobuf/protoc-gen-go/descriptor | github.com/golang/protobuf/protoc-gen-go/descriptor | ||||
| github.com/golang/protobuf/ptypes | github.com/golang/protobuf/ptypes | ||||
| @@ -442,7 +391,6 @@ github.com/golang/snappy | |||||
| github.com/gomodule/redigo/internal | github.com/gomodule/redigo/internal | ||||
| github.com/gomodule/redigo/redis | github.com/gomodule/redigo/redis | ||||
| # github.com/google/go-github/v24 v24.0.1 | # github.com/google/go-github/v24 v24.0.1 | ||||
| ## explicit | |||||
| github.com/google/go-github/v24/github | github.com/google/go-github/v24/github | ||||
| # github.com/google/go-querystring v1.0.0 | # github.com/google/go-querystring v1.0.0 | ||||
| github.com/google/go-querystring/query | github.com/google/go-querystring/query | ||||
| @@ -451,7 +399,6 @@ github.com/google/uuid | |||||
| # github.com/googleapis/gax-go/v2 v2.0.5 | # github.com/googleapis/gax-go/v2 v2.0.5 | ||||
| github.com/googleapis/gax-go/v2 | github.com/googleapis/gax-go/v2 | ||||
| # github.com/gorilla/context v1.1.1 | # github.com/gorilla/context v1.1.1 | ||||
| ## explicit | |||||
| github.com/gorilla/context | github.com/gorilla/context | ||||
| # github.com/gorilla/css v1.0.0 | # github.com/gorilla/css v1.0.0 | ||||
| github.com/gorilla/css/scanner | github.com/gorilla/css/scanner | ||||
| @@ -466,7 +413,6 @@ github.com/gorilla/sessions | |||||
| # github.com/hashicorp/go-cleanhttp v0.5.1 | # github.com/hashicorp/go-cleanhttp v0.5.1 | ||||
| github.com/hashicorp/go-cleanhttp | github.com/hashicorp/go-cleanhttp | ||||
| # github.com/hashicorp/go-retryablehttp v0.6.6 | # github.com/hashicorp/go-retryablehttp v0.6.6 | ||||
| ## explicit | |||||
| github.com/hashicorp/go-retryablehttp | github.com/hashicorp/go-retryablehttp | ||||
| # github.com/hashicorp/hcl v1.0.0 | # github.com/hashicorp/hcl v1.0.0 | ||||
| github.com/hashicorp/hcl | github.com/hashicorp/hcl | ||||
| @@ -480,15 +426,10 @@ github.com/hashicorp/hcl/json/parser | |||||
| github.com/hashicorp/hcl/json/scanner | github.com/hashicorp/hcl/json/scanner | ||||
| github.com/hashicorp/hcl/json/token | github.com/hashicorp/hcl/json/token | ||||
| # github.com/huandu/xstrings v1.3.0 | # github.com/huandu/xstrings v1.3.0 | ||||
| ## explicit | |||||
| github.com/huandu/xstrings | github.com/huandu/xstrings | ||||
| # github.com/issue9/assert v1.3.2 | |||||
| ## explicit | |||||
| # github.com/issue9/identicon v1.0.1 | # github.com/issue9/identicon v1.0.1 | ||||
| ## explicit | |||||
| github.com/issue9/identicon | github.com/issue9/identicon | ||||
| # github.com/jaytaylor/html2text v0.0.0-20160923191438-8fb95d837f7d | # github.com/jaytaylor/html2text v0.0.0-20160923191438-8fb95d837f7d | ||||
| ## explicit | |||||
| github.com/jaytaylor/html2text | github.com/jaytaylor/html2text | ||||
| # github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 | # github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 | ||||
| github.com/jbenet/go-context/io | github.com/jbenet/go-context/io | ||||
| @@ -496,21 +437,15 @@ github.com/jbenet/go-context/io | |||||
| github.com/jessevdk/go-flags | github.com/jessevdk/go-flags | ||||
| # github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af | # github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af | ||||
| github.com/jmespath/go-jmespath | github.com/jmespath/go-jmespath | ||||
| # github.com/jmhodges/levigo v1.0.0 | |||||
| ## explicit | |||||
| # github.com/joho/godotenv v1.3.0 | |||||
| ## explicit | |||||
| # github.com/json-iterator/go v1.1.9 | # github.com/json-iterator/go v1.1.9 | ||||
| github.com/json-iterator/go | github.com/json-iterator/go | ||||
| # github.com/kballard/go-shellquote v0.0.0-20170619183022-cd60e84ee657 | # github.com/kballard/go-shellquote v0.0.0-20170619183022-cd60e84ee657 | ||||
| ## explicit | |||||
| github.com/kballard/go-shellquote | github.com/kballard/go-shellquote | ||||
| # github.com/kelseyhightower/envconfig v1.3.0 | # github.com/kelseyhightower/envconfig v1.3.0 | ||||
| github.com/kelseyhightower/envconfig | github.com/kelseyhightower/envconfig | ||||
| # github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd | # github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd | ||||
| github.com/kevinburke/ssh_config | github.com/kevinburke/ssh_config | ||||
| # github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4 | # github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4 | ||||
| ## explicit | |||||
| github.com/keybase/go-crypto/brainpool | github.com/keybase/go-crypto/brainpool | ||||
| github.com/keybase/go-crypto/cast5 | github.com/keybase/go-crypto/cast5 | ||||
| github.com/keybase/go-crypto/curve25519 | github.com/keybase/go-crypto/curve25519 | ||||
| @@ -525,7 +460,6 @@ github.com/keybase/go-crypto/openpgp/packet | |||||
| github.com/keybase/go-crypto/openpgp/s2k | github.com/keybase/go-crypto/openpgp/s2k | ||||
| github.com/keybase/go-crypto/rsa | github.com/keybase/go-crypto/rsa | ||||
| # github.com/klauspost/compress v1.10.2 | # github.com/klauspost/compress v1.10.2 | ||||
| ## explicit | |||||
| github.com/klauspost/compress/flate | github.com/klauspost/compress/flate | ||||
| github.com/klauspost/compress/gzip | github.com/klauspost/compress/gzip | ||||
| # github.com/klauspost/cpuid v1.2.3 | # github.com/klauspost/cpuid v1.2.3 | ||||
| @@ -535,16 +469,13 @@ github.com/kr/pretty | |||||
| # github.com/kr/text v0.2.0 | # github.com/kr/text v0.2.0 | ||||
| github.com/kr/text | github.com/kr/text | ||||
| # github.com/lafriks/xormstore v1.3.2 | # github.com/lafriks/xormstore v1.3.2 | ||||
| ## explicit | |||||
| github.com/lafriks/xormstore | github.com/lafriks/xormstore | ||||
| github.com/lafriks/xormstore/util | github.com/lafriks/xormstore/util | ||||
| # github.com/lib/pq v1.2.0 | # github.com/lib/pq v1.2.0 | ||||
| ## explicit | |||||
| github.com/lib/pq | github.com/lib/pq | ||||
| github.com/lib/pq/oid | github.com/lib/pq/oid | ||||
| github.com/lib/pq/scram | github.com/lib/pq/scram | ||||
| # github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 | # github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 | ||||
| ## explicit | |||||
| github.com/lunny/dingtalk_webhook | github.com/lunny/dingtalk_webhook | ||||
| # github.com/lunny/log v0.0.0-20160921050905-7887c61bf0de | # github.com/lunny/log v0.0.0-20160921050905-7887c61bf0de | ||||
| github.com/lunny/log | github.com/lunny/log | ||||
| @@ -557,13 +488,11 @@ github.com/lunny/nodb/store/goleveldb | |||||
| # github.com/magiconair/properties v1.8.1 | # github.com/magiconair/properties v1.8.1 | ||||
| github.com/magiconair/properties | github.com/magiconair/properties | ||||
| # github.com/mailru/easyjson v0.7.0 | # github.com/mailru/easyjson v0.7.0 | ||||
| ## explicit | |||||
| github.com/mailru/easyjson | github.com/mailru/easyjson | ||||
| github.com/mailru/easyjson/buffer | github.com/mailru/easyjson/buffer | ||||
| github.com/mailru/easyjson/jlexer | github.com/mailru/easyjson/jlexer | ||||
| github.com/mailru/easyjson/jwriter | github.com/mailru/easyjson/jwriter | ||||
| # github.com/markbates/goth v1.61.2 | # github.com/markbates/goth v1.61.2 | ||||
| ## explicit | |||||
| github.com/markbates/goth | github.com/markbates/goth | ||||
| github.com/markbates/goth/gothic | github.com/markbates/goth/gothic | ||||
| github.com/markbates/goth/providers/bitbucket | github.com/markbates/goth/providers/bitbucket | ||||
| @@ -581,35 +510,26 @@ github.com/markbates/goth/providers/yandex | |||||
| # github.com/mattn/go-colorable v0.1.4 | # github.com/mattn/go-colorable v0.1.4 | ||||
| github.com/mattn/go-colorable | github.com/mattn/go-colorable | ||||
| # github.com/mattn/go-isatty v0.0.11 | # github.com/mattn/go-isatty v0.0.11 | ||||
| ## explicit | |||||
| github.com/mattn/go-isatty | github.com/mattn/go-isatty | ||||
| # github.com/mattn/go-oci8 v0.0.0-20190320171441-14ba190cf52d | |||||
| ## explicit | |||||
| # github.com/mattn/go-runewidth v0.0.7 | # github.com/mattn/go-runewidth v0.0.7 | ||||
| github.com/mattn/go-runewidth | github.com/mattn/go-runewidth | ||||
| # github.com/mattn/go-sqlite3 v1.11.0 | # github.com/mattn/go-sqlite3 v1.11.0 | ||||
| ## explicit | |||||
| github.com/mattn/go-sqlite3 | github.com/mattn/go-sqlite3 | ||||
| # github.com/matttproud/golang_protobuf_extensions v1.0.1 | # github.com/matttproud/golang_protobuf_extensions v1.0.1 | ||||
| github.com/matttproud/golang_protobuf_extensions/pbutil | github.com/matttproud/golang_protobuf_extensions/pbutil | ||||
| # github.com/mcuadros/go-version v0.0.0-20190308113854-92cdf37c5b75 | # github.com/mcuadros/go-version v0.0.0-20190308113854-92cdf37c5b75 | ||||
| ## explicit | |||||
| github.com/mcuadros/go-version | github.com/mcuadros/go-version | ||||
| # github.com/mgechev/dots v0.0.0-20190921121421-c36f7dcfbb81 | # github.com/mgechev/dots v0.0.0-20190921121421-c36f7dcfbb81 | ||||
| ## explicit | |||||
| github.com/mgechev/dots | github.com/mgechev/dots | ||||
| # github.com/mgechev/revive v1.0.2 | # github.com/mgechev/revive v1.0.2 | ||||
| ## explicit | |||||
| github.com/mgechev/revive/formatter | github.com/mgechev/revive/formatter | ||||
| github.com/mgechev/revive/lint | github.com/mgechev/revive/lint | ||||
| github.com/mgechev/revive/rule | github.com/mgechev/revive/rule | ||||
| # github.com/microcosm-cc/bluemonday v1.0.3-0.20191119130333-0a75d7616912 | # github.com/microcosm-cc/bluemonday v1.0.3-0.20191119130333-0a75d7616912 | ||||
| ## explicit | |||||
| github.com/microcosm-cc/bluemonday | github.com/microcosm-cc/bluemonday | ||||
| # github.com/minio/md5-simd v1.1.0 | # github.com/minio/md5-simd v1.1.0 | ||||
| github.com/minio/md5-simd | github.com/minio/md5-simd | ||||
| # github.com/minio/minio-go v6.0.14+incompatible | # github.com/minio/minio-go v6.0.14+incompatible | ||||
| ## explicit | |||||
| github.com/minio/minio-go | github.com/minio/minio-go | ||||
| github.com/minio/minio-go/pkg/credentials | github.com/minio/minio-go/pkg/credentials | ||||
| github.com/minio/minio-go/pkg/encrypt | github.com/minio/minio-go/pkg/encrypt | ||||
| @@ -617,7 +537,6 @@ github.com/minio/minio-go/pkg/s3signer | |||||
| github.com/minio/minio-go/pkg/s3utils | github.com/minio/minio-go/pkg/s3utils | ||||
| github.com/minio/minio-go/pkg/set | github.com/minio/minio-go/pkg/set | ||||
| # github.com/minio/minio-go/v6 v6.0.57 | # github.com/minio/minio-go/v6 v6.0.57 | ||||
| ## explicit | |||||
| github.com/minio/minio-go/v6 | github.com/minio/minio-go/v6 | ||||
| github.com/minio/minio-go/v6/pkg/credentials | github.com/minio/minio-go/v6/pkg/credentials | ||||
| github.com/minio/minio-go/v6/pkg/encrypt | github.com/minio/minio-go/v6/pkg/encrypt | ||||
| @@ -628,7 +547,6 @@ github.com/minio/minio-go/v6/pkg/tags | |||||
| # github.com/minio/sha256-simd v0.1.1 | # github.com/minio/sha256-simd v0.1.1 | ||||
| github.com/minio/sha256-simd | github.com/minio/sha256-simd | ||||
| # github.com/mitchellh/go-homedir v1.1.0 | # github.com/mitchellh/go-homedir v1.1.0 | ||||
| ## explicit | |||||
| github.com/mitchellh/go-homedir | github.com/mitchellh/go-homedir | ||||
| # github.com/mitchellh/mapstructure v1.1.2 | # github.com/mitchellh/mapstructure v1.1.2 | ||||
| github.com/mitchellh/mapstructure | github.com/mitchellh/mapstructure | ||||
| @@ -641,21 +559,16 @@ github.com/mrjones/oauth | |||||
| # github.com/mschoch/smat v0.2.0 | # github.com/mschoch/smat v0.2.0 | ||||
| github.com/mschoch/smat | github.com/mschoch/smat | ||||
| # github.com/msteinert/pam v0.0.0-20151204160544-02ccfbfaf0cc | # github.com/msteinert/pam v0.0.0-20151204160544-02ccfbfaf0cc | ||||
| ## explicit | |||||
| github.com/msteinert/pam | github.com/msteinert/pam | ||||
| # github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 | # github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 | ||||
| ## explicit | |||||
| github.com/nfnt/resize | github.com/nfnt/resize | ||||
| # github.com/niklasfasching/go-org v0.1.9 | # github.com/niklasfasching/go-org v0.1.9 | ||||
| ## explicit | |||||
| github.com/niklasfasching/go-org/org | github.com/niklasfasching/go-org/org | ||||
| # github.com/olekukonko/tablewriter v0.0.4 | # github.com/olekukonko/tablewriter v0.0.4 | ||||
| github.com/olekukonko/tablewriter | github.com/olekukonko/tablewriter | ||||
| # github.com/oliamb/cutter v0.2.2 | # github.com/oliamb/cutter v0.2.2 | ||||
| ## explicit | |||||
| github.com/oliamb/cutter | github.com/oliamb/cutter | ||||
| # github.com/olivere/elastic/v7 v7.0.9 | # github.com/olivere/elastic/v7 v7.0.9 | ||||
| ## explicit | |||||
| github.com/olivere/elastic/v7 | github.com/olivere/elastic/v7 | ||||
| github.com/olivere/elastic/v7/config | github.com/olivere/elastic/v7/config | ||||
| github.com/olivere/elastic/v7/uritemplates | github.com/olivere/elastic/v7/uritemplates | ||||
| @@ -668,53 +581,41 @@ github.com/pelletier/go-toml | |||||
| # github.com/philhofer/fwd v1.0.0 | # github.com/philhofer/fwd v1.0.0 | ||||
| github.com/philhofer/fwd | github.com/philhofer/fwd | ||||
| # github.com/pkg/errors v0.9.1 | # github.com/pkg/errors v0.9.1 | ||||
| ## explicit | |||||
| github.com/pkg/errors | github.com/pkg/errors | ||||
| # github.com/pmezard/go-difflib v1.0.0 | # github.com/pmezard/go-difflib v1.0.0 | ||||
| github.com/pmezard/go-difflib/difflib | github.com/pmezard/go-difflib/difflib | ||||
| # github.com/pquerna/otp v1.2.0 | # github.com/pquerna/otp v1.2.0 | ||||
| ## explicit | |||||
| github.com/pquerna/otp | github.com/pquerna/otp | ||||
| github.com/pquerna/otp/hotp | github.com/pquerna/otp/hotp | ||||
| github.com/pquerna/otp/totp | github.com/pquerna/otp/totp | ||||
| # github.com/prometheus/client_golang v1.1.0 | # github.com/prometheus/client_golang v1.1.0 | ||||
| ## explicit | |||||
| github.com/prometheus/client_golang/prometheus | github.com/prometheus/client_golang/prometheus | ||||
| github.com/prometheus/client_golang/prometheus/internal | github.com/prometheus/client_golang/prometheus/internal | ||||
| github.com/prometheus/client_golang/prometheus/promhttp | github.com/prometheus/client_golang/prometheus/promhttp | ||||
| # github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 | # github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 | ||||
| ## explicit | |||||
| github.com/prometheus/client_model/go | github.com/prometheus/client_model/go | ||||
| # github.com/prometheus/common v0.6.0 | # github.com/prometheus/common v0.6.0 | ||||
| github.com/prometheus/common/expfmt | github.com/prometheus/common/expfmt | ||||
| github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg | github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg | ||||
| github.com/prometheus/common/model | github.com/prometheus/common/model | ||||
| # github.com/prometheus/procfs v0.0.4 | # github.com/prometheus/procfs v0.0.4 | ||||
| ## explicit | |||||
| github.com/prometheus/procfs | github.com/prometheus/procfs | ||||
| github.com/prometheus/procfs/internal/fs | github.com/prometheus/procfs/internal/fs | ||||
| github.com/prometheus/procfs/internal/util | github.com/prometheus/procfs/internal/util | ||||
| # github.com/quasoft/websspi v1.0.0 | # github.com/quasoft/websspi v1.0.0 | ||||
| ## explicit | |||||
| github.com/quasoft/websspi | github.com/quasoft/websspi | ||||
| github.com/quasoft/websspi/secctx | github.com/quasoft/websspi/secctx | ||||
| # github.com/remyoudompheng/bigfft v0.0.0-20190321074620-2f0d2b0e0001 | |||||
| ## explicit | |||||
| # github.com/russross/blackfriday/v2 v2.0.1 | # github.com/russross/blackfriday/v2 v2.0.1 | ||||
| github.com/russross/blackfriday/v2 | github.com/russross/blackfriday/v2 | ||||
| # github.com/satori/go.uuid v1.2.0 | # github.com/satori/go.uuid v1.2.0 | ||||
| ## explicit | |||||
| github.com/satori/go.uuid | github.com/satori/go.uuid | ||||
| # github.com/sergi/go-diff v1.1.0 | # github.com/sergi/go-diff v1.1.0 | ||||
| ## explicit | |||||
| github.com/sergi/go-diff/diffmatchpatch | github.com/sergi/go-diff/diffmatchpatch | ||||
| # github.com/shurcooL/httpfs v0.0.0-20190527155220-6a4d4a70508b | # github.com/shurcooL/httpfs v0.0.0-20190527155220-6a4d4a70508b | ||||
| ## explicit | |||||
| github.com/shurcooL/httpfs/vfsutil | github.com/shurcooL/httpfs/vfsutil | ||||
| # github.com/shurcooL/sanitized_anchor_name v1.0.0 | # github.com/shurcooL/sanitized_anchor_name v1.0.0 | ||||
| github.com/shurcooL/sanitized_anchor_name | github.com/shurcooL/sanitized_anchor_name | ||||
| # github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd | # github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd | ||||
| ## explicit | |||||
| github.com/shurcooL/vfsgen | github.com/shurcooL/vfsgen | ||||
| # github.com/siddontang/go-snappy v0.0.0-20140704025258-d8f7bb82a96d | # github.com/siddontang/go-snappy v0.0.0-20140704025258-d8f7bb82a96d | ||||
| github.com/siddontang/go-snappy/snappy | github.com/siddontang/go-snappy/snappy | ||||
| @@ -734,7 +635,6 @@ github.com/steveyen/gtreap | |||||
| # github.com/streadway/amqp v0.0.0-20190214183023-884228600bc9 | # github.com/streadway/amqp v0.0.0-20190214183023-884228600bc9 | ||||
| github.com/streadway/amqp | github.com/streadway/amqp | ||||
| # github.com/stretchr/testify v1.4.0 | # github.com/stretchr/testify v1.4.0 | ||||
| ## explicit | |||||
| github.com/stretchr/testify/assert | github.com/stretchr/testify/assert | ||||
| github.com/stretchr/testify/require | github.com/stretchr/testify/require | ||||
| # github.com/syndtr/goleveldb v1.0.0 | # github.com/syndtr/goleveldb v1.0.0 | ||||
| @@ -750,38 +650,28 @@ github.com/syndtr/goleveldb/leveldb/opt | |||||
| github.com/syndtr/goleveldb/leveldb/storage | github.com/syndtr/goleveldb/leveldb/storage | ||||
| github.com/syndtr/goleveldb/leveldb/table | github.com/syndtr/goleveldb/leveldb/table | ||||
| github.com/syndtr/goleveldb/leveldb/util | github.com/syndtr/goleveldb/leveldb/util | ||||
| # github.com/tecbot/gorocksdb v0.0.0-20181010114359-8752a9433481 | |||||
| ## explicit | |||||
| # github.com/tinylib/msgp v1.1.2 | # github.com/tinylib/msgp v1.1.2 | ||||
| ## explicit | |||||
| github.com/tinylib/msgp/msgp | github.com/tinylib/msgp/msgp | ||||
| # github.com/toqueteos/trie v1.0.0 | # github.com/toqueteos/trie v1.0.0 | ||||
| github.com/toqueteos/trie | github.com/toqueteos/trie | ||||
| # github.com/toqueteos/webbrowser v1.2.0 | # github.com/toqueteos/webbrowser v1.2.0 | ||||
| github.com/toqueteos/webbrowser | github.com/toqueteos/webbrowser | ||||
| # github.com/tstranex/u2f v1.0.0 | # github.com/tstranex/u2f v1.0.0 | ||||
| ## explicit | |||||
| github.com/tstranex/u2f | github.com/tstranex/u2f | ||||
| # github.com/unknwon/cae v1.0.0 | # github.com/unknwon/cae v1.0.0 | ||||
| ## explicit | |||||
| github.com/unknwon/cae | github.com/unknwon/cae | ||||
| github.com/unknwon/cae/zip | github.com/unknwon/cae/zip | ||||
| # github.com/unknwon/com v1.0.1 | # github.com/unknwon/com v1.0.1 | ||||
| ## explicit | |||||
| github.com/unknwon/com | github.com/unknwon/com | ||||
| # github.com/unknwon/i18n v0.0.0-20190805065654-5c6446a380b6 | # github.com/unknwon/i18n v0.0.0-20190805065654-5c6446a380b6 | ||||
| ## explicit | |||||
| github.com/unknwon/i18n | github.com/unknwon/i18n | ||||
| # github.com/unknwon/paginater v0.0.0-20151104151617-7748a72e0141 | # github.com/unknwon/paginater v0.0.0-20151104151617-7748a72e0141 | ||||
| ## explicit | |||||
| github.com/unknwon/paginater | github.com/unknwon/paginater | ||||
| # github.com/urfave/cli v1.22.1 | # github.com/urfave/cli v1.22.1 | ||||
| ## explicit | |||||
| github.com/urfave/cli | github.com/urfave/cli | ||||
| # github.com/willf/bitset v1.1.10 | # github.com/willf/bitset v1.1.10 | ||||
| github.com/willf/bitset | github.com/willf/bitset | ||||
| # github.com/xanzy/go-gitlab v0.31.0 | # github.com/xanzy/go-gitlab v0.31.0 | ||||
| ## explicit | |||||
| github.com/xanzy/go-gitlab | github.com/xanzy/go-gitlab | ||||
| # github.com/xanzy/ssh-agent v0.2.1 | # github.com/xanzy/ssh-agent v0.2.1 | ||||
| github.com/xanzy/ssh-agent | github.com/xanzy/ssh-agent | ||||
| @@ -790,10 +680,8 @@ github.com/xdg/scram | |||||
| # github.com/xdg/stringprep v1.0.0 | # github.com/xdg/stringprep v1.0.0 | ||||
| github.com/xdg/stringprep | github.com/xdg/stringprep | ||||
| # github.com/yohcop/openid-go v1.0.0 | # github.com/yohcop/openid-go v1.0.0 | ||||
| ## explicit | |||||
| github.com/yohcop/openid-go | github.com/yohcop/openid-go | ||||
| # github.com/yuin/goldmark v1.1.27 | # github.com/yuin/goldmark v1.1.27 | ||||
| ## explicit | |||||
| github.com/yuin/goldmark | github.com/yuin/goldmark | ||||
| github.com/yuin/goldmark/ast | github.com/yuin/goldmark/ast | ||||
| github.com/yuin/goldmark/extension | github.com/yuin/goldmark/extension | ||||
| @@ -804,7 +692,6 @@ github.com/yuin/goldmark/renderer/html | |||||
| github.com/yuin/goldmark/text | github.com/yuin/goldmark/text | ||||
| github.com/yuin/goldmark/util | github.com/yuin/goldmark/util | ||||
| # github.com/yuin/goldmark-meta v0.0.0-20191126180153-f0638e958b60 | # github.com/yuin/goldmark-meta v0.0.0-20191126180153-f0638e958b60 | ||||
| ## explicit | |||||
| github.com/yuin/goldmark-meta | github.com/yuin/goldmark-meta | ||||
| # go.etcd.io/bbolt v1.3.4 | # go.etcd.io/bbolt v1.3.4 | ||||
| go.etcd.io/bbolt | go.etcd.io/bbolt | ||||
| @@ -856,7 +743,6 @@ go.opencensus.io/trace/internal | |||||
| go.opencensus.io/trace/propagation | go.opencensus.io/trace/propagation | ||||
| go.opencensus.io/trace/tracestate | go.opencensus.io/trace/tracestate | ||||
| # golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79 | # golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79 | ||||
| ## explicit | |||||
| golang.org/x/crypto/acme | golang.org/x/crypto/acme | ||||
| golang.org/x/crypto/acme/autocert | golang.org/x/crypto/acme/autocert | ||||
| golang.org/x/crypto/argon2 | golang.org/x/crypto/argon2 | ||||
| @@ -884,11 +770,9 @@ golang.org/x/crypto/ssh/agent | |||||
| golang.org/x/crypto/ssh/internal/bcrypt_pbkdf | golang.org/x/crypto/ssh/internal/bcrypt_pbkdf | ||||
| golang.org/x/crypto/ssh/knownhosts | golang.org/x/crypto/ssh/knownhosts | ||||
| # golang.org/x/mod v0.3.0 | # golang.org/x/mod v0.3.0 | ||||
| ## explicit | |||||
| golang.org/x/mod/module | golang.org/x/mod/module | ||||
| golang.org/x/mod/semver | golang.org/x/mod/semver | ||||
| # golang.org/x/net v0.0.0-20200513185701-a91f0712d120 | # golang.org/x/net v0.0.0-20200513185701-a91f0712d120 | ||||
| ## explicit | |||||
| golang.org/x/net/context | golang.org/x/net/context | ||||
| golang.org/x/net/context/ctxhttp | golang.org/x/net/context/ctxhttp | ||||
| golang.org/x/net/html | golang.org/x/net/html | ||||
| @@ -904,7 +788,6 @@ golang.org/x/net/proxy | |||||
| golang.org/x/net/publicsuffix | golang.org/x/net/publicsuffix | ||||
| golang.org/x/net/trace | golang.org/x/net/trace | ||||
| # golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d | # golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d | ||||
| ## explicit | |||||
| golang.org/x/oauth2 | golang.org/x/oauth2 | ||||
| golang.org/x/oauth2/google | golang.org/x/oauth2/google | ||||
| golang.org/x/oauth2/internal | golang.org/x/oauth2/internal | ||||
| @@ -914,7 +797,6 @@ golang.org/x/oauth2/jwt | |||||
| golang.org/x/sync/errgroup | golang.org/x/sync/errgroup | ||||
| golang.org/x/sync/semaphore | golang.org/x/sync/semaphore | ||||
| # golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f | # golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f | ||||
| ## explicit | |||||
| golang.org/x/sys/cpu | golang.org/x/sys/cpu | ||||
| golang.org/x/sys/internal/unsafeheader | golang.org/x/sys/internal/unsafeheader | ||||
| golang.org/x/sys/unix | golang.org/x/sys/unix | ||||
| @@ -922,7 +804,6 @@ golang.org/x/sys/windows | |||||
| golang.org/x/sys/windows/svc | golang.org/x/sys/windows/svc | ||||
| golang.org/x/sys/windows/svc/debug | golang.org/x/sys/windows/svc/debug | ||||
| # golang.org/x/text v0.3.2 | # golang.org/x/text v0.3.2 | ||||
| ## explicit | |||||
| golang.org/x/text/encoding | golang.org/x/text/encoding | ||||
| golang.org/x/text/encoding/charmap | golang.org/x/text/encoding/charmap | ||||
| golang.org/x/text/encoding/htmlindex | golang.org/x/text/encoding/htmlindex | ||||
| @@ -945,10 +826,8 @@ golang.org/x/text/unicode/bidi | |||||
| golang.org/x/text/unicode/norm | golang.org/x/text/unicode/norm | ||||
| golang.org/x/text/width | golang.org/x/text/width | ||||
| # golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 | # golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 | ||||
| ## explicit | |||||
| golang.org/x/time/rate | golang.org/x/time/rate | ||||
| # golang.org/x/tools v0.0.0-20200515220128-d3bf790afa53 | # golang.org/x/tools v0.0.0-20200515220128-d3bf790afa53 | ||||
| ## explicit | |||||
| golang.org/x/tools/cover | golang.org/x/tools/cover | ||||
| golang.org/x/tools/go/analysis | golang.org/x/tools/go/analysis | ||||
| golang.org/x/tools/go/analysis/internal/analysisflags | golang.org/x/tools/go/analysis/internal/analysisflags | ||||
| @@ -988,7 +867,6 @@ google.golang.org/api/transport/grpc | |||||
| google.golang.org/api/transport/http | google.golang.org/api/transport/http | ||||
| google.golang.org/api/transport/http/internal/propagation | google.golang.org/api/transport/http/internal/propagation | ||||
| # google.golang.org/appengine v1.6.5 | # google.golang.org/appengine v1.6.5 | ||||
| ## explicit | |||||
| google.golang.org/appengine | google.golang.org/appengine | ||||
| google.golang.org/appengine/cloudsql | google.golang.org/appengine/cloudsql | ||||
| google.golang.org/appengine/internal | google.golang.org/appengine/internal | ||||
| @@ -1087,41 +965,32 @@ google.golang.org/protobuf/types/known/durationpb | |||||
| google.golang.org/protobuf/types/known/emptypb | google.golang.org/protobuf/types/known/emptypb | ||||
| google.golang.org/protobuf/types/known/timestamppb | google.golang.org/protobuf/types/known/timestamppb | ||||
| # gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc | # gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc | ||||
| ## explicit | |||||
| gopkg.in/alexcesaro/quotedprintable.v3 | gopkg.in/alexcesaro/quotedprintable.v3 | ||||
| # gopkg.in/asn1-ber.v1 v1.0.0-20150924051756-4e86f4367175 | # gopkg.in/asn1-ber.v1 v1.0.0-20150924051756-4e86f4367175 | ||||
| ## explicit | |||||
| gopkg.in/asn1-ber.v1 | gopkg.in/asn1-ber.v1 | ||||
| # gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df | # gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df | ||||
| ## explicit | |||||
| gopkg.in/gomail.v2 | gopkg.in/gomail.v2 | ||||
| # gopkg.in/ini.v1 v1.52.0 | # gopkg.in/ini.v1 v1.52.0 | ||||
| ## explicit | |||||
| gopkg.in/ini.v1 | gopkg.in/ini.v1 | ||||
| # gopkg.in/ldap.v3 v3.0.2 | # gopkg.in/ldap.v3 v3.0.2 | ||||
| ## explicit | |||||
| gopkg.in/ldap.v3 | gopkg.in/ldap.v3 | ||||
| # gopkg.in/macaron.v1 v1.3.9 | |||||
| gopkg.in/macaron.v1 | |||||
| # gopkg.in/testfixtures.v2 v2.5.0 | # gopkg.in/testfixtures.v2 v2.5.0 | ||||
| ## explicit | |||||
| gopkg.in/testfixtures.v2 | gopkg.in/testfixtures.v2 | ||||
| # gopkg.in/toqueteos/substring.v1 v1.0.2 | # gopkg.in/toqueteos/substring.v1 v1.0.2 | ||||
| gopkg.in/toqueteos/substring.v1 | gopkg.in/toqueteos/substring.v1 | ||||
| # gopkg.in/warnings.v0 v0.1.2 | # gopkg.in/warnings.v0 v0.1.2 | ||||
| gopkg.in/warnings.v0 | gopkg.in/warnings.v0 | ||||
| # gopkg.in/yaml.v2 v2.2.8 | # gopkg.in/yaml.v2 v2.2.8 | ||||
| ## explicit | |||||
| gopkg.in/yaml.v2 | gopkg.in/yaml.v2 | ||||
| # mvdan.cc/xurls/v2 v2.1.0 | # mvdan.cc/xurls/v2 v2.1.0 | ||||
| ## explicit | |||||
| mvdan.cc/xurls/v2 | mvdan.cc/xurls/v2 | ||||
| # strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251 | # strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251 | ||||
| ## explicit | |||||
| strk.kbt.io/projects/go/libravatar | strk.kbt.io/projects/go/libravatar | ||||
| # xorm.io/builder v0.3.7 | # xorm.io/builder v0.3.7 | ||||
| ## explicit | |||||
| xorm.io/builder | xorm.io/builder | ||||
| # xorm.io/xorm v1.0.1 | # xorm.io/xorm v1.0.1 | ||||
| ## explicit | |||||
| xorm.io/xorm | xorm.io/xorm | ||||
| xorm.io/xorm/caches | xorm.io/xorm/caches | ||||
| xorm.io/xorm/contexts | xorm.io/xorm/contexts | ||||