| @@ -1049,6 +1049,9 @@ RESULT_BACKEND = redis://localhost:6379 | |||
| HOST = http://192.168.204.24 | |||
| USERNAME = | |||
| PASSWORD = | |||
| ; cloudbrain visit opendata | |||
| USER = cW4cMtH24eoWPE7X | |||
| PWD = 4BPmgvK2hb2Eywwyp4YZRY4B7yQf4DAC | |||
| [decompress] | |||
| 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-git/v5 v5.0.0 | |||
| 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-redis/redis v6.15.2+incompatible | |||
| 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/ini.v1 v1.52.0 | |||
| gopkg.in/ldap.v3 v3.0.2 | |||
| gopkg.in/macaron.v1 v1.3.9 // indirect | |||
| gopkg.in/testfixtures.v2 v2.5.0 | |||
| gopkg.in/yaml.v2 v2.2.8 | |||
| 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.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= | |||
| 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.17.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/ldap.v3 v3.0.2 h1:R6RBtabK6e1GO0eQKtkyOFbAHO73QesLzI2w2DZ6b9w= | |||
| 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/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= | |||
| 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) | |||
| 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/log" | |||
| "code.gitea.io/gitea/modules/setting" | |||
| "encoding/base64" | |||
| "net/http" | |||
| "gitea.com/macaron/csrf" | |||
| "gitea.com/macaron/macaron" | |||
| marc_auth "github.com/go-macaron/auth" | |||
| ) | |||
| // ToggleOptions contains required or check options | |||
| @@ -21,6 +25,7 @@ type ToggleOptions struct { | |||
| SignOutRequired bool | |||
| AdminRequired bool | |||
| DisableCSRF bool | |||
| BasicAuthRequired bool | |||
| } | |||
| // Toggle returns toggle options as middleware | |||
| @@ -130,5 +135,29 @@ func Toggle(options *ToggleOptions) macaron.Handler { | |||
| } | |||
| 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 | |||
| AuthUser string | |||
| AuthPassword string | |||
| //cloudbrain config | |||
| CBAuthUser string | |||
| CBAuthPassword string | |||
| ) | |||
| // 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") | |||
| AuthUser = sec.Key("USER").MustString("cW4cMtH24eoWPE7X") | |||
| 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 { | |||
| @@ -6,6 +6,7 @@ package repo | |||
| import ( | |||
| contexExt "context" | |||
| "encoding/json" | |||
| "fmt" | |||
| "net/http" | |||
| "strconv" | |||
| @@ -29,6 +30,11 @@ const ( | |||
| DecompressFailed = "1" | |||
| ) | |||
| type PublicDataset struct { | |||
| Name string `json:"name"` | |||
| Path string `json:"path"` | |||
| } | |||
| func RenderAttachmentSettings(ctx *context.Context) { | |||
| renderAttachmentSettings(ctx) | |||
| } | |||
| @@ -374,7 +380,7 @@ func GetSuccessChunks(ctx *context.Context) { | |||
| chunks, err = storage.GetPartInfos(fileChunk.UUID, fileChunk.UploadID) | |||
| if err != nil { | |||
| ctx.ServerError("json.Marshal failed", err) | |||
| ctx.ServerError("GetPartInfos failed", err) | |||
| return | |||
| } | |||
| } | |||
| @@ -601,3 +607,39 @@ func HandleUnDecompressAttachment() { | |||
| 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}) | |||
| ignSignInAndCsrf := context.Toggle(&context.ToggleOptions{DisableCSRF: true}) | |||
| reqSignOut := context.Toggle(&context.ToggleOptions{SignOutRequired: true}) | |||
| reqBasicAuth := context.Toggle(&context.ToggleOptions{BasicAuthRequired:true}) | |||
| bindIgnErr := binding.BindIgnErr | |||
| validation.AddBindingRules() | |||
| @@ -533,6 +534,10 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
| m.Post("/decompress_done_notify", repo.UpdateAttachmentDecompressState) | |||
| }) | |||
| m.Group("/attachments/public", func() { | |||
| m.Get("/query", repo.QueryAllPublicDataset) | |||
| }, reqBasicAuth) | |||
| m.Group("/:username", func() { | |||
| m.Post("/action/:action", user.Action) | |||
| }, 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 | |||
| ## explicit | |||
| cloud.google.com/go/compute/metadata | |||
| cloud.google.com/go/iam | |||
| 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/internal/distribution | |||
| # gitea.com/jolheiser/gitea-vet v0.1.0 | |||
| ## explicit | |||
| gitea.com/jolheiser/gitea-vet | |||
| gitea.com/jolheiser/gitea-vet/checks | |||
| # gitea.com/lunny/levelqueue v0.3.0 | |||
| ## explicit | |||
| gitea.com/lunny/levelqueue | |||
| # gitea.com/macaron/binding v0.0.0-20190822013154-a5f53841ed2b | |||
| ## explicit | |||
| gitea.com/macaron/binding | |||
| # gitea.com/macaron/cache v0.0.0-20190822004001-a6e7fee4ee76 | |||
| ## explicit | |||
| gitea.com/macaron/cache | |||
| gitea.com/macaron/cache/memcache | |||
| gitea.com/macaron/cache/redis | |||
| # gitea.com/macaron/captcha v0.0.0-20190822015246-daa973478bae | |||
| ## explicit | |||
| gitea.com/macaron/captcha | |||
| # gitea.com/macaron/cors v0.0.0-20190826180238-95aec09ea8b4 | |||
| ## explicit | |||
| gitea.com/macaron/cors | |||
| # gitea.com/macaron/csrf v0.0.0-20190822024205-3dc5a4474439 | |||
| ## explicit | |||
| gitea.com/macaron/csrf | |||
| # gitea.com/macaron/gzip v0.0.0-20191118041502-506895b47aae | |||
| ## explicit | |||
| gitea.com/macaron/gzip | |||
| # gitea.com/macaron/i18n v0.0.0-20190822004228-474e714e2223 | |||
| ## explicit | |||
| gitea.com/macaron/i18n | |||
| # gitea.com/macaron/inject v0.0.0-20190805023432-d4c86e31027a | |||
| ## explicit | |||
| gitea.com/macaron/inject | |||
| # gitea.com/macaron/macaron v1.4.0 | |||
| ## explicit | |||
| gitea.com/macaron/macaron | |||
| # gitea.com/macaron/session v0.0.0-20191207215012-613cebf0674d | |||
| ## explicit | |||
| gitea.com/macaron/session | |||
| gitea.com/macaron/session/couchbase | |||
| gitea.com/macaron/session/memcache | |||
| @@ -53,13 +40,10 @@ gitea.com/macaron/session/nodb | |||
| gitea.com/macaron/session/postgres | |||
| gitea.com/macaron/session/redis | |||
| # gitea.com/macaron/toolbox v0.0.0-20190822013122-05ff0fc766b7 | |||
| ## explicit | |||
| gitea.com/macaron/toolbox | |||
| # github.com/BurntSushi/toml v0.3.1 | |||
| ## explicit | |||
| github.com/BurntSushi/toml | |||
| # github.com/PuerkitoBio/goquery v1.5.0 | |||
| ## explicit | |||
| github.com/PuerkitoBio/goquery | |||
| # github.com/PuerkitoBio/purell v1.1.1 | |||
| 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 | |||
| # github.com/RichardKnop/machinery v1.6.9 | |||
| ## explicit | |||
| github.com/RichardKnop/machinery/v1 | |||
| github.com/RichardKnop/machinery/v1/backends/amqp | |||
| 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 | |||
| # github.com/RoaringBitmap/roaring v0.4.23 | |||
| ## explicit | |||
| github.com/RoaringBitmap/roaring | |||
| # github.com/andybalholm/cascadia v1.0.0 | |||
| github.com/andybalholm/cascadia | |||
| @@ -147,10 +129,7 @@ github.com/aws/aws-sdk-go/service/sts/stsiface | |||
| github.com/aymerick/douceur/css | |||
| # github.com/beorn7/perks v1.0.1 | |||
| github.com/beorn7/perks/quantile | |||
| # github.com/bgentry/speakeasy v0.1.0 | |||
| ## explicit | |||
| # github.com/blevesearch/bleve v1.0.7 | |||
| ## explicit | |||
| github.com/blevesearch/bleve | |||
| github.com/blevesearch/bleve/analysis | |||
| 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/parser | |||
| # github.com/couchbase/gomemcached v0.0.0-20191004160342-7b5da2ec40b2 | |||
| ## explicit | |||
| github.com/couchbase/gomemcached | |||
| github.com/couchbase/gomemcached/client | |||
| # 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/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d | |||
| 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/spew | |||
| # github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc | |||
| ## explicit | |||
| github.com/denisenkom/go-mssqldb | |||
| github.com/denisenkom/go-mssqldb/internal/cp | |||
| github.com/denisenkom/go-mssqldb/internal/decimal | |||
| github.com/denisenkom/go-mssqldb/internal/querytext | |||
| # github.com/dgrijalva/jwt-go v3.2.0+incompatible | |||
| ## explicit | |||
| github.com/dgrijalva/jwt-go | |||
| # github.com/dustin/go-humanize v1.0.0 | |||
| ## explicit | |||
| github.com/dustin/go-humanize | |||
| # github.com/editorconfig/editorconfig-core-go/v2 v2.1.1 | |||
| ## explicit | |||
| github.com/editorconfig/editorconfig-core-go/v2 | |||
| # github.com/emirpasic/gods v1.12.0 | |||
| ## explicit | |||
| github.com/emirpasic/gods/containers | |||
| github.com/emirpasic/gods/lists | |||
| 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/utils | |||
| # github.com/ethantkoenig/rupture v0.0.0-20180203182544-0a76f03a811a | |||
| ## explicit | |||
| 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 | |||
| # 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 | |||
| # github.com/gliderlabs/ssh v0.2.2 | |||
| ## explicit | |||
| github.com/gliderlabs/ssh | |||
| # github.com/glycerine/go-unsnap-stream v0.0.0-20190901134440-81cf024a9e0a | |||
| ## explicit | |||
| github.com/glycerine/go-unsnap-stream | |||
| # 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/data | |||
| 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/types | |||
| # 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/helper/chroot | |||
| github.com/go-git/go-billy/v5/helper/polyfill | |||
| github.com/go-git/go-billy/v5/osfs | |||
| github.com/go-git/go-billy/v5/util | |||
| # 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/config | |||
| 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/noder | |||
| # github.com/go-ini/ini v1.56.0 | |||
| ## explicit | |||
| 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 | |||
| 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 | |||
| # github.com/go-openapi/jsonreference v0.19.3 | |||
| ## explicit | |||
| github.com/go-openapi/jsonreference | |||
| # github.com/go-openapi/loads v0.19.3 | |||
| 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 | |||
| # github.com/go-redis/redis v6.15.2+incompatible | |||
| ## explicit | |||
| github.com/go-redis/redis | |||
| github.com/go-redis/redis/internal | |||
| 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/util | |||
| # github.com/go-resty/resty/v2 v2.3.0 | |||
| ## explicit | |||
| github.com/go-resty/resty/v2 | |||
| # github.com/go-sql-driver/mysql v1.4.1 | |||
| ## explicit | |||
| github.com/go-sql-driver/mysql | |||
| # github.com/go-stack/stack v1.8.0 | |||
| github.com/go-stack/stack | |||
| # 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/commands | |||
| 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/scan | |||
| # github.com/gobwas/glob v0.2.3 | |||
| ## explicit | |||
| github.com/gobwas/glob | |||
| github.com/gobwas/glob/compiler | |||
| 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/strings | |||
| # github.com/gogs/chardet v0.0.0-20191104214054-4b6791f73a28 | |||
| ## explicit | |||
| github.com/gogs/chardet | |||
| # github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14 | |||
| ## explicit | |||
| github.com/gogs/cron | |||
| # github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe | |||
| github.com/golang-sql/civil | |||
| # github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 | |||
| github.com/golang/groupcache/lru | |||
| # github.com/golang/protobuf v1.4.1 | |||
| ## explicit | |||
| github.com/golang/protobuf/proto | |||
| github.com/golang/protobuf/protoc-gen-go/descriptor | |||
| github.com/golang/protobuf/ptypes | |||
| @@ -442,7 +391,6 @@ github.com/golang/snappy | |||
| github.com/gomodule/redigo/internal | |||
| github.com/gomodule/redigo/redis | |||
| # github.com/google/go-github/v24 v24.0.1 | |||
| ## explicit | |||
| github.com/google/go-github/v24/github | |||
| # github.com/google/go-querystring v1.0.0 | |||
| 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 | |||
| # github.com/gorilla/context v1.1.1 | |||
| ## explicit | |||
| github.com/gorilla/context | |||
| # github.com/gorilla/css v1.0.0 | |||
| 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 | |||
| # github.com/hashicorp/go-retryablehttp v0.6.6 | |||
| ## explicit | |||
| github.com/hashicorp/go-retryablehttp | |||
| # github.com/hashicorp/hcl v1.0.0 | |||
| 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/token | |||
| # github.com/huandu/xstrings v1.3.0 | |||
| ## explicit | |||
| github.com/huandu/xstrings | |||
| # github.com/issue9/assert v1.3.2 | |||
| ## explicit | |||
| # github.com/issue9/identicon v1.0.1 | |||
| ## explicit | |||
| github.com/issue9/identicon | |||
| # github.com/jaytaylor/html2text v0.0.0-20160923191438-8fb95d837f7d | |||
| ## explicit | |||
| github.com/jaytaylor/html2text | |||
| # github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 | |||
| github.com/jbenet/go-context/io | |||
| @@ -496,21 +437,15 @@ github.com/jbenet/go-context/io | |||
| github.com/jessevdk/go-flags | |||
| # github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af | |||
| 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 | |||
| # github.com/kballard/go-shellquote v0.0.0-20170619183022-cd60e84ee657 | |||
| ## explicit | |||
| github.com/kballard/go-shellquote | |||
| # github.com/kelseyhightower/envconfig v1.3.0 | |||
| github.com/kelseyhightower/envconfig | |||
| # github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd | |||
| github.com/kevinburke/ssh_config | |||
| # github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4 | |||
| ## explicit | |||
| github.com/keybase/go-crypto/brainpool | |||
| github.com/keybase/go-crypto/cast5 | |||
| 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/rsa | |||
| # github.com/klauspost/compress v1.10.2 | |||
| ## explicit | |||
| github.com/klauspost/compress/flate | |||
| github.com/klauspost/compress/gzip | |||
| # 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 | |||
| # github.com/lafriks/xormstore v1.3.2 | |||
| ## explicit | |||
| github.com/lafriks/xormstore | |||
| github.com/lafriks/xormstore/util | |||
| # github.com/lib/pq v1.2.0 | |||
| ## explicit | |||
| github.com/lib/pq | |||
| github.com/lib/pq/oid | |||
| github.com/lib/pq/scram | |||
| # github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 | |||
| ## explicit | |||
| github.com/lunny/dingtalk_webhook | |||
| # github.com/lunny/log v0.0.0-20160921050905-7887c61bf0de | |||
| 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 | |||
| # github.com/mailru/easyjson v0.7.0 | |||
| ## explicit | |||
| github.com/mailru/easyjson | |||
| github.com/mailru/easyjson/buffer | |||
| github.com/mailru/easyjson/jlexer | |||
| github.com/mailru/easyjson/jwriter | |||
| # github.com/markbates/goth v1.61.2 | |||
| ## explicit | |||
| github.com/markbates/goth | |||
| github.com/markbates/goth/gothic | |||
| 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 | |||
| # github.com/mattn/go-isatty v0.0.11 | |||
| ## explicit | |||
| 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 | |||
| # github.com/mattn/go-sqlite3 v1.11.0 | |||
| ## explicit | |||
| github.com/mattn/go-sqlite3 | |||
| # github.com/matttproud/golang_protobuf_extensions v1.0.1 | |||
| github.com/matttproud/golang_protobuf_extensions/pbutil | |||
| # github.com/mcuadros/go-version v0.0.0-20190308113854-92cdf37c5b75 | |||
| ## explicit | |||
| github.com/mcuadros/go-version | |||
| # github.com/mgechev/dots v0.0.0-20190921121421-c36f7dcfbb81 | |||
| ## explicit | |||
| github.com/mgechev/dots | |||
| # github.com/mgechev/revive v1.0.2 | |||
| ## explicit | |||
| github.com/mgechev/revive/formatter | |||
| github.com/mgechev/revive/lint | |||
| github.com/mgechev/revive/rule | |||
| # github.com/microcosm-cc/bluemonday v1.0.3-0.20191119130333-0a75d7616912 | |||
| ## explicit | |||
| github.com/microcosm-cc/bluemonday | |||
| # github.com/minio/md5-simd v1.1.0 | |||
| github.com/minio/md5-simd | |||
| # github.com/minio/minio-go v6.0.14+incompatible | |||
| ## explicit | |||
| github.com/minio/minio-go | |||
| github.com/minio/minio-go/pkg/credentials | |||
| 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/set | |||
| # github.com/minio/minio-go/v6 v6.0.57 | |||
| ## explicit | |||
| github.com/minio/minio-go/v6 | |||
| github.com/minio/minio-go/v6/pkg/credentials | |||
| 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 | |||
| # github.com/mitchellh/go-homedir v1.1.0 | |||
| ## explicit | |||
| github.com/mitchellh/go-homedir | |||
| # github.com/mitchellh/mapstructure v1.1.2 | |||
| github.com/mitchellh/mapstructure | |||
| @@ -641,21 +559,16 @@ github.com/mrjones/oauth | |||
| # github.com/mschoch/smat v0.2.0 | |||
| github.com/mschoch/smat | |||
| # github.com/msteinert/pam v0.0.0-20151204160544-02ccfbfaf0cc | |||
| ## explicit | |||
| github.com/msteinert/pam | |||
| # github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 | |||
| ## explicit | |||
| github.com/nfnt/resize | |||
| # github.com/niklasfasching/go-org v0.1.9 | |||
| ## explicit | |||
| github.com/niklasfasching/go-org/org | |||
| # github.com/olekukonko/tablewriter v0.0.4 | |||
| github.com/olekukonko/tablewriter | |||
| # github.com/oliamb/cutter v0.2.2 | |||
| ## explicit | |||
| github.com/oliamb/cutter | |||
| # github.com/olivere/elastic/v7 v7.0.9 | |||
| ## explicit | |||
| github.com/olivere/elastic/v7 | |||
| github.com/olivere/elastic/v7/config | |||
| 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 | |||
| # github.com/pkg/errors v0.9.1 | |||
| ## explicit | |||
| github.com/pkg/errors | |||
| # github.com/pmezard/go-difflib v1.0.0 | |||
| github.com/pmezard/go-difflib/difflib | |||
| # github.com/pquerna/otp v1.2.0 | |||
| ## explicit | |||
| github.com/pquerna/otp | |||
| github.com/pquerna/otp/hotp | |||
| github.com/pquerna/otp/totp | |||
| # github.com/prometheus/client_golang v1.1.0 | |||
| ## explicit | |||
| github.com/prometheus/client_golang/prometheus | |||
| github.com/prometheus/client_golang/prometheus/internal | |||
| github.com/prometheus/client_golang/prometheus/promhttp | |||
| # github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 | |||
| ## explicit | |||
| github.com/prometheus/client_model/go | |||
| # github.com/prometheus/common v0.6.0 | |||
| github.com/prometheus/common/expfmt | |||
| github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg | |||
| github.com/prometheus/common/model | |||
| # github.com/prometheus/procfs v0.0.4 | |||
| ## explicit | |||
| github.com/prometheus/procfs | |||
| github.com/prometheus/procfs/internal/fs | |||
| github.com/prometheus/procfs/internal/util | |||
| # github.com/quasoft/websspi v1.0.0 | |||
| ## explicit | |||
| github.com/quasoft/websspi | |||
| 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 | |||
| # github.com/satori/go.uuid v1.2.0 | |||
| ## explicit | |||
| github.com/satori/go.uuid | |||
| # github.com/sergi/go-diff v1.1.0 | |||
| ## explicit | |||
| github.com/sergi/go-diff/diffmatchpatch | |||
| # github.com/shurcooL/httpfs v0.0.0-20190527155220-6a4d4a70508b | |||
| ## explicit | |||
| github.com/shurcooL/httpfs/vfsutil | |||
| # github.com/shurcooL/sanitized_anchor_name v1.0.0 | |||
| github.com/shurcooL/sanitized_anchor_name | |||
| # github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd | |||
| ## explicit | |||
| github.com/shurcooL/vfsgen | |||
| # github.com/siddontang/go-snappy v0.0.0-20140704025258-d8f7bb82a96d | |||
| 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 | |||
| # github.com/stretchr/testify v1.4.0 | |||
| ## explicit | |||
| github.com/stretchr/testify/assert | |||
| github.com/stretchr/testify/require | |||
| # 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/table | |||
| github.com/syndtr/goleveldb/leveldb/util | |||
| # github.com/tecbot/gorocksdb v0.0.0-20181010114359-8752a9433481 | |||
| ## explicit | |||
| # github.com/tinylib/msgp v1.1.2 | |||
| ## explicit | |||
| github.com/tinylib/msgp/msgp | |||
| # github.com/toqueteos/trie v1.0.0 | |||
| github.com/toqueteos/trie | |||
| # github.com/toqueteos/webbrowser v1.2.0 | |||
| github.com/toqueteos/webbrowser | |||
| # github.com/tstranex/u2f v1.0.0 | |||
| ## explicit | |||
| github.com/tstranex/u2f | |||
| # github.com/unknwon/cae v1.0.0 | |||
| ## explicit | |||
| github.com/unknwon/cae | |||
| github.com/unknwon/cae/zip | |||
| # github.com/unknwon/com v1.0.1 | |||
| ## explicit | |||
| github.com/unknwon/com | |||
| # github.com/unknwon/i18n v0.0.0-20190805065654-5c6446a380b6 | |||
| ## explicit | |||
| github.com/unknwon/i18n | |||
| # github.com/unknwon/paginater v0.0.0-20151104151617-7748a72e0141 | |||
| ## explicit | |||
| github.com/unknwon/paginater | |||
| # github.com/urfave/cli v1.22.1 | |||
| ## explicit | |||
| github.com/urfave/cli | |||
| # github.com/willf/bitset v1.1.10 | |||
| github.com/willf/bitset | |||
| # github.com/xanzy/go-gitlab v0.31.0 | |||
| ## explicit | |||
| github.com/xanzy/go-gitlab | |||
| # github.com/xanzy/ssh-agent v0.2.1 | |||
| github.com/xanzy/ssh-agent | |||
| @@ -790,10 +680,8 @@ github.com/xdg/scram | |||
| # github.com/xdg/stringprep v1.0.0 | |||
| github.com/xdg/stringprep | |||
| # github.com/yohcop/openid-go v1.0.0 | |||
| ## explicit | |||
| github.com/yohcop/openid-go | |||
| # github.com/yuin/goldmark v1.1.27 | |||
| ## explicit | |||
| github.com/yuin/goldmark | |||
| github.com/yuin/goldmark/ast | |||
| github.com/yuin/goldmark/extension | |||
| @@ -804,7 +692,6 @@ github.com/yuin/goldmark/renderer/html | |||
| github.com/yuin/goldmark/text | |||
| github.com/yuin/goldmark/util | |||
| # github.com/yuin/goldmark-meta v0.0.0-20191126180153-f0638e958b60 | |||
| ## explicit | |||
| github.com/yuin/goldmark-meta | |||
| # go.etcd.io/bbolt v1.3.4 | |||
| go.etcd.io/bbolt | |||
| @@ -856,7 +743,6 @@ go.opencensus.io/trace/internal | |||
| go.opencensus.io/trace/propagation | |||
| go.opencensus.io/trace/tracestate | |||
| # golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79 | |||
| ## explicit | |||
| golang.org/x/crypto/acme | |||
| golang.org/x/crypto/acme/autocert | |||
| 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/knownhosts | |||
| # golang.org/x/mod v0.3.0 | |||
| ## explicit | |||
| golang.org/x/mod/module | |||
| golang.org/x/mod/semver | |||
| # golang.org/x/net v0.0.0-20200513185701-a91f0712d120 | |||
| ## explicit | |||
| golang.org/x/net/context | |||
| golang.org/x/net/context/ctxhttp | |||
| golang.org/x/net/html | |||
| @@ -904,7 +788,6 @@ golang.org/x/net/proxy | |||
| golang.org/x/net/publicsuffix | |||
| golang.org/x/net/trace | |||
| # golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d | |||
| ## explicit | |||
| golang.org/x/oauth2 | |||
| golang.org/x/oauth2/google | |||
| golang.org/x/oauth2/internal | |||
| @@ -914,7 +797,6 @@ golang.org/x/oauth2/jwt | |||
| golang.org/x/sync/errgroup | |||
| golang.org/x/sync/semaphore | |||
| # golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f | |||
| ## explicit | |||
| golang.org/x/sys/cpu | |||
| golang.org/x/sys/internal/unsafeheader | |||
| 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/debug | |||
| # golang.org/x/text v0.3.2 | |||
| ## explicit | |||
| golang.org/x/text/encoding | |||
| golang.org/x/text/encoding/charmap | |||
| 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/width | |||
| # golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 | |||
| ## explicit | |||
| golang.org/x/time/rate | |||
| # golang.org/x/tools v0.0.0-20200515220128-d3bf790afa53 | |||
| ## explicit | |||
| golang.org/x/tools/cover | |||
| golang.org/x/tools/go/analysis | |||
| 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/internal/propagation | |||
| # google.golang.org/appengine v1.6.5 | |||
| ## explicit | |||
| google.golang.org/appengine | |||
| google.golang.org/appengine/cloudsql | |||
| 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/timestamppb | |||
| # gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc | |||
| ## explicit | |||
| gopkg.in/alexcesaro/quotedprintable.v3 | |||
| # gopkg.in/asn1-ber.v1 v1.0.0-20150924051756-4e86f4367175 | |||
| ## explicit | |||
| gopkg.in/asn1-ber.v1 | |||
| # gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df | |||
| ## explicit | |||
| gopkg.in/gomail.v2 | |||
| # gopkg.in/ini.v1 v1.52.0 | |||
| ## explicit | |||
| gopkg.in/ini.v1 | |||
| # gopkg.in/ldap.v3 v3.0.2 | |||
| ## explicit | |||
| gopkg.in/ldap.v3 | |||
| # gopkg.in/macaron.v1 v1.3.9 | |||
| gopkg.in/macaron.v1 | |||
| # gopkg.in/testfixtures.v2 v2.5.0 | |||
| ## explicit | |||
| gopkg.in/testfixtures.v2 | |||
| # gopkg.in/toqueteos/substring.v1 v1.0.2 | |||
| gopkg.in/toqueteos/substring.v1 | |||
| # gopkg.in/warnings.v0 v0.1.2 | |||
| gopkg.in/warnings.v0 | |||
| # gopkg.in/yaml.v2 v2.2.8 | |||
| ## explicit | |||
| gopkg.in/yaml.v2 | |||
| # mvdan.cc/xurls/v2 v2.1.0 | |||
| ## explicit | |||
| mvdan.cc/xurls/v2 | |||
| # strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251 | |||
| ## explicit | |||
| strk.kbt.io/projects/go/libravatar | |||
| # xorm.io/builder v0.3.7 | |||
| ## explicit | |||
| xorm.io/builder | |||
| # xorm.io/xorm v1.0.1 | |||
| ## explicit | |||
| xorm.io/xorm | |||
| xorm.io/xorm/caches | |||
| xorm.io/xorm/contexts | |||