| @@ -17,4 +17,3 @@ output* | |||
| gogs.sublime-project | |||
| gogs.sublime-workspace | |||
| /release | |||
| vendor | |||
| @@ -7,18 +7,14 @@ go: | |||
| before_install: | |||
| - sudo apt-get update -qq | |||
| - sudo apt-get install -y libpam-dev | |||
| - go get github.com/msteinert/pam | |||
| install: | |||
| - go get -t -v ./... | |||
| script: | | |||
| go build -v -tags "pam" | |||
| for pkg in $(go list ./... | grep -v /vendor/) | |||
| do | |||
| go test -v -race -cover -coverprofile $GOPATH/src/$pkg/coverage.out $pkg || exit 1 | |||
| done | |||
| script: | |||
| - go build -v -tags 'cert sqlite pam miniwinsvc' | |||
| - | | |||
| for pkg in $(go list ./... | grep -v /vendor/) | |||
| do | |||
| go test -v -race -cover -coverprofile $GOPATH/src/$pkg/coverage.out $pkg || exit 1 | |||
| done | |||
| after_success: | |||
| - bash <(curl -s https://codecov.io/bash) | |||
| @@ -648,6 +648,8 @@ settings.external_wiki_url_desc = Visitors will be redirected to URL when they c | |||
| settings.issues_desc = Enable issue tracker | |||
| settings.use_internal_issue_tracker = Use builtin lightweight issue tracker | |||
| settings.use_external_issue_tracker = Use external issue tracker | |||
| settings.external_tracker_url = External Issue Tracker URL | |||
| settings.external_tracker_url_desc = Visitors will be redirected to URL when they click on the tab. | |||
| settings.tracker_url_format = External Issue Tracker URL Format | |||
| settings.tracker_issue_style = External Issue Tracker Naming Style: | |||
| settings.tracker_issue_style.numeric = Numeric | |||
| @@ -1,152 +0,0 @@ | |||
| hash: 1d5fcf2a90f7621ecbc0b1abed548e11d13bda3fea49b4326c829a523268e5cf | |||
| updated: 2016-06-12T17:35:14.27036884+08:00 | |||
| imports: | |||
| - name: github.com/bradfitz/gomemcache | |||
| version: fb1f79c6b65acda83063cbc69f6bba1522558bfc | |||
| subpackages: | |||
| - memcache | |||
| - name: github.com/urfave/cli | |||
| version: 1efa31f08b9333f1bd4882d61f9d668a70cd902e | |||
| - name: github.com/go-macaron/binding | |||
| version: 9440f336b443056c90d7d448a0a55ad8c7599880 | |||
| - name: github.com/go-macaron/cache | |||
| version: 56173531277692bc2925924d51fda1cd0a6b8178 | |||
| subpackages: | |||
| - memcache | |||
| - redis | |||
| - name: github.com/go-macaron/captcha | |||
| version: 8aa5919789ab301e865595eb4b1114d6b9847deb | |||
| - name: github.com/go-macaron/csrf | |||
| version: 6a9a7df172cc1fcd81e4585f44b09200b6087cc0 | |||
| - name: github.com/go-macaron/gzip | |||
| version: cad1c6580a07c56f5f6bc52d66002a05985c5854 | |||
| - name: github.com/go-macaron/i18n | |||
| version: ef57533c3b0fc2d8581deda14937e52f11a203ab | |||
| - name: github.com/go-macaron/inject | |||
| version: c5ab7bf3a307593cd44cb272d1a5beea473dd072 | |||
| - name: github.com/go-macaron/session | |||
| version: 66031fcb37a0fff002a1f028eb0b3a815c78306b | |||
| subpackages: | |||
| - redis | |||
| - name: github.com/go-macaron/toolbox | |||
| version: 82b511550b0aefc36b3a28062ad3a52e812bee38 | |||
| - name: github.com/go-sql-driver/mysql | |||
| version: 0b58b37b664c21f3010e836f1b931e1d0b0b0685 | |||
| - name: github.com/go-xorm/core | |||
| version: 5bf745d7d163f4380e6c2bba8c4afa60534dd087 | |||
| - name: github.com/go-xorm/xorm | |||
| version: c6c705684057842d9854e8299dd51abb06ae29f5 | |||
| - name: github.com/gogits/chardet | |||
| version: 2404f777256163ea3eadb273dada5dcb037993c0 | |||
| - name: github.com/gogits/cron | |||
| version: 7f3990acf1833faa5ebd0e86f0a4c72a4b5eba3c | |||
| - name: github.com/gogits/git-module | |||
| version: 5e0c1330d7853d1affbc193885d517db0f8d1ca5 | |||
| - name: github.com/gogits/go-gogs-client | |||
| version: c52f7ee0cc58d3cd6e379025552873a8df6de322 | |||
| - name: github.com/issue9/identicon | |||
| version: d36b54562f4cf70c83653e13dc95c220c79ef521 | |||
| - name: github.com/jaytaylor/html2text | |||
| version: 52d9b785554a1918cb09909b89a1509a98b853fd | |||
| - name: github.com/kardianos/minwinsvc | |||
| version: cad6b2b879b0970e4245a20ebf1a81a756e2bb70 | |||
| - name: github.com/klauspost/compress | |||
| version: 14eb9c4951195779ecfbec34431a976de7335b0a | |||
| subpackages: | |||
| - gzip | |||
| - flate | |||
| - name: github.com/klauspost/cpuid | |||
| version: 09cded8978dc9e80714c4d85b0322337b0a1e5e0 | |||
| - name: github.com/klauspost/crc32 | |||
| version: 19b0b332c9e4516a6370a0456e6182c3b5036720 | |||
| - name: github.com/lib/pq | |||
| version: 80f8150043c80fb52dee6bc863a709cdac7ec8f8 | |||
| subpackages: | |||
| - oid | |||
| - name: github.com/mattn/go-sqlite3 | |||
| version: e118d4451349065b8e7ce0f0af32e033995363f8 | |||
| - name: github.com/mcuadros/go-version | |||
| version: d52711f8d6bea8dc01efafdb68ad95a4e2606630 | |||
| - name: github.com/microcosm-cc/bluemonday | |||
| version: 9dc199233bf72cc1aad9b61f73daf2f0075b9ee4 | |||
| - name: github.com/msteinert/pam | |||
| version: 02ccfbfaf0cc627aa3aec8ef7ed5cfeec5b43f63 | |||
| - name: github.com/nfnt/resize | |||
| version: 891127d8d1b52734debe1b3c3d7e747502b6c366 | |||
| - name: github.com/russross/blackfriday | |||
| version: 93622da34e54fb6529bfb7c57e710f37a8d9cbd8 | |||
| - name: github.com/satori/go.uuid | |||
| version: 0aa62d5ddceb50dbcb909d790b5345affd3669b6 | |||
| - name: github.com/sergi/go-diff | |||
| version: ec7fdbb58eb3e300c8595ad5ac74a5aa50019cc7 | |||
| subpackages: | |||
| - diffmatchpatch | |||
| - name: strk.kbt.io/projects/go/libravatar | |||
| version: 5eed7bff870ae19ef51c5773dbc8f3e9fcbd0982 | |||
| - name: github.com/shurcooL/sanitized_anchor_name | |||
| version: 10ef21a441db47d8b13ebcc5fd2310f636973c77 | |||
| - name: github.com/Unknwon/cae | |||
| version: 7f5e046bc8a6c3cde743c233b96ee4fd84ee6ecd | |||
| subpackages: | |||
| - zip | |||
| - name: github.com/Unknwon/com | |||
| version: 28b053d5a2923b87ce8c5a08f3af779894a72758 | |||
| - name: github.com/Unknwon/i18n | |||
| version: 39d6f2727e0698b1021ceb6a77c1801aa92e7d5d | |||
| - name: github.com/Unknwon/paginater | |||
| version: 7748a72e01415173a27d79866b984328e7b0c12b | |||
| - name: golang.org/x/crypto | |||
| version: bc89c496413265e715159bdc8478ee9a92fdc265 | |||
| subpackages: | |||
| - ssh | |||
| - curve25519 | |||
| - ed25519 | |||
| - ed25519/internal/edwards25519 | |||
| - name: golang.org/x/net | |||
| version: 57bfaa875b96fb91b4766077f34470528d4b03e9 | |||
| subpackages: | |||
| - html | |||
| - html/charset | |||
| - html/atom | |||
| - name: golang.org/x/sys | |||
| version: a646d33e2ee3172a661fc09bca23bb4889a41bc8 | |||
| subpackages: | |||
| - windows/svc | |||
| - windows | |||
| - name: golang.org/x/text | |||
| version: 2910a502d2bf9e43193af9d68ca516529614eed3 | |||
| subpackages: | |||
| - transform | |||
| - language | |||
| - encoding | |||
| - encoding/charmap | |||
| - encoding/htmlindex | |||
| - internal/tag | |||
| - encoding/internal/identifier | |||
| - encoding/internal | |||
| - encoding/japanese | |||
| - encoding/korean | |||
| - encoding/simplifiedchinese | |||
| - encoding/traditionalchinese | |||
| - encoding/unicode | |||
| - internal/utf8internal | |||
| - runes | |||
| - name: gopkg.in/alexcesaro/quotedprintable.v3 | |||
| version: 2caba252f4dc53eaf6b553000885530023f54623 | |||
| - name: gopkg.in/asn1-ber.v1 | |||
| version: 4e86f4367175e39f69d9358a5f17b4dda270378d | |||
| - name: gopkg.in/bufio.v1 | |||
| version: 567b2bfa514e796916c4747494d6ff5132a1dfce | |||
| - name: gopkg.in/editorconfig/editorconfig-core-go.v1 | |||
| version: a872f05c2e34b37b567401384d202aff11ba06d4 | |||
| - name: gopkg.in/gomail.v2 | |||
| version: 81ebce5c23dfd25c6c67194b37d3dd3f338c98b1 | |||
| - name: gopkg.in/ini.v1 | |||
| version: cf53f9204df4fbdd7ec4164b57fa6184ba168292 | |||
| - name: gopkg.in/ldap.v2 | |||
| version: d0a5ced67b4dc310b9158d63a2c6f9c5ec13f105 | |||
| - name: gopkg.in/macaron.v1 | |||
| version: 7564489a79f3f96b7ac8034652b35eeebb468eb4 | |||
| - name: gopkg.in/redis.v2 | |||
| version: e6179049628164864e6e84e973cfb56335748dea | |||
| devImports: [] | |||
| @@ -1,59 +0,0 @@ | |||
| package: github.com/go-gitea/gitea | |||
| import: | |||
| - package: github.com/Unknwon/cae | |||
| subpackages: | |||
| - zip | |||
| - package: github.com/Unknwon/com | |||
| - package: github.com/Unknwon/i18n | |||
| - package: github.com/Unknwon/paginater | |||
| - package: github.com/urfave/cli | |||
| - package: github.com/go-macaron/binding | |||
| - package: github.com/go-macaron/cache | |||
| subpackages: | |||
| - memcache | |||
| - redis | |||
| - package: github.com/go-macaron/captcha | |||
| - package: github.com/go-macaron/csrf | |||
| - package: github.com/go-macaron/gzip | |||
| - package: github.com/go-macaron/i18n | |||
| - package: github.com/go-macaron/session | |||
| subpackages: | |||
| - redis | |||
| - package: github.com/go-macaron/toolbox | |||
| - package: github.com/go-sql-driver/mysql | |||
| - package: github.com/go-xorm/core | |||
| - package: github.com/go-xorm/xorm | |||
| - package: github.com/gogits/chardet | |||
| - package: github.com/gogits/cron | |||
| - package: github.com/gogits/git-module | |||
| - package: github.com/gogits/go-gogs-client | |||
| - package: github.com/issue9/identicon | |||
| - package: github.com/kardianos/minwinsvc | |||
| - package: github.com/lib/pq | |||
| - package: github.com/mattn/go-sqlite3 | |||
| - package: github.com/mcuadros/go-version | |||
| - package: github.com/microcosm-cc/bluemonday | |||
| - package: github.com/msteinert/pam | |||
| - package: github.com/nfnt/resize | |||
| - package: github.com/russross/blackfriday | |||
| - package: github.com/satori/go.uuid | |||
| - package: github.com/sergi/go-diff | |||
| subpackages: | |||
| - diffmatchpatch | |||
| - package: strk.kbt.io/projects/go/libravatar | |||
| - package: golang.org/x/crypto | |||
| subpackages: | |||
| - ssh | |||
| - package: golang.org/x/net | |||
| subpackages: | |||
| - html | |||
| - html/charset | |||
| - package: golang.org/x/text | |||
| subpackages: | |||
| - transform | |||
| - language | |||
| - package: gopkg.in/editorconfig/editorconfig-core-go.v1 | |||
| - package: gopkg.in/gomail.v2 | |||
| - package: gopkg.in/ini.v1 | |||
| - package: gopkg.in/ldap.v2 | |||
| - package: gopkg.in/macaron.v1 | |||
| @@ -186,6 +186,7 @@ type Repository struct { | |||
| ExternalWikiURL string | |||
| EnableIssues bool `xorm:"NOT NULL DEFAULT true"` | |||
| EnableExternalTracker bool | |||
| ExternalTrackerURL string | |||
| ExternalTrackerFormat string | |||
| ExternalTrackerStyle string | |||
| ExternalMetas map[string]string `xorm:"-"` | |||
| @@ -1,11 +1,11 @@ | |||
| package models_test | |||
| import ( | |||
| . "github.com/go-gitea/gitea/models" | |||
| . "github.com/smartystreets/goconvey/convey" | |||
| "testing" | |||
| . "github.com/go-gitea/gitea/models" | |||
| "github.com/go-gitea/gitea/modules/markdown" | |||
| . "github.com/smartystreets/goconvey/convey" | |||
| ) | |||
| func TestRepo(t *testing.T) { | |||
| @@ -96,6 +96,7 @@ type RepoSettingForm struct { | |||
| ExternalWikiURL string | |||
| EnableIssues bool | |||
| EnableExternalTracker bool | |||
| ExternalTrackerURL string | |||
| TrackerURLFormat string | |||
| TrackerIssueStyle string | |||
| EnablePulls bool | |||
| @@ -1,13 +1,13 @@ | |||
| package markdown_test | |||
| import ( | |||
| . "github.com/go-gitea/gitea/modules/markdown" | |||
| . "github.com/smartystreets/goconvey/convey" | |||
| "bytes" | |||
| "testing" | |||
| "bytes" | |||
| . "github.com/go-gitea/gitea/modules/markdown" | |||
| "github.com/go-gitea/gitea/modules/setting" | |||
| "github.com/russross/blackfriday" | |||
| . "github.com/smartystreets/goconvey/convey" | |||
| ) | |||
| func TestMarkdown(t *testing.T) { | |||
| @@ -22,8 +22,8 @@ import ( | |||
| _ "github.com/go-macaron/cache/redis" | |||
| "github.com/go-macaron/session" | |||
| _ "github.com/go-macaron/session/redis" | |||
| "strk.kbt.io/projects/go/libravatar" | |||
| "gopkg.in/ini.v1" | |||
| "strk.kbt.io/projects/go/libravatar" | |||
| "github.com/go-gitea/gitea/modules/bindata" | |||
| "github.com/go-gitea/gitea/modules/log" | |||
| @@ -52,10 +52,15 @@ var ( | |||
| ) | |||
| func MustEnableIssues(ctx *context.Context) { | |||
| if !ctx.Repo.Repository.EnableIssues || ctx.Repo.Repository.EnableExternalTracker { | |||
| if !ctx.Repo.Repository.EnableIssues { | |||
| ctx.Handle(404, "MustEnableIssues", nil) | |||
| return | |||
| } | |||
| if ctx.Repo.Repository.EnableExternalTracker { | |||
| ctx.Redirect(ctx.Repo.Repository.ExternalTrackerURL) | |||
| return | |||
| } | |||
| } | |||
| func MustAllowPulls(ctx *context.Context) { | |||
| @@ -502,7 +507,7 @@ func ViewIssue(ctx *context.Context) { | |||
| } | |||
| return | |||
| } | |||
| ctx.Data["Title"] = issue.Title | |||
| ctx.Data["Title"] = fmt.Sprintf("#%d - %s", issue.Index, issue.Title) | |||
| // Make sure type and URL matches. | |||
| if ctx.Params(":type") == "issues" && issue.IsPull { | |||
| @@ -6,6 +6,7 @@ package repo | |||
| import ( | |||
| "container/list" | |||
| "fmt" | |||
| "path" | |||
| "strings" | |||
| @@ -148,7 +149,7 @@ func checkPullInfo(ctx *context.Context) *models.Issue { | |||
| } | |||
| return nil | |||
| } | |||
| ctx.Data["Title"] = issue.Title | |||
| ctx.Data["Title"] = fmt.Sprintf("#%d - %s", issue.Index, issue.Title) | |||
| ctx.Data["Issue"] = issue | |||
| if !issue.IsPull { | |||
| @@ -146,6 +146,7 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) { | |||
| repo.ExternalWikiURL = form.ExternalWikiURL | |||
| repo.EnableIssues = form.EnableIssues | |||
| repo.EnableExternalTracker = form.EnableExternalTracker | |||
| repo.ExternalTrackerURL = form.ExternalTrackerURL | |||
| repo.ExternalTrackerFormat = form.TrackerURLFormat | |||
| repo.ExternalTrackerStyle = form.TrackerIssueStyle | |||
| repo.EnablePulls = form.EnablePulls | |||
| @@ -30,7 +30,11 @@ | |||
| <tr> | |||
| <td class="author"> | |||
| {{if .User}} | |||
| <img class="ui avatar image" src="{{.User.RelAvatarLink}}" alt=""/> <a href="{{AppSubUrl}}/{{.User.Name}}">{{.Author.Name}}</a> | |||
| {{if .User.FullName}} | |||
| <img class="ui avatar image" src="{{.User.RelAvatarLink}}" alt=""/> <a href="{{AppSubUrl}}/{{.User.Name}}">{{.User.FullName}}</a> | |||
| {{else}} | |||
| <img class="ui avatar image" src="{{.User.RelAvatarLink}}" alt=""/> <a href="{{AppSubUrl}}/{{.User.Name}}">{{.Author.Name}}</a> | |||
| {{end}} | |||
| {{else}} | |||
| <img class="ui avatar image" src="{{AvatarLink .Author.Email}}" alt=""/> {{.Author.Name}} | |||
| {{end}} | |||
| @@ -14,7 +14,11 @@ | |||
| <div class="ui attached info segment"> | |||
| {{if .Author}} | |||
| <img class="ui avatar image" src="{{.Author.RelAvatarLink}}" /> | |||
| <a href="{{.Author.HomeLink}}"><strong>{{.Commit.Author.Name}}</strong></a> {{if .IsSigned}}<{{.Commit.Author.Email}}>{{end}} | |||
| {{if .Author.FullName}} | |||
| <a href="{{.Author.HomeLink}}"><strong>{{.Author.FullName}}</strong></a> {{if .IsSigned}}<{{.Commit.Author.Email}}>{{end}} | |||
| {{else}} | |||
| <a href="{{.Author.HomeLink}}"><strong>{{.Commit.Author.Name}}</strong></a> {{if .IsSigned}}<{{.Commit.Author.Email}}>{{end}} | |||
| {{end}} | |||
| {{else}} | |||
| <img class="ui avatar image" src="{{AvatarLink .Commit.Author.Email}}" /> | |||
| <strong>{{.Commit.Author.Name}}</strong> | |||
| @@ -52,9 +52,9 @@ | |||
| <a class="{{if .PageIsViewCode}}active{{end}} item" href="{{.RepoLink}}"> | |||
| <i class="octicon octicon-code"></i> {{.i18n.Tr "repo.code"}} | |||
| </a> | |||
| {{if and .Repository.EnableIssues (not .Repository.EnableExternalTracker)}} | |||
| {{if .Repository.EnableIssues}} | |||
| <a class="{{if .PageIsIssueList}}active{{end}} item" href="{{.RepoLink}}/issues"> | |||
| <i class="octicon octicon-issue-opened"></i> {{.i18n.Tr "repo.issues"}} <span class="ui {{if not .Repository.NumOpenIssues}}gray{{else}}blue{{end}} small label">{{.Repository.NumOpenIssues}}</span> | |||
| <i class="octicon octicon-issue-opened"></i> {{.i18n.Tr "repo.issues"}} {{if not .Repository.EnableExternalTracker}}<span class="ui {{if not .Repository.NumOpenIssues}}gray{{else}}blue{{end}} small label">{{.Repository.NumOpenIssues}}{{end}}</span> | |||
| </a> | |||
| {{end}} | |||
| {{if .Repository.AllowsPulls}} | |||
| @@ -161,6 +161,11 @@ | |||
| </div> | |||
| </div> | |||
| <div class="field {{if not .Repository.EnableExternalTracker}}disabled{{end}}" id="external_issue_box"> | |||
| <div class="field"> | |||
| <label for="external_tracker_url">{{.i18n.Tr "repo.settings.external_tracker_url"}}</label> | |||
| <input id="external_tracker_url" name="external_tracker_url" type="url" value="{{.Repository.ExternalTrackerURL}}"> | |||
| <p class="help">{{.i18n.Tr "repo.settings.external_tracker_url_desc"}}</p> | |||
| </div> | |||
| <div class="field"> | |||
| <label for="tracker_url_format">{{.i18n.Tr "repo.settings.tracker_url_format"}}</label> | |||
| <input id="tracker_url_format" name="tracker_url_format" type="url" value="{{.Repository.ExternalTrackerFormat}}" placeholder="e.g. https://github.com/{user}/{repo}/issues/{index}"> | |||
| @@ -4,7 +4,11 @@ | |||
| <th class="four wide"> | |||
| {{if .LatestCommitUser}} | |||
| <img class="ui avatar image img-12" src="{{.LatestCommitUser.RelAvatarLink}}" /> | |||
| <a href="{{AppSubUrl}}/{{.LatestCommitUser.Name}}"><strong>{{.LatestCommit.Author.Name}}</strong></a> | |||
| {{if .LatestCommitUser.FullName}} | |||
| <a href="{{AppSubUrl}}/{{.LatestCommitUser.Name}}"><strong>{{.LatestCommitUser.FullName}}</strong></a> | |||
| {{else}} | |||
| <a href="{{AppSubUrl}}/{{.LatestCommitUser.Name}}"><strong>{{.LatestCommit.Author.Name}}</strong></a> | |||
| {{end}} | |||
| {{else}} | |||
| <img class="ui avatar image img-12" src="{{AvatarLink .LatestCommit.Author.Email}}" /> | |||
| <strong>{{.LatestCommit.Author.Name}}</strong> | |||
| @@ -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,37 @@ | |||
| Compression and Archive Extensions | |||
| ================================== | |||
| [](http://gowalker.org/github.com/Unknwon/cae) | |||
| [中文文档](README_ZH.md) | |||
| Package cae implements PHP-like Compression and Archive Extensions. | |||
| But this package has some modifications depends on Go-style. | |||
| Reference: [PHP:Compression and Archive Extensions](http://www.php.net/manual/en/refs.compression.php). | |||
| Code Convention: based on [Go Code Convention](https://github.com/Unknwon/go-code-convention). | |||
| ### Implementations | |||
| Package `zip`([Go Walker](http://gowalker.org/github.com/Unknwon/cae/zip)) and `tz`([Go Walker](http://gowalker.org/github.com/Unknwon/cae/tz)) both enable you to transparently read or write ZIP/TAR.GZ compressed archives and the files inside them. | |||
| - Features: | |||
| - Add file or directory from everywhere to archive, no one-to-one limitation. | |||
| - Extract part of entries, not all at once. | |||
| - Stream data directly into `io.Writer` without any file system storage. | |||
| ### Test cases and Coverage | |||
| All subpackages use [GoConvey](http://goconvey.co/) to write test cases, and coverage is more than 80 percent. | |||
| ### Use cases | |||
| - [Gogs](https://github.com/gogits/gogs): self hosted Git service in the Go Programming Language. | |||
| - [GoBlog](https://github.com/fuxiaohei/GoBlog): personal blogging application. | |||
| - [GoBuild](https://github.com/shxsun/gobuild/): online Go cross-platform compilation and download service. | |||
| ## License | |||
| This project is under Apache v2 License. See the [LICENSE](LICENSE) file for the full license text. | |||
| @@ -0,0 +1,29 @@ | |||
| 压缩与打包扩展 | |||
| ============= | |||
| [](http://gowalker.org/github.com/Unknwon/cae) | |||
| 包 cae 实现了 PHP 风格的压缩与打包扩展。 | |||
| 但本包依据 Go 语言的风格进行了一些修改。 | |||
| 引用:[PHP:Compression and Archive Extensions](http://www.php.net/manual/en/refs.compression.php) | |||
| 编码规范:基于 [Go 编码规范](https://github.com/Unknwon/go-code-convention) | |||
| ### 实现 | |||
| 包 `zip`([Go Walker](http://gowalker.org/github.com/Unknwon/cae/zip)) 和 `tz`([Go Walker](http://gowalker.org/github.com/Unknwon/cae/tz)) 都允许你轻易的读取或写入 ZIP/TAR.GZ 压缩档案和其内部文件。 | |||
| - 特性: | |||
| - 将任意位置的文件或目录加入档案,没有一对一的操作限制。 | |||
| - 只解压部分文件,而非一次性解压全部。 | |||
| - 将数据以流的形式直接写入 `io.Writer` 而不需经过文件系统的存储。 | |||
| ### 测试用例与覆盖率 | |||
| 所有子包均采用 [GoConvey](http://goconvey.co/) 来书写测试用例,覆盖率均超过 80%。 | |||
| ## 授权许可 | |||
| 本项目采用 Apache v2 开源授权许可证,完整的授权说明已放置在 [LICENSE](LICENSE) 文件中。 | |||
| @@ -0,0 +1,108 @@ | |||
| // Copyright 2013 Unknown | |||
| // | |||
| // 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 cae implements PHP-like Compression and Archive Extensions. | |||
| package cae | |||
| import ( | |||
| "io" | |||
| "os" | |||
| "strings" | |||
| ) | |||
| // A Streamer describes an streamable archive object. | |||
| type Streamer interface { | |||
| StreamFile(string, os.FileInfo, []byte) error | |||
| StreamReader(string, os.FileInfo, io.Reader) error | |||
| Close() error | |||
| } | |||
| // A HookFunc represents a middleware for packing and extracting archive. | |||
| type HookFunc func(string, os.FileInfo) error | |||
| // HasPrefix returns true if name has any string in given slice as prefix. | |||
| func HasPrefix(name string, prefixes []string) bool { | |||
| for _, prefix := range prefixes { | |||
| if strings.HasPrefix(name, prefix) { | |||
| return true | |||
| } | |||
| } | |||
| return false | |||
| } | |||
| // IsEntry returns true if name equals to any string in given slice. | |||
| func IsEntry(name string, entries []string) bool { | |||
| for _, e := range entries { | |||
| if e == name { | |||
| return true | |||
| } | |||
| } | |||
| return false | |||
| } | |||
| // IsFilter returns true if given name matches any of global filter rule. | |||
| func IsFilter(name string) bool { | |||
| if strings.Contains(name, ".DS_Store") { | |||
| return true | |||
| } | |||
| return false | |||
| } | |||
| // IsExist returns true if given path is a file or directory. | |||
| func IsExist(path string) bool { | |||
| _, err := os.Stat(path) | |||
| return err == nil || os.IsExist(err) | |||
| } | |||
| // Copy copies file from source to target path. | |||
| func Copy(dest, src string) error { | |||
| // Gather file information to set back later. | |||
| si, err := os.Lstat(src) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| // Handle symbolic link. | |||
| if si.Mode()&os.ModeSymlink != 0 { | |||
| target, err := os.Readlink(src) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| // NOTE: os.Chmod and os.Chtimes don't recoganize symbolic link, | |||
| // which will lead "no such file or directory" error. | |||
| return os.Symlink(target, dest) | |||
| } | |||
| sr, err := os.Open(src) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| defer sr.Close() | |||
| dw, err := os.Create(dest) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| defer dw.Close() | |||
| if _, err = io.Copy(dw, sr); err != nil { | |||
| return err | |||
| } | |||
| // Set back file information. | |||
| if err = os.Chtimes(dest, si.ModTime(), si.ModTime()); err != nil { | |||
| return err | |||
| } | |||
| return os.Chmod(dest, si.Mode()) | |||
| } | |||
| @@ -0,0 +1,67 @@ | |||
| // Copyright 2013 Unknown | |||
| // | |||
| // 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 zip | |||
| import ( | |||
| "archive/zip" | |||
| "os" | |||
| "strings" | |||
| ) | |||
| // OpenFile is the generalized open call; most users will use Open | |||
| // instead. It opens the named zip file with specified flag | |||
| // (O_RDONLY etc.) if applicable. If successful, | |||
| // methods on the returned ZipArchive can be used for I/O. | |||
| // If there is an error, it will be of type *PathError. | |||
| func (z *ZipArchive) Open(name string, flag int, perm os.FileMode) error { | |||
| // Create a new archive if it's specified and not exist. | |||
| if flag&os.O_CREATE != 0 { | |||
| f, err := os.Create(name) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| zw := zip.NewWriter(f) | |||
| if err = zw.Close(); err != nil { | |||
| return err | |||
| } | |||
| } | |||
| rc, err := zip.OpenReader(name) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| z.ReadCloser = rc | |||
| z.FileName = name | |||
| z.Comment = rc.Comment | |||
| z.NumFiles = len(rc.File) | |||
| z.Flag = flag | |||
| z.Permission = perm | |||
| z.isHasChanged = false | |||
| z.files = make([]*File, z.NumFiles) | |||
| for i, f := range rc.File { | |||
| z.files[i] = &File{} | |||
| z.files[i].FileHeader, err = zip.FileInfoHeader(f.FileInfo()) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| z.files[i].Name = strings.Replace(f.Name, "\\", "/", -1) | |||
| if f.FileInfo().IsDir() && !strings.HasSuffix(z.files[i].Name, "/") { | |||
| z.files[i].Name += "/" | |||
| } | |||
| } | |||
| return nil | |||
| } | |||
| @@ -0,0 +1,77 @@ | |||
| // Copyright 2014 Unknown | |||
| // | |||
| // 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 zip | |||
| import ( | |||
| "archive/zip" | |||
| "io" | |||
| "os" | |||
| "path/filepath" | |||
| ) | |||
| // A StreamArchive represents a streamable archive. | |||
| type StreamArchive struct { | |||
| *zip.Writer | |||
| } | |||
| // NewStreamArachive returns a new streamable archive with given io.Writer. | |||
| // It's caller's responsibility to close io.Writer and streamer after operation. | |||
| func NewStreamArachive(w io.Writer) *StreamArchive { | |||
| return &StreamArchive{zip.NewWriter(w)} | |||
| } | |||
| // StreamFile streams a file or directory entry into StreamArchive. | |||
| func (s *StreamArchive) StreamFile(relPath string, fi os.FileInfo, data []byte) error { | |||
| if fi.IsDir() { | |||
| fh, err := zip.FileInfoHeader(fi) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| fh.Name = relPath + "/" | |||
| if _, err = s.Writer.CreateHeader(fh); err != nil { | |||
| return err | |||
| } | |||
| } else { | |||
| fh, err := zip.FileInfoHeader(fi) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| fh.Name = filepath.Join(relPath, fi.Name()) | |||
| fh.Method = zip.Deflate | |||
| fw, err := s.Writer.CreateHeader(fh) | |||
| if err != nil { | |||
| return err | |||
| } else if _, err = fw.Write(data); err != nil { | |||
| return err | |||
| } | |||
| } | |||
| return nil | |||
| } | |||
| // StreamReader streams data from io.Reader to StreamArchive. | |||
| func (s *StreamArchive) StreamReader(relPath string, fi os.FileInfo, r io.Reader) (err error) { | |||
| fh, err := zip.FileInfoHeader(fi) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| fh.Name = filepath.Join(relPath, fi.Name()) | |||
| fw, err := s.Writer.CreateHeader(fh) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| _, err = io.Copy(fw, r) | |||
| return err | |||
| } | |||
| @@ -0,0 +1,364 @@ | |||
| // Copyright 2013 Unknown | |||
| // | |||
| // 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 zip | |||
| import ( | |||
| "archive/zip" | |||
| "fmt" | |||
| "io" | |||
| "os" | |||
| "path" | |||
| "path/filepath" | |||
| "strings" | |||
| "github.com/Unknwon/cae" | |||
| ) | |||
| // Switcher of printing trace information when pack and extract. | |||
| var Verbose = true | |||
| // extractFile extracts zip.File to file system. | |||
| func extractFile(f *zip.File, destPath string) error { | |||
| filePath := path.Join(destPath, f.Name) | |||
| os.MkdirAll(path.Dir(filePath), os.ModePerm) | |||
| rc, err := f.Open() | |||
| if err != nil { | |||
| return err | |||
| } | |||
| defer rc.Close() | |||
| fw, err := os.Create(filePath) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| defer fw.Close() | |||
| if _, err = io.Copy(fw, rc); err != nil { | |||
| return err | |||
| } | |||
| // Skip symbolic links. | |||
| if f.FileInfo().Mode()&os.ModeSymlink != 0 { | |||
| return nil | |||
| } | |||
| // Set back file information. | |||
| if err = os.Chtimes(filePath, f.ModTime(), f.ModTime()); err != nil { | |||
| return err | |||
| } | |||
| return os.Chmod(filePath, f.FileInfo().Mode()) | |||
| } | |||
| var defaultExtractFunc = func(fullName string, fi os.FileInfo) error { | |||
| if !Verbose { | |||
| return nil | |||
| } | |||
| fmt.Println("Extracting file..." + fullName) | |||
| return nil | |||
| } | |||
| // ExtractToFunc extracts the whole archive or the given files to the | |||
| // specified destination. | |||
| // It accepts a function as a middleware for custom operations. | |||
| func (z *ZipArchive) ExtractToFunc(destPath string, fn cae.HookFunc, entries ...string) (err error) { | |||
| destPath = strings.Replace(destPath, "\\", "/", -1) | |||
| isHasEntry := len(entries) > 0 | |||
| if Verbose { | |||
| fmt.Println("Unzipping " + z.FileName + "...") | |||
| } | |||
| os.MkdirAll(destPath, os.ModePerm) | |||
| for _, f := range z.File { | |||
| f.Name = strings.Replace(f.Name, "\\", "/", -1) | |||
| // Directory. | |||
| if strings.HasSuffix(f.Name, "/") { | |||
| if isHasEntry { | |||
| if cae.IsEntry(f.Name, entries) { | |||
| if err = fn(f.Name, f.FileInfo()); err != nil { | |||
| continue | |||
| } | |||
| os.MkdirAll(path.Join(destPath, f.Name), os.ModePerm) | |||
| } | |||
| continue | |||
| } | |||
| if err = fn(f.Name, f.FileInfo()); err != nil { | |||
| continue | |||
| } | |||
| os.MkdirAll(path.Join(destPath, f.Name), os.ModePerm) | |||
| continue | |||
| } | |||
| // File. | |||
| if isHasEntry { | |||
| if cae.IsEntry(f.Name, entries) { | |||
| if err = fn(f.Name, f.FileInfo()); err != nil { | |||
| continue | |||
| } | |||
| err = extractFile(f, destPath) | |||
| } | |||
| } else { | |||
| if err = fn(f.Name, f.FileInfo()); err != nil { | |||
| continue | |||
| } | |||
| err = extractFile(f, destPath) | |||
| } | |||
| if err != nil { | |||
| return err | |||
| } | |||
| } | |||
| return nil | |||
| } | |||
| // ExtractToFunc extracts the whole archive or the given files to the | |||
| // specified destination. | |||
| // It accepts a function as a middleware for custom operations. | |||
| func ExtractToFunc(srcPath, destPath string, fn cae.HookFunc, entries ...string) (err error) { | |||
| z, err := Open(srcPath) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| defer z.Close() | |||
| return z.ExtractToFunc(destPath, fn, entries...) | |||
| } | |||
| // ExtractTo extracts the whole archive or the given files to the | |||
| // specified destination. | |||
| // Call Flush() to apply changes before this. | |||
| func (z *ZipArchive) ExtractTo(destPath string, entries ...string) (err error) { | |||
| return z.ExtractToFunc(destPath, defaultExtractFunc, entries...) | |||
| } | |||
| // ExtractTo extracts given archive or the given files to the | |||
| // specified destination. | |||
| func ExtractTo(srcPath, destPath string, entries ...string) (err error) { | |||
| return ExtractToFunc(srcPath, destPath, defaultExtractFunc, entries...) | |||
| } | |||
| // extractFile extracts file from ZipArchive to file system. | |||
| func (z *ZipArchive) extractFile(f *File) error { | |||
| if !z.isHasWriter { | |||
| for _, zf := range z.ReadCloser.File { | |||
| if f.Name == zf.Name { | |||
| return extractFile(zf, path.Dir(f.tmpPath)) | |||
| } | |||
| } | |||
| } | |||
| return cae.Copy(f.tmpPath, f.absPath) | |||
| } | |||
| // Flush saves changes to original zip file if any. | |||
| func (z *ZipArchive) Flush() error { | |||
| if !z.isHasChanged || (z.ReadCloser == nil && !z.isHasWriter) { | |||
| return nil | |||
| } | |||
| // Extract to tmp path and pack back. | |||
| tmpPath := path.Join(os.TempDir(), "cae", path.Base(z.FileName)) | |||
| os.RemoveAll(tmpPath) | |||
| defer os.RemoveAll(tmpPath) | |||
| for _, f := range z.files { | |||
| if strings.HasSuffix(f.Name, "/") { | |||
| os.MkdirAll(path.Join(tmpPath, f.Name), os.ModePerm) | |||
| continue | |||
| } | |||
| // Relative path inside zip temporary changed. | |||
| f.tmpPath = path.Join(tmpPath, f.Name) | |||
| if err := z.extractFile(f); err != nil { | |||
| return err | |||
| } | |||
| } | |||
| if z.isHasWriter { | |||
| return packToWriter(tmpPath, z.writer, defaultPackFunc, true) | |||
| } | |||
| if err := PackTo(tmpPath, z.FileName); err != nil { | |||
| return err | |||
| } | |||
| return z.Open(z.FileName, os.O_RDWR|os.O_TRUNC, z.Permission) | |||
| } | |||
| // packFile packs a file or directory to zip.Writer. | |||
| func packFile(srcFile string, recPath string, zw *zip.Writer, fi os.FileInfo) error { | |||
| if fi.IsDir() { | |||
| fh, err := zip.FileInfoHeader(fi) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| fh.Name = recPath + "/" | |||
| if _, err = zw.CreateHeader(fh); err != nil { | |||
| return err | |||
| } | |||
| } else { | |||
| fh, err := zip.FileInfoHeader(fi) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| fh.Name = recPath | |||
| fh.Method = zip.Deflate | |||
| fw, err := zw.CreateHeader(fh) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| if fi.Mode()&os.ModeSymlink != 0 { | |||
| target, err := os.Readlink(srcFile) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| if _, err = fw.Write([]byte(target)); err != nil { | |||
| return err | |||
| } | |||
| } else { | |||
| f, err := os.Open(srcFile) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| defer f.Close() | |||
| if _, err = io.Copy(fw, f); err != nil { | |||
| return err | |||
| } | |||
| } | |||
| } | |||
| return nil | |||
| } | |||
| // packDir packs a directory and its subdirectories and files | |||
| // recursively to zip.Writer. | |||
| func packDir(srcPath string, recPath string, zw *zip.Writer, fn cae.HookFunc) error { | |||
| dir, err := os.Open(srcPath) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| defer dir.Close() | |||
| fis, err := dir.Readdir(0) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| for _, fi := range fis { | |||
| if cae.IsFilter(fi.Name()) { | |||
| continue | |||
| } | |||
| curPath := srcPath + "/" + fi.Name() | |||
| tmpRecPath := filepath.Join(recPath, fi.Name()) | |||
| if err = fn(curPath, fi); err != nil { | |||
| continue | |||
| } | |||
| if fi.IsDir() { | |||
| if err = packFile(srcPath, tmpRecPath, zw, fi); err != nil { | |||
| return err | |||
| } | |||
| err = packDir(curPath, tmpRecPath, zw, fn) | |||
| } else { | |||
| err = packFile(curPath, tmpRecPath, zw, fi) | |||
| } | |||
| if err != nil { | |||
| return err | |||
| } | |||
| } | |||
| return nil | |||
| } | |||
| // packToWriter packs given path object to io.Writer. | |||
| func packToWriter(srcPath string, w io.Writer, fn func(fullName string, fi os.FileInfo) error, includeDir bool) error { | |||
| zw := zip.NewWriter(w) | |||
| defer zw.Close() | |||
| f, err := os.Open(srcPath) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| defer f.Close() | |||
| fi, err := f.Stat() | |||
| if err != nil { | |||
| return err | |||
| } | |||
| basePath := path.Base(srcPath) | |||
| if fi.IsDir() { | |||
| if includeDir { | |||
| if err = packFile(srcPath, basePath, zw, fi); err != nil { | |||
| return err | |||
| } | |||
| } else { | |||
| basePath = "" | |||
| } | |||
| return packDir(srcPath, basePath, zw, fn) | |||
| } | |||
| return packFile(srcPath, basePath, zw, fi) | |||
| } | |||
| // packTo packs given source path object to target path. | |||
| func packTo(srcPath, destPath string, fn cae.HookFunc, includeDir bool) error { | |||
| fw, err := os.Create(destPath) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| defer fw.Close() | |||
| return packToWriter(srcPath, fw, fn, includeDir) | |||
| } | |||
| // PackToFunc packs the complete archive to the specified destination. | |||
| // It accepts a function as a middleware for custom operations. | |||
| func PackToFunc(srcPath, destPath string, fn func(fullName string, fi os.FileInfo) error, includeDir ...bool) error { | |||
| isIncludeDir := false | |||
| if len(includeDir) > 0 && includeDir[0] { | |||
| isIncludeDir = true | |||
| } | |||
| return packTo(srcPath, destPath, fn, isIncludeDir) | |||
| } | |||
| var defaultPackFunc = func(fullName string, fi os.FileInfo) error { | |||
| if !Verbose { | |||
| return nil | |||
| } | |||
| if fi.IsDir() { | |||
| fmt.Printf("Adding dir...%s\n", fullName) | |||
| } else { | |||
| fmt.Printf("Adding file...%s\n", fullName) | |||
| } | |||
| return nil | |||
| } | |||
| // PackTo packs the whole archive to the specified destination. | |||
| // Call Flush() will automatically call this in the end. | |||
| func PackTo(srcPath, destPath string, includeDir ...bool) error { | |||
| return PackToFunc(srcPath, destPath, defaultPackFunc, includeDir...) | |||
| } | |||
| // Close opens or creates archive and save changes. | |||
| func (z *ZipArchive) Close() (err error) { | |||
| if err = z.Flush(); err != nil { | |||
| return err | |||
| } | |||
| if z.ReadCloser != nil { | |||
| if err = z.ReadCloser.Close(); err != nil { | |||
| return err | |||
| } | |||
| z.ReadCloser = nil | |||
| } | |||
| return nil | |||
| } | |||
| @@ -0,0 +1,238 @@ | |||
| // Copyright 2013 Unknown | |||
| // | |||
| // 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 zip enables you to transparently read or write ZIP compressed archives and the files inside them. | |||
| package zip | |||
| import ( | |||
| "archive/zip" | |||
| "errors" | |||
| "io" | |||
| "os" | |||
| "path" | |||
| "strings" | |||
| "github.com/Unknwon/cae" | |||
| ) | |||
| // A File represents a file or directory entry in archive. | |||
| type File struct { | |||
| *zip.FileHeader | |||
| oldName string // NOTE: unused, for future change name feature. | |||
| oldComment string // NOTE: unused, for future change comment feature. | |||
| absPath string // Absolute path of local file system. | |||
| tmpPath string | |||
| } | |||
| // A ZipArchive represents a file archive, compressed with Zip. | |||
| type ZipArchive struct { | |||
| *zip.ReadCloser | |||
| FileName string | |||
| Comment string | |||
| NumFiles int | |||
| Flag int | |||
| Permission os.FileMode | |||
| files []*File | |||
| isHasChanged bool | |||
| // For supporting flushing to io.Writer. | |||
| writer io.Writer | |||
| isHasWriter bool | |||
| } | |||
| // OpenFile is the generalized open call; most users will use Open | |||
| // instead. It opens the named zip file with specified flag | |||
| // (O_RDONLY etc.) if applicable. If successful, | |||
| // methods on the returned ZipArchive can be used for I/O. | |||
| // If there is an error, it will be of type *PathError. | |||
| func OpenFile(name string, flag int, perm os.FileMode) (*ZipArchive, error) { | |||
| z := new(ZipArchive) | |||
| err := z.Open(name, flag, perm) | |||
| return z, err | |||
| } | |||
| // Create creates the named zip file, truncating | |||
| // it if it already exists. If successful, methods on the returned | |||
| // ZipArchive can be used for I/O; the associated file descriptor has mode | |||
| // O_RDWR. | |||
| // If there is an error, it will be of type *PathError. | |||
| func Create(name string) (*ZipArchive, error) { | |||
| os.MkdirAll(path.Dir(name), os.ModePerm) | |||
| return OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) | |||
| } | |||
| // Open opens the named zip file for reading. If successful, methods on | |||
| // the returned ZipArchive can be used for reading; the associated file | |||
| // descriptor has mode O_RDONLY. | |||
| // If there is an error, it will be of type *PathError. | |||
| func Open(name string) (*ZipArchive, error) { | |||
| return OpenFile(name, os.O_RDONLY, 0) | |||
| } | |||
| // New accepts a variable that implemented interface io.Writer | |||
| // for write-only purpose operations. | |||
| func New(w io.Writer) *ZipArchive { | |||
| return &ZipArchive{ | |||
| writer: w, | |||
| isHasWriter: true, | |||
| } | |||
| } | |||
| // List returns a string slice of files' name in ZipArchive. | |||
| // Specify prefixes will be used as filters. | |||
| func (z *ZipArchive) List(prefixes ...string) []string { | |||
| isHasPrefix := len(prefixes) > 0 | |||
| names := make([]string, 0, z.NumFiles) | |||
| for _, f := range z.files { | |||
| if isHasPrefix && !cae.HasPrefix(f.Name, prefixes) { | |||
| continue | |||
| } | |||
| names = append(names, f.Name) | |||
| } | |||
| return names | |||
| } | |||
| // AddEmptyDir adds a raw directory entry to ZipArchive, | |||
| // it returns false if same directory enry already existed. | |||
| func (z *ZipArchive) AddEmptyDir(dirPath string) bool { | |||
| dirPath = strings.Replace(dirPath, "\\", "/", -1) | |||
| if !strings.HasSuffix(dirPath, "/") { | |||
| dirPath += "/" | |||
| } | |||
| for _, f := range z.files { | |||
| if dirPath == f.Name { | |||
| return false | |||
| } | |||
| } | |||
| dirPath = strings.TrimSuffix(dirPath, "/") | |||
| if strings.Contains(dirPath, "/") { | |||
| // Auto add all upper level directories. | |||
| z.AddEmptyDir(path.Dir(dirPath)) | |||
| } | |||
| z.files = append(z.files, &File{ | |||
| FileHeader: &zip.FileHeader{ | |||
| Name: dirPath + "/", | |||
| UncompressedSize: 0, | |||
| }, | |||
| }) | |||
| z.updateStat() | |||
| return true | |||
| } | |||
| // AddDir adds a directory and subdirectories entries to ZipArchive. | |||
| func (z *ZipArchive) AddDir(dirPath, absPath string) error { | |||
| dir, err := os.Open(absPath) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| defer dir.Close() | |||
| // Make sure we have all upper level directories. | |||
| z.AddEmptyDir(dirPath) | |||
| fis, err := dir.Readdir(0) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| for _, fi := range fis { | |||
| curPath := absPath + "/" + fi.Name() | |||
| tmpRecPath := path.Join(dirPath, fi.Name()) | |||
| if fi.IsDir() { | |||
| if err = z.AddDir(tmpRecPath, curPath); err != nil { | |||
| return err | |||
| } | |||
| } else { | |||
| if err = z.AddFile(tmpRecPath, curPath); err != nil { | |||
| return err | |||
| } | |||
| } | |||
| } | |||
| return nil | |||
| } | |||
| // updateStat should be called after every change for rebuilding statistic. | |||
| func (z *ZipArchive) updateStat() { | |||
| z.NumFiles = len(z.files) | |||
| z.isHasChanged = true | |||
| } | |||
| // AddFile adds a file entry to ZipArchive. | |||
| func (z *ZipArchive) AddFile(fileName, absPath string) error { | |||
| fileName = strings.Replace(fileName, "\\", "/", -1) | |||
| absPath = strings.Replace(absPath, "\\", "/", -1) | |||
| if cae.IsFilter(absPath) { | |||
| return nil | |||
| } | |||
| f, err := os.Open(absPath) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| defer f.Close() | |||
| fi, err := f.Stat() | |||
| if err != nil { | |||
| return err | |||
| } | |||
| file := new(File) | |||
| file.FileHeader, err = zip.FileInfoHeader(fi) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| file.Name = fileName | |||
| file.absPath = absPath | |||
| z.AddEmptyDir(path.Dir(fileName)) | |||
| isExist := false | |||
| for _, f := range z.files { | |||
| if fileName == f.Name { | |||
| f = file | |||
| isExist = true | |||
| break | |||
| } | |||
| } | |||
| if !isExist { | |||
| z.files = append(z.files, file) | |||
| } | |||
| z.updateStat() | |||
| return nil | |||
| } | |||
| // DeleteIndex deletes an entry in the archive by its index. | |||
| func (z *ZipArchive) DeleteIndex(idx int) error { | |||
| if idx >= z.NumFiles { | |||
| return errors.New("index out of range of number of files") | |||
| } | |||
| z.files = append(z.files[:idx], z.files[idx+1:]...) | |||
| return nil | |||
| } | |||
| // DeleteName deletes an entry in the archive by its name. | |||
| func (z *ZipArchive) DeleteName(name string) error { | |||
| for i, f := range z.files { | |||
| if f.Name == name { | |||
| return z.DeleteIndex(i) | |||
| } | |||
| } | |||
| return errors.New("entry with given name not found") | |||
| } | |||
| @@ -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,20 @@ | |||
| Common Functions | |||
| ================ | |||
| [](https://travis-ci.org/Unknwon/com) [](http://gowalker.org/github.com/Unknwon/com) | |||
| This is an open source project for commonly used functions for the Go programming language. | |||
| This package need >= **go 1.2** | |||
| Code Convention: based on [Go Code Convention](https://github.com/Unknwon/go-code-convention). | |||
| ## Contribute | |||
| Your contribute is welcome, but you have to check following steps after you added some functions and commit them: | |||
| 1. Make sure you wrote user-friendly comments for **all functions** . | |||
| 2. Make sure you wrote test cases with any possible condition for **all functions** in file `*_test.go`. | |||
| 3. Make sure you wrote benchmarks for **all functions** in file `*_test.go`. | |||
| 4. Make sure you wrote useful examples for **all functions** in file `example_test.go`. | |||
| 5. Make sure you ran `go test` and got **PASS** . | |||
| @@ -0,0 +1,161 @@ | |||
| // +build go1.2 | |||
| // Copyright 2013 com 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 com is an open source project for commonly used functions for the Go programming language. | |||
| package com | |||
| import ( | |||
| "bytes" | |||
| "fmt" | |||
| "os/exec" | |||
| "runtime" | |||
| "strings" | |||
| ) | |||
| // ExecCmdDirBytes executes system command in given directory | |||
| // and return stdout, stderr in bytes type, along with possible error. | |||
| func ExecCmdDirBytes(dir, cmdName string, args ...string) ([]byte, []byte, error) { | |||
| bufOut := new(bytes.Buffer) | |||
| bufErr := new(bytes.Buffer) | |||
| cmd := exec.Command(cmdName, args...) | |||
| cmd.Dir = dir | |||
| cmd.Stdout = bufOut | |||
| cmd.Stderr = bufErr | |||
| err := cmd.Run() | |||
| return bufOut.Bytes(), bufErr.Bytes(), err | |||
| } | |||
| // ExecCmdBytes executes system command | |||
| // and return stdout, stderr in bytes type, along with possible error. | |||
| func ExecCmdBytes(cmdName string, args ...string) ([]byte, []byte, error) { | |||
| return ExecCmdDirBytes("", cmdName, args...) | |||
| } | |||
| // ExecCmdDir executes system command in given directory | |||
| // and return stdout, stderr in string type, along with possible error. | |||
| func ExecCmdDir(dir, cmdName string, args ...string) (string, string, error) { | |||
| bufOut, bufErr, err := ExecCmdDirBytes(dir, cmdName, args...) | |||
| return string(bufOut), string(bufErr), err | |||
| } | |||
| // ExecCmd executes system command | |||
| // and return stdout, stderr in string type, along with possible error. | |||
| func ExecCmd(cmdName string, args ...string) (string, string, error) { | |||
| return ExecCmdDir("", cmdName, args...) | |||
| } | |||
| // _________ .__ .____ | |||
| // \_ ___ \ ____ | | ___________ | | ____ ____ | |||
| // / \ \/ / _ \| | / _ \_ __ \ | | / _ \ / ___\ | |||
| // \ \___( <_> ) |_( <_> ) | \/ | |__( <_> ) /_/ > | |||
| // \______ /\____/|____/\____/|__| |_______ \____/\___ / | |||
| // \/ \/ /_____/ | |||
| // Color number constants. | |||
| const ( | |||
| Gray = uint8(iota + 90) | |||
| Red | |||
| Green | |||
| Yellow | |||
| Blue | |||
| Magenta | |||
| //NRed = uint8(31) // Normal | |||
| EndColor = "\033[0m" | |||
| ) | |||
| // getColorLevel returns colored level string by given level. | |||
| func getColorLevel(level string) string { | |||
| level = strings.ToUpper(level) | |||
| switch level { | |||
| case "TRAC": | |||
| return fmt.Sprintf("\033[%dm%s\033[0m", Blue, level) | |||
| case "ERRO": | |||
| return fmt.Sprintf("\033[%dm%s\033[0m", Red, level) | |||
| case "WARN": | |||
| return fmt.Sprintf("\033[%dm%s\033[0m", Magenta, level) | |||
| case "SUCC": | |||
| return fmt.Sprintf("\033[%dm%s\033[0m", Green, level) | |||
| default: | |||
| return level | |||
| } | |||
| } | |||
| // ColorLogS colors log and return colored content. | |||
| // Log format: <level> <content [highlight][path]> [ error ]. | |||
| // Level: TRAC -> blue; ERRO -> red; WARN -> Magenta; SUCC -> green; others -> default. | |||
| // Content: default; path: yellow; error -> red. | |||
| // Level has to be surrounded by "[" and "]". | |||
| // Highlights have to be surrounded by "# " and " #"(space), "#" will be deleted. | |||
| // Paths have to be surrounded by "( " and " )"(space). | |||
| // Errors have to be surrounded by "[ " and " ]"(space). | |||
| // Note: it hasn't support windows yet, contribute is welcome. | |||
| func ColorLogS(format string, a ...interface{}) string { | |||
| log := fmt.Sprintf(format, a...) | |||
| var clog string | |||
| if runtime.GOOS != "windows" { | |||
| // Level. | |||
| i := strings.Index(log, "]") | |||
| if log[0] == '[' && i > -1 { | |||
| clog += "[" + getColorLevel(log[1:i]) + "]" | |||
| } | |||
| log = log[i+1:] | |||
| // Error. | |||
| log = strings.Replace(log, "[ ", fmt.Sprintf("[\033[%dm", Red), -1) | |||
| log = strings.Replace(log, " ]", EndColor+"]", -1) | |||
| // Path. | |||
| log = strings.Replace(log, "( ", fmt.Sprintf("(\033[%dm", Yellow), -1) | |||
| log = strings.Replace(log, " )", EndColor+")", -1) | |||
| // Highlights. | |||
| log = strings.Replace(log, "# ", fmt.Sprintf("\033[%dm", Gray), -1) | |||
| log = strings.Replace(log, " #", EndColor, -1) | |||
| } else { | |||
| // Level. | |||
| i := strings.Index(log, "]") | |||
| if log[0] == '[' && i > -1 { | |||
| clog += "[" + log[1:i] + "]" | |||
| } | |||
| log = log[i+1:] | |||
| // Error. | |||
| log = strings.Replace(log, "[ ", "[", -1) | |||
| log = strings.Replace(log, " ]", "]", -1) | |||
| // Path. | |||
| log = strings.Replace(log, "( ", "(", -1) | |||
| log = strings.Replace(log, " )", ")", -1) | |||
| // Highlights. | |||
| log = strings.Replace(log, "# ", "", -1) | |||
| log = strings.Replace(log, " #", "", -1) | |||
| } | |||
| return clog + log | |||
| } | |||
| // ColorLog prints colored log to stdout. | |||
| // See color rules in function 'ColorLogS'. | |||
| func ColorLog(format string, a ...interface{}) { | |||
| fmt.Print(ColorLogS(format, a...)) | |||
| } | |||
| @@ -0,0 +1,157 @@ | |||
| // Copyright 2014 com 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 com | |||
| import ( | |||
| "fmt" | |||
| "strconv" | |||
| ) | |||
| // Convert string to specify type. | |||
| type StrTo string | |||
| func (f StrTo) Exist() bool { | |||
| return string(f) != string(0x1E) | |||
| } | |||
| func (f StrTo) Uint8() (uint8, error) { | |||
| v, err := strconv.ParseUint(f.String(), 10, 8) | |||
| return uint8(v), err | |||
| } | |||
| func (f StrTo) Int() (int, error) { | |||
| v, err := strconv.ParseInt(f.String(), 10, 0) | |||
| return int(v), err | |||
| } | |||
| func (f StrTo) Int64() (int64, error) { | |||
| v, err := strconv.ParseInt(f.String(), 10, 64) | |||
| return int64(v), err | |||
| } | |||
| func (f StrTo) MustUint8() uint8 { | |||
| v, _ := f.Uint8() | |||
| return v | |||
| } | |||
| func (f StrTo) MustInt() int { | |||
| v, _ := f.Int() | |||
| return v | |||
| } | |||
| func (f StrTo) MustInt64() int64 { | |||
| v, _ := f.Int64() | |||
| return v | |||
| } | |||
| func (f StrTo) String() string { | |||
| if f.Exist() { | |||
| return string(f) | |||
| } | |||
| return "" | |||
| } | |||
| // Convert any type to string. | |||
| func ToStr(value interface{}, args ...int) (s string) { | |||
| switch v := value.(type) { | |||
| case bool: | |||
| s = strconv.FormatBool(v) | |||
| case float32: | |||
| s = strconv.FormatFloat(float64(v), 'f', argInt(args).Get(0, -1), argInt(args).Get(1, 32)) | |||
| case float64: | |||
| s = strconv.FormatFloat(v, 'f', argInt(args).Get(0, -1), argInt(args).Get(1, 64)) | |||
| case int: | |||
| s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10)) | |||
| case int8: | |||
| s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10)) | |||
| case int16: | |||
| s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10)) | |||
| case int32: | |||
| s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10)) | |||
| case int64: | |||
| s = strconv.FormatInt(v, argInt(args).Get(0, 10)) | |||
| case uint: | |||
| s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10)) | |||
| case uint8: | |||
| s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10)) | |||
| case uint16: | |||
| s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10)) | |||
| case uint32: | |||
| s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10)) | |||
| case uint64: | |||
| s = strconv.FormatUint(v, argInt(args).Get(0, 10)) | |||
| case string: | |||
| s = v | |||
| case []byte: | |||
| s = string(v) | |||
| default: | |||
| s = fmt.Sprintf("%v", v) | |||
| } | |||
| return s | |||
| } | |||
| type argInt []int | |||
| func (a argInt) Get(i int, args ...int) (r int) { | |||
| if i >= 0 && i < len(a) { | |||
| r = a[i] | |||
| } else if len(args) > 0 { | |||
| r = args[0] | |||
| } | |||
| return | |||
| } | |||
| // HexStr2int converts hex format string to decimal number. | |||
| func HexStr2int(hexStr string) (int, error) { | |||
| num := 0 | |||
| length := len(hexStr) | |||
| for i := 0; i < length; i++ { | |||
| char := hexStr[length-i-1] | |||
| factor := -1 | |||
| switch { | |||
| case char >= '0' && char <= '9': | |||
| factor = int(char) - '0' | |||
| case char >= 'a' && char <= 'f': | |||
| factor = int(char) - 'a' + 10 | |||
| default: | |||
| return -1, fmt.Errorf("invalid hex: %s", string(char)) | |||
| } | |||
| num += factor * PowInt(16, i) | |||
| } | |||
| return num, nil | |||
| } | |||
| // Int2HexStr converts decimal number to hex format string. | |||
| func Int2HexStr(num int) (hex string) { | |||
| if num == 0 { | |||
| return "0" | |||
| } | |||
| for num > 0 { | |||
| r := num % 16 | |||
| c := "?" | |||
| if r >= 0 && r <= 9 { | |||
| c = string(r + '0') | |||
| } else { | |||
| c = string(r + 'a' - 10) | |||
| } | |||
| hex = c + hex | |||
| num = num / 16 | |||
| } | |||
| return hex | |||
| } | |||
| @@ -0,0 +1,173 @@ | |||
| // Copyright 2013 com 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 com | |||
| import ( | |||
| "errors" | |||
| "fmt" | |||
| "os" | |||
| "path" | |||
| "strings" | |||
| ) | |||
| // IsDir returns true if given path is a directory, | |||
| // or returns false when it's a file or does not exist. | |||
| func IsDir(dir string) bool { | |||
| f, e := os.Stat(dir) | |||
| if e != nil { | |||
| return false | |||
| } | |||
| return f.IsDir() | |||
| } | |||
| func statDir(dirPath, recPath string, includeDir, isDirOnly bool) ([]string, error) { | |||
| dir, err := os.Open(dirPath) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| defer dir.Close() | |||
| fis, err := dir.Readdir(0) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| statList := make([]string, 0) | |||
| for _, fi := range fis { | |||
| if strings.Contains(fi.Name(), ".DS_Store") { | |||
| continue | |||
| } | |||
| relPath := path.Join(recPath, fi.Name()) | |||
| curPath := path.Join(dirPath, fi.Name()) | |||
| if fi.IsDir() { | |||
| if includeDir { | |||
| statList = append(statList, relPath+"/") | |||
| } | |||
| s, err := statDir(curPath, relPath, includeDir, isDirOnly) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| statList = append(statList, s...) | |||
| } else if !isDirOnly { | |||
| statList = append(statList, relPath) | |||
| } | |||
| } | |||
| return statList, nil | |||
| } | |||
| // StatDir gathers information of given directory by depth-first. | |||
| // It returns slice of file list and includes subdirectories if enabled; | |||
| // it returns error and nil slice when error occurs in underlying functions, | |||
| // or given path is not a directory or does not exist. | |||
| // | |||
| // Slice does not include given path itself. | |||
| // If subdirectories is enabled, they will have suffix '/'. | |||
| func StatDir(rootPath string, includeDir ...bool) ([]string, error) { | |||
| if !IsDir(rootPath) { | |||
| return nil, errors.New("not a directory or does not exist: " + rootPath) | |||
| } | |||
| isIncludeDir := false | |||
| if len(includeDir) >= 1 { | |||
| isIncludeDir = includeDir[0] | |||
| } | |||
| return statDir(rootPath, "", isIncludeDir, false) | |||
| } | |||
| // GetAllSubDirs returns all subdirectories of given root path. | |||
| // Slice does not include given path itself. | |||
| func GetAllSubDirs(rootPath string) ([]string, error) { | |||
| if !IsDir(rootPath) { | |||
| return nil, errors.New("not a directory or does not exist: " + rootPath) | |||
| } | |||
| return statDir(rootPath, "", true, true) | |||
| } | |||
| // GetFileListBySuffix returns an ordered list of file paths. | |||
| // It recognize if given path is a file, and don't do recursive find. | |||
| func GetFileListBySuffix(dirPath, suffix string) ([]string, error) { | |||
| if !IsExist(dirPath) { | |||
| return nil, fmt.Errorf("given path does not exist: %s", dirPath) | |||
| } else if IsFile(dirPath) { | |||
| return []string{dirPath}, nil | |||
| } | |||
| // Given path is a directory. | |||
| dir, err := os.Open(dirPath) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| fis, err := dir.Readdir(0) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| files := make([]string, 0, len(fis)) | |||
| for _, fi := range fis { | |||
| if strings.HasSuffix(fi.Name(), suffix) { | |||
| files = append(files, path.Join(dirPath, fi.Name())) | |||
| } | |||
| } | |||
| return files, nil | |||
| } | |||
| // CopyDir copy files recursively from source to target directory. | |||
| // | |||
| // The filter accepts a function that process the path info. | |||
| // and should return true for need to filter. | |||
| // | |||
| // It returns error when error occurs in underlying functions. | |||
| func CopyDir(srcPath, destPath string, filters ...func(filePath string) bool) error { | |||
| // Check if target directory exists. | |||
| if IsExist(destPath) { | |||
| return errors.New("file or directory alreay exists: " + destPath) | |||
| } | |||
| err := os.MkdirAll(destPath, os.ModePerm) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| // Gather directory info. | |||
| infos, err := StatDir(srcPath, true) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| var filter func(filePath string) bool | |||
| if len(filters) > 0 { | |||
| filter = filters[0] | |||
| } | |||
| for _, info := range infos { | |||
| if filter != nil && filter(info) { | |||
| continue | |||
| } | |||
| curPath := path.Join(destPath, info) | |||
| if strings.HasSuffix(info, "/") { | |||
| err = os.MkdirAll(curPath, os.ModePerm) | |||
| } else { | |||
| err = Copy(path.Join(srcPath, info), curPath) | |||
| } | |||
| if err != nil { | |||
| return err | |||
| } | |||
| } | |||
| return nil | |||
| } | |||
| @@ -0,0 +1,145 @@ | |||
| // Copyright 2013 com 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 com | |||
| import ( | |||
| "fmt" | |||
| "io" | |||
| "io/ioutil" | |||
| "math" | |||
| "os" | |||
| "path" | |||
| ) | |||
| // Storage unit constants. | |||
| const ( | |||
| Byte = 1 | |||
| KByte = Byte * 1024 | |||
| MByte = KByte * 1024 | |||
| GByte = MByte * 1024 | |||
| TByte = GByte * 1024 | |||
| PByte = TByte * 1024 | |||
| EByte = PByte * 1024 | |||
| ) | |||
| func logn(n, b float64) float64 { | |||
| return math.Log(n) / math.Log(b) | |||
| } | |||
| func humanateBytes(s uint64, base float64, sizes []string) string { | |||
| if s < 10 { | |||
| return fmt.Sprintf("%dB", s) | |||
| } | |||
| e := math.Floor(logn(float64(s), base)) | |||
| suffix := sizes[int(e)] | |||
| val := float64(s) / math.Pow(base, math.Floor(e)) | |||
| f := "%.0f" | |||
| if val < 10 { | |||
| f = "%.1f" | |||
| } | |||
| return fmt.Sprintf(f+"%s", val, suffix) | |||
| } | |||
| // HumaneFileSize calculates the file size and generate user-friendly string. | |||
| func HumaneFileSize(s uint64) string { | |||
| sizes := []string{"B", "KB", "MB", "GB", "TB", "PB", "EB"} | |||
| return humanateBytes(s, 1024, sizes) | |||
| } | |||
| // FileMTime returns file modified time and possible error. | |||
| func FileMTime(file string) (int64, error) { | |||
| f, err := os.Stat(file) | |||
| if err != nil { | |||
| return 0, err | |||
| } | |||
| return f.ModTime().Unix(), nil | |||
| } | |||
| // FileSize returns file size in bytes and possible error. | |||
| func FileSize(file string) (int64, error) { | |||
| f, err := os.Stat(file) | |||
| if err != nil { | |||
| return 0, err | |||
| } | |||
| return f.Size(), nil | |||
| } | |||
| // Copy copies file from source to target path. | |||
| func Copy(src, dest string) error { | |||
| // Gather file information to set back later. | |||
| si, err := os.Lstat(src) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| // Handle symbolic link. | |||
| if si.Mode()&os.ModeSymlink != 0 { | |||
| target, err := os.Readlink(src) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| // NOTE: os.Chmod and os.Chtimes don't recoganize symbolic link, | |||
| // which will lead "no such file or directory" error. | |||
| return os.Symlink(target, dest) | |||
| } | |||
| sr, err := os.Open(src) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| defer sr.Close() | |||
| dw, err := os.Create(dest) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| defer dw.Close() | |||
| if _, err = io.Copy(dw, sr); err != nil { | |||
| return err | |||
| } | |||
| // Set back file information. | |||
| if err = os.Chtimes(dest, si.ModTime(), si.ModTime()); err != nil { | |||
| return err | |||
| } | |||
| return os.Chmod(dest, si.Mode()) | |||
| } | |||
| // WriteFile writes data to a file named by filename. | |||
| // If the file does not exist, WriteFile creates it | |||
| // and its upper level paths. | |||
| func WriteFile(filename string, data []byte) error { | |||
| os.MkdirAll(path.Dir(filename), os.ModePerm) | |||
| return ioutil.WriteFile(filename, data, 0655) | |||
| } | |||
| // IsFile returns true if given path is a file, | |||
| // or returns false when it's a directory or does not exist. | |||
| func IsFile(filePath string) bool { | |||
| f, e := os.Stat(filePath) | |||
| if e != nil { | |||
| return false | |||
| } | |||
| return !f.IsDir() | |||
| } | |||
| // IsExist checks whether a file or directory exists. | |||
| // It returns false when the file or directory does not exist. | |||
| func IsExist(path string) bool { | |||
| _, err := os.Stat(path) | |||
| return err == nil || os.IsExist(err) | |||
| } | |||
| @@ -0,0 +1,60 @@ | |||
| // Copyright 2013 com 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 com | |||
| import ( | |||
| "html" | |||
| "regexp" | |||
| "strings" | |||
| ) | |||
| // Html2JS converts []byte type of HTML content into JS format. | |||
| func Html2JS(data []byte) []byte { | |||
| s := string(data) | |||
| s = strings.Replace(s, `\`, `\\`, -1) | |||
| s = strings.Replace(s, "\n", `\n`, -1) | |||
| s = strings.Replace(s, "\r", "", -1) | |||
| s = strings.Replace(s, "\"", `\"`, -1) | |||
| s = strings.Replace(s, "<table>", "<table>", -1) | |||
| return []byte(s) | |||
| } | |||
| // encode html chars to string | |||
| func HtmlEncode(str string) string { | |||
| return html.EscapeString(str) | |||
| } | |||
| // decode string to html chars | |||
| func HtmlDecode(str string) string { | |||
| return html.UnescapeString(str) | |||
| } | |||
| // strip tags in html string | |||
| func StripTags(src string) string { | |||
| //去除style,script,html tag | |||
| re := regexp.MustCompile(`(?s)<(?:style|script)[^<>]*>.*?</(?:style|script)>|</?[a-z][a-z0-9]*[^<>]*>|<!--.*?-->`) | |||
| src = re.ReplaceAllString(src, "") | |||
| //trim all spaces(2+) into \n | |||
| re = regexp.MustCompile(`\s{2,}`) | |||
| src = re.ReplaceAllString(src, "\n") | |||
| return strings.TrimSpace(src) | |||
| } | |||
| // change \n to <br/> | |||
| func Nl2br(str string) string { | |||
| return strings.Replace(str, "\n", "<br/>", -1) | |||
| } | |||
| @@ -0,0 +1,201 @@ | |||
| // Copyright 2013 com 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 com | |||
| import ( | |||
| "bytes" | |||
| "encoding/json" | |||
| "fmt" | |||
| "io" | |||
| "io/ioutil" | |||
| "net/http" | |||
| "os" | |||
| "path" | |||
| ) | |||
| type NotFoundError struct { | |||
| Message string | |||
| } | |||
| func (e NotFoundError) Error() string { | |||
| return e.Message | |||
| } | |||
| type RemoteError struct { | |||
| Host string | |||
| Err error | |||
| } | |||
| func (e *RemoteError) Error() string { | |||
| return e.Err.Error() | |||
| } | |||
| var UserAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1541.0 Safari/537.36" | |||
| // HttpCall makes HTTP method call. | |||
| func HttpCall(client *http.Client, method, url string, header http.Header, body io.Reader) (io.ReadCloser, error) { | |||
| req, err := http.NewRequest(method, url, body) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| req.Header.Set("User-Agent", UserAgent) | |||
| for k, vs := range header { | |||
| req.Header[k] = vs | |||
| } | |||
| resp, err := client.Do(req) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| if resp.StatusCode == 200 { | |||
| return resp.Body, nil | |||
| } | |||
| resp.Body.Close() | |||
| if resp.StatusCode == 404 { // 403 can be rate limit error. || resp.StatusCode == 403 { | |||
| err = fmt.Errorf("resource not found: %s", url) | |||
| } else { | |||
| err = fmt.Errorf("%s %s -> %d", method, url, resp.StatusCode) | |||
| } | |||
| return nil, err | |||
| } | |||
| // HttpGet gets the specified resource. | |||
| // ErrNotFound is returned if the server responds with status 404. | |||
| func HttpGet(client *http.Client, url string, header http.Header) (io.ReadCloser, error) { | |||
| return HttpCall(client, "GET", url, header, nil) | |||
| } | |||
| // HttpPost posts the specified resource. | |||
| // ErrNotFound is returned if the server responds with status 404. | |||
| func HttpPost(client *http.Client, url string, header http.Header, body []byte) (io.ReadCloser, error) { | |||
| return HttpCall(client, "POST", url, header, bytes.NewBuffer(body)) | |||
| } | |||
| // HttpGetToFile gets the specified resource and writes to file. | |||
| // ErrNotFound is returned if the server responds with status 404. | |||
| func HttpGetToFile(client *http.Client, url string, header http.Header, fileName string) error { | |||
| rc, err := HttpGet(client, url, header) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| defer rc.Close() | |||
| os.MkdirAll(path.Dir(fileName), os.ModePerm) | |||
| f, err := os.Create(fileName) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| defer f.Close() | |||
| _, err = io.Copy(f, rc) | |||
| return err | |||
| } | |||
| // HttpGetBytes gets the specified resource. ErrNotFound is returned if the server | |||
| // responds with status 404. | |||
| func HttpGetBytes(client *http.Client, url string, header http.Header) ([]byte, error) { | |||
| rc, err := HttpGet(client, url, header) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| defer rc.Close() | |||
| return ioutil.ReadAll(rc) | |||
| } | |||
| // HttpGetJSON gets the specified resource and mapping to struct. | |||
| // ErrNotFound is returned if the server responds with status 404. | |||
| func HttpGetJSON(client *http.Client, url string, v interface{}) error { | |||
| rc, err := HttpGet(client, url, nil) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| defer rc.Close() | |||
| err = json.NewDecoder(rc).Decode(v) | |||
| if _, ok := err.(*json.SyntaxError); ok { | |||
| return fmt.Errorf("JSON syntax error at %s", url) | |||
| } | |||
| return nil | |||
| } | |||
| // HttpPostJSON posts the specified resource with struct values, | |||
| // and maps results to struct. | |||
| // ErrNotFound is returned if the server responds with status 404. | |||
| func HttpPostJSON(client *http.Client, url string, body, v interface{}) error { | |||
| data, err := json.Marshal(body) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| rc, err := HttpPost(client, url, http.Header{"content-type": []string{"application/json"}}, data) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| defer rc.Close() | |||
| err = json.NewDecoder(rc).Decode(v) | |||
| if _, ok := err.(*json.SyntaxError); ok { | |||
| return fmt.Errorf("JSON syntax error at %s", url) | |||
| } | |||
| return nil | |||
| } | |||
| // A RawFile describes a file that can be downloaded. | |||
| type RawFile interface { | |||
| Name() string | |||
| RawUrl() string | |||
| Data() []byte | |||
| SetData([]byte) | |||
| } | |||
| // FetchFiles fetches files specified by the rawURL field in parallel. | |||
| func FetchFiles(client *http.Client, files []RawFile, header http.Header) error { | |||
| ch := make(chan error, len(files)) | |||
| for i := range files { | |||
| go func(i int) { | |||
| p, err := HttpGetBytes(client, files[i].RawUrl(), nil) | |||
| if err != nil { | |||
| ch <- err | |||
| return | |||
| } | |||
| files[i].SetData(p) | |||
| ch <- nil | |||
| }(i) | |||
| } | |||
| for _ = range files { | |||
| if err := <-ch; err != nil { | |||
| return err | |||
| } | |||
| } | |||
| return nil | |||
| } | |||
| // FetchFiles uses command `curl` to fetch files specified by the rawURL field in parallel. | |||
| func FetchFilesCurl(files []RawFile, curlOptions ...string) error { | |||
| ch := make(chan error, len(files)) | |||
| for i := range files { | |||
| go func(i int) { | |||
| stdout, _, err := ExecCmd("curl", append(curlOptions, files[i].RawUrl())...) | |||
| if err != nil { | |||
| ch <- err | |||
| return | |||
| } | |||
| files[i].SetData([]byte(stdout)) | |||
| ch <- nil | |||
| }(i) | |||
| } | |||
| for _ = range files { | |||
| if err := <-ch; err != nil { | |||
| return err | |||
| } | |||
| } | |||
| return nil | |||
| } | |||
| @@ -0,0 +1,29 @@ | |||
| // Copyright 2014 com 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 com | |||
| // PowInt is int type of math.Pow function. | |||
| func PowInt(x int, y int) int { | |||
| if y <= 0 { | |||
| return 1 | |||
| } else { | |||
| if y % 2 == 0 { | |||
| sqrt := PowInt(x, y/2) | |||
| return sqrt * sqrt | |||
| } else { | |||
| return PowInt(x, y-1) * x | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,80 @@ | |||
| // Copyright 2013 com 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 com | |||
| import ( | |||
| "errors" | |||
| "os" | |||
| "path/filepath" | |||
| "runtime" | |||
| "strings" | |||
| ) | |||
| // GetGOPATHs returns all paths in GOPATH variable. | |||
| func GetGOPATHs() []string { | |||
| gopath := os.Getenv("GOPATH") | |||
| var paths []string | |||
| if runtime.GOOS == "windows" { | |||
| gopath = strings.Replace(gopath, "\\", "/", -1) | |||
| paths = strings.Split(gopath, ";") | |||
| } else { | |||
| paths = strings.Split(gopath, ":") | |||
| } | |||
| return paths | |||
| } | |||
| // GetSrcPath returns app. source code path. | |||
| // It only works when you have src. folder in GOPATH, | |||
| // it returns error not able to locate source folder path. | |||
| func GetSrcPath(importPath string) (appPath string, err error) { | |||
| paths := GetGOPATHs() | |||
| for _, p := range paths { | |||
| if IsExist(p + "/src/" + importPath + "/") { | |||
| appPath = p + "/src/" + importPath + "/" | |||
| break | |||
| } | |||
| } | |||
| if len(appPath) == 0 { | |||
| return "", errors.New("Unable to locate source folder path") | |||
| } | |||
| appPath = filepath.Dir(appPath) + "/" | |||
| if runtime.GOOS == "windows" { | |||
| // Replace all '\' to '/'. | |||
| appPath = strings.Replace(appPath, "\\", "/", -1) | |||
| } | |||
| return appPath, nil | |||
| } | |||
| // HomeDir returns path of '~'(in Linux) on Windows, | |||
| // it returns error when the variable does not exist. | |||
| func HomeDir() (home string, err error) { | |||
| if runtime.GOOS == "windows" { | |||
| home = os.Getenv("USERPROFILE") | |||
| if len(home) == 0 { | |||
| home = os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH") | |||
| } | |||
| } else { | |||
| home = os.Getenv("HOME") | |||
| } | |||
| if len(home) == 0 { | |||
| return "", errors.New("Cannot specify home directory because it's empty") | |||
| } | |||
| return home, nil | |||
| } | |||
| @@ -0,0 +1,56 @@ | |||
| // Copyright 2013 com 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 com | |||
| import "regexp" | |||
| const ( | |||
| regex_email_pattern = `(?i)[A-Z0-9._%+-]+@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}` | |||
| regex_strict_email_pattern = `(?i)[A-Z0-9!#$%&'*+/=?^_{|}~-]+` + | |||
| `(?:\.[A-Z0-9!#$%&'*+/=?^_{|}~-]+)*` + | |||
| `@(?:[A-Z0-9](?:[A-Z0-9-]*[A-Z0-9])?\.)+` + | |||
| `[A-Z0-9](?:[A-Z0-9-]*[A-Z0-9])?` | |||
| regex_url_pattern = `(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?` | |||
| ) | |||
| var ( | |||
| regex_email *regexp.Regexp | |||
| regex_strict_email *regexp.Regexp | |||
| regex_url *regexp.Regexp | |||
| ) | |||
| func init() { | |||
| regex_email = regexp.MustCompile(regex_email_pattern) | |||
| regex_strict_email = regexp.MustCompile(regex_strict_email_pattern) | |||
| regex_url = regexp.MustCompile(regex_url_pattern) | |||
| } | |||
| // validate string is an email address, if not return false | |||
| // basically validation can match 99% cases | |||
| func IsEmail(email string) bool { | |||
| return regex_email.MatchString(email) | |||
| } | |||
| // validate string is an email address, if not return false | |||
| // this validation omits RFC 2822 | |||
| func IsEmailRFC(email string) bool { | |||
| return regex_strict_email.MatchString(email) | |||
| } | |||
| // validate string is a url link, if not return false | |||
| // simple validation can match 99% cases | |||
| func IsUrl(url string) bool { | |||
| return regex_url.MatchString(url) | |||
| } | |||
| @@ -0,0 +1,87 @@ | |||
| // Copyright 2013 com 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 com | |||
| import ( | |||
| "strings" | |||
| ) | |||
| // AppendStr appends string to slice with no duplicates. | |||
| func AppendStr(strs []string, str string) []string { | |||
| for _, s := range strs { | |||
| if s == str { | |||
| return strs | |||
| } | |||
| } | |||
| return append(strs, str) | |||
| } | |||
| // CompareSliceStr compares two 'string' type slices. | |||
| // It returns true if elements and order are both the same. | |||
| func CompareSliceStr(s1, s2 []string) bool { | |||
| if len(s1) != len(s2) { | |||
| return false | |||
| } | |||
| for i := range s1 { | |||
| if s1[i] != s2[i] { | |||
| return false | |||
| } | |||
| } | |||
| return true | |||
| } | |||
| // CompareSliceStr compares two 'string' type slices. | |||
| // It returns true if elements are the same, and ignores the order. | |||
| func CompareSliceStrU(s1, s2 []string) bool { | |||
| if len(s1) != len(s2) { | |||
| return false | |||
| } | |||
| for i := range s1 { | |||
| for j := len(s2) - 1; j >= 0; j-- { | |||
| if s1[i] == s2[j] { | |||
| s2 = append(s2[:j], s2[j+1:]...) | |||
| break | |||
| } | |||
| } | |||
| } | |||
| if len(s2) > 0 { | |||
| return false | |||
| } | |||
| return true | |||
| } | |||
| // IsSliceContainsStr returns true if the string exists in given slice, ignore case. | |||
| func IsSliceContainsStr(sl []string, str string) bool { | |||
| str = strings.ToLower(str) | |||
| for _, s := range sl { | |||
| if strings.ToLower(s) == str { | |||
| return true | |||
| } | |||
| } | |||
| return false | |||
| } | |||
| // IsSliceContainsInt64 returns true if the int64 exists in given slice. | |||
| func IsSliceContainsInt64(sl []int64, i int64) bool { | |||
| for _, s := range sl { | |||
| if s == i { | |||
| return true | |||
| } | |||
| } | |||
| return false | |||
| } | |||
| @@ -0,0 +1,243 @@ | |||
| // Copyright 2013 com 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 com | |||
| import ( | |||
| "bytes" | |||
| "crypto/aes" | |||
| "crypto/cipher" | |||
| "crypto/rand" | |||
| "encoding/base64" | |||
| "errors" | |||
| "io" | |||
| r "math/rand" | |||
| "strconv" | |||
| "strings" | |||
| "time" | |||
| "unicode" | |||
| "unicode/utf8" | |||
| ) | |||
| // AESEncrypt encrypts text and given key with AES. | |||
| func AESEncrypt(key, text []byte) ([]byte, error) { | |||
| block, err := aes.NewCipher(key) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| b := base64.StdEncoding.EncodeToString(text) | |||
| ciphertext := make([]byte, aes.BlockSize+len(b)) | |||
| iv := ciphertext[:aes.BlockSize] | |||
| if _, err := io.ReadFull(rand.Reader, iv); err != nil { | |||
| return nil, err | |||
| } | |||
| cfb := cipher.NewCFBEncrypter(block, iv) | |||
| cfb.XORKeyStream(ciphertext[aes.BlockSize:], []byte(b)) | |||
| return ciphertext, nil | |||
| } | |||
| // AESDecrypt decrypts text and given key with AES. | |||
| func AESDecrypt(key, text []byte) ([]byte, error) { | |||
| block, err := aes.NewCipher(key) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| if len(text) < aes.BlockSize { | |||
| return nil, errors.New("ciphertext too short") | |||
| } | |||
| iv := text[:aes.BlockSize] | |||
| text = text[aes.BlockSize:] | |||
| cfb := cipher.NewCFBDecrypter(block, iv) | |||
| cfb.XORKeyStream(text, text) | |||
| data, err := base64.StdEncoding.DecodeString(string(text)) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| return data, nil | |||
| } | |||
| // IsLetter returns true if the 'l' is an English letter. | |||
| func IsLetter(l uint8) bool { | |||
| n := (l | 0x20) - 'a' | |||
| if n >= 0 && n < 26 { | |||
| return true | |||
| } | |||
| return false | |||
| } | |||
| // Expand replaces {k} in template with match[k] or subs[atoi(k)] if k is not in match. | |||
| func Expand(template string, match map[string]string, subs ...string) string { | |||
| var p []byte | |||
| var i int | |||
| for { | |||
| i = strings.Index(template, "{") | |||
| if i < 0 { | |||
| break | |||
| } | |||
| p = append(p, template[:i]...) | |||
| template = template[i+1:] | |||
| i = strings.Index(template, "}") | |||
| if s, ok := match[template[:i]]; ok { | |||
| p = append(p, s...) | |||
| } else { | |||
| j, _ := strconv.Atoi(template[:i]) | |||
| if j >= len(subs) { | |||
| p = append(p, []byte("Missing")...) | |||
| } else { | |||
| p = append(p, subs[j]...) | |||
| } | |||
| } | |||
| template = template[i+1:] | |||
| } | |||
| p = append(p, template...) | |||
| return string(p) | |||
| } | |||
| // Reverse s string, support unicode | |||
| func Reverse(s string) string { | |||
| n := len(s) | |||
| runes := make([]rune, n) | |||
| for _, rune := range s { | |||
| n-- | |||
| runes[n] = rune | |||
| } | |||
| return string(runes[n:]) | |||
| } | |||
| // RandomCreateBytes generate random []byte by specify chars. | |||
| func RandomCreateBytes(n int, alphabets ...byte) []byte { | |||
| const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" | |||
| var bytes = make([]byte, n) | |||
| var randby bool | |||
| if num, err := rand.Read(bytes); num != n || err != nil { | |||
| r.Seed(time.Now().UnixNano()) | |||
| randby = true | |||
| } | |||
| for i, b := range bytes { | |||
| if len(alphabets) == 0 { | |||
| if randby { | |||
| bytes[i] = alphanum[r.Intn(len(alphanum))] | |||
| } else { | |||
| bytes[i] = alphanum[b%byte(len(alphanum))] | |||
| } | |||
| } else { | |||
| if randby { | |||
| bytes[i] = alphabets[r.Intn(len(alphabets))] | |||
| } else { | |||
| bytes[i] = alphabets[b%byte(len(alphabets))] | |||
| } | |||
| } | |||
| } | |||
| return bytes | |||
| } | |||
| // ToSnakeCase can convert all upper case characters in a string to | |||
| // underscore format. | |||
| // | |||
| // Some samples. | |||
| // "FirstName" => "first_name" | |||
| // "HTTPServer" => "http_server" | |||
| // "NoHTTPS" => "no_https" | |||
| // "GO_PATH" => "go_path" | |||
| // "GO PATH" => "go_path" // space is converted to underscore. | |||
| // "GO-PATH" => "go_path" // hyphen is converted to underscore. | |||
| // | |||
| // From https://github.com/huandu/xstrings | |||
| func ToSnakeCase(str string) string { | |||
| if len(str) == 0 { | |||
| return "" | |||
| } | |||
| buf := &bytes.Buffer{} | |||
| var prev, r0, r1 rune | |||
| var size int | |||
| r0 = '_' | |||
| for len(str) > 0 { | |||
| prev = r0 | |||
| r0, size = utf8.DecodeRuneInString(str) | |||
| str = str[size:] | |||
| switch { | |||
| case r0 == utf8.RuneError: | |||
| buf.WriteByte(byte(str[0])) | |||
| case unicode.IsUpper(r0): | |||
| if prev != '_' { | |||
| buf.WriteRune('_') | |||
| } | |||
| buf.WriteRune(unicode.ToLower(r0)) | |||
| if len(str) == 0 { | |||
| break | |||
| } | |||
| r0, size = utf8.DecodeRuneInString(str) | |||
| str = str[size:] | |||
| if !unicode.IsUpper(r0) { | |||
| buf.WriteRune(r0) | |||
| break | |||
| } | |||
| // find next non-upper-case character and insert `_` properly. | |||
| // it's designed to convert `HTTPServer` to `http_server`. | |||
| // if there are more than 2 adjacent upper case characters in a word, | |||
| // treat them as an abbreviation plus a normal word. | |||
| for len(str) > 0 { | |||
| r1 = r0 | |||
| r0, size = utf8.DecodeRuneInString(str) | |||
| str = str[size:] | |||
| if r0 == utf8.RuneError { | |||
| buf.WriteRune(unicode.ToLower(r1)) | |||
| buf.WriteByte(byte(str[0])) | |||
| break | |||
| } | |||
| if !unicode.IsUpper(r0) { | |||
| if r0 == '_' || r0 == ' ' || r0 == '-' { | |||
| r0 = '_' | |||
| buf.WriteRune(unicode.ToLower(r1)) | |||
| } else { | |||
| buf.WriteRune('_') | |||
| buf.WriteRune(unicode.ToLower(r1)) | |||
| buf.WriteRune(r0) | |||
| } | |||
| break | |||
| } | |||
| buf.WriteRune(unicode.ToLower(r1)) | |||
| } | |||
| if len(str) == 0 || r0 == '_' { | |||
| buf.WriteRune(unicode.ToLower(r0)) | |||
| break | |||
| } | |||
| default: | |||
| if r0 == ' ' || r0 == '-' { | |||
| r0 = '_' | |||
| } | |||
| buf.WriteRune(r0) | |||
| } | |||
| } | |||
| return buf.String() | |||
| } | |||
| @@ -0,0 +1,115 @@ | |||
| // Copyright 2013 com 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 com | |||
| import ( | |||
| "fmt" | |||
| "strconv" | |||
| "strings" | |||
| "time" | |||
| ) | |||
| // Format unix time int64 to string | |||
| func Date(ti int64, format string) string { | |||
| t := time.Unix(int64(ti), 0) | |||
| return DateT(t, format) | |||
| } | |||
| // Format unix time string to string | |||
| func DateS(ts string, format string) string { | |||
| i, _ := strconv.ParseInt(ts, 10, 64) | |||
| return Date(i, format) | |||
| } | |||
| // Format time.Time struct to string | |||
| // MM - month - 01 | |||
| // M - month - 1, single bit | |||
| // DD - day - 02 | |||
| // D - day 2 | |||
| // YYYY - year - 2006 | |||
| // YY - year - 06 | |||
| // HH - 24 hours - 03 | |||
| // H - 24 hours - 3 | |||
| // hh - 12 hours - 03 | |||
| // h - 12 hours - 3 | |||
| // mm - minute - 04 | |||
| // m - minute - 4 | |||
| // ss - second - 05 | |||
| // s - second = 5 | |||
| func DateT(t time.Time, format string) string { | |||
| res := strings.Replace(format, "MM", t.Format("01"), -1) | |||
| res = strings.Replace(res, "M", t.Format("1"), -1) | |||
| res = strings.Replace(res, "DD", t.Format("02"), -1) | |||
| res = strings.Replace(res, "D", t.Format("2"), -1) | |||
| res = strings.Replace(res, "YYYY", t.Format("2006"), -1) | |||
| res = strings.Replace(res, "YY", t.Format("06"), -1) | |||
| res = strings.Replace(res, "HH", fmt.Sprintf("%02d", t.Hour()), -1) | |||
| res = strings.Replace(res, "H", fmt.Sprintf("%d", t.Hour()), -1) | |||
| res = strings.Replace(res, "hh", t.Format("03"), -1) | |||
| res = strings.Replace(res, "h", t.Format("3"), -1) | |||
| res = strings.Replace(res, "mm", t.Format("04"), -1) | |||
| res = strings.Replace(res, "m", t.Format("4"), -1) | |||
| res = strings.Replace(res, "ss", t.Format("05"), -1) | |||
| res = strings.Replace(res, "s", t.Format("5"), -1) | |||
| return res | |||
| } | |||
| // DateFormat pattern rules. | |||
| var datePatterns = []string{ | |||
| // year | |||
| "Y", "2006", // A full numeric representation of a year, 4 digits Examples: 1999 or 2003 | |||
| "y", "06", //A two digit representation of a year Examples: 99 or 03 | |||
| // month | |||
| "m", "01", // Numeric representation of a month, with leading zeros 01 through 12 | |||
| "n", "1", // Numeric representation of a month, without leading zeros 1 through 12 | |||
| "M", "Jan", // A short textual representation of a month, three letters Jan through Dec | |||
| "F", "January", // A full textual representation of a month, such as January or March January through December | |||
| // day | |||
| "d", "02", // Day of the month, 2 digits with leading zeros 01 to 31 | |||
| "j", "2", // Day of the month without leading zeros 1 to 31 | |||
| // week | |||
| "D", "Mon", // A textual representation of a day, three letters Mon through Sun | |||
| "l", "Monday", // A full textual representation of the day of the week Sunday through Saturday | |||
| // time | |||
| "g", "3", // 12-hour format of an hour without leading zeros 1 through 12 | |||
| "G", "15", // 24-hour format of an hour without leading zeros 0 through 23 | |||
| "h", "03", // 12-hour format of an hour with leading zeros 01 through 12 | |||
| "H", "15", // 24-hour format of an hour with leading zeros 00 through 23 | |||
| "a", "pm", // Lowercase Ante meridiem and Post meridiem am or pm | |||
| "A", "PM", // Uppercase Ante meridiem and Post meridiem AM or PM | |||
| "i", "04", // Minutes with leading zeros 00 to 59 | |||
| "s", "05", // Seconds, with leading zeros 00 through 59 | |||
| // time zone | |||
| "T", "MST", | |||
| "P", "-07:00", | |||
| "O", "-0700", | |||
| // RFC 2822 | |||
| "r", time.RFC1123Z, | |||
| } | |||
| // Parse Date use PHP time format. | |||
| func DateParse(dateString, format string) (time.Time, error) { | |||
| replacer := strings.NewReplacer(datePatterns...) | |||
| format = replacer.Replace(format) | |||
| return time.ParseInLocation(format, dateString, time.Local) | |||
| } | |||
| @@ -0,0 +1,41 @@ | |||
| // Copyright 2013 com 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 com | |||
| import ( | |||
| "encoding/base64" | |||
| "net/url" | |||
| ) | |||
| // url encode string, is + not %20 | |||
| func UrlEncode(str string) string { | |||
| return url.QueryEscape(str) | |||
| } | |||
| // url decode string | |||
| func UrlDecode(str string) (string, error) { | |||
| return url.QueryUnescape(str) | |||
| } | |||
| // base64 encode | |||
| func Base64Encode(str string) string { | |||
| return base64.StdEncoding.EncodeToString([]byte(str)) | |||
| } | |||
| // base64 decode | |||
| func Base64Decode(str string) (string, error) { | |||
| s, e := base64.StdEncoding.DecodeString(str) | |||
| return string(s), e | |||
| } | |||
| @@ -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,134 @@ | |||
| i18n | |||
| ==== | |||
| Package i18n is for app Internationalization and Localization. | |||
| ## Introduction | |||
| This package provides multiple-language options to improve user experience. Sites like [Go Walker](http://gowalker.org) and [gogs.io](http://gogs.io) are using this module to implement Chinese and English user interfaces. | |||
| You can use following command to install this module: | |||
| go get github.com/Unknwon/i18n | |||
| ## Usage | |||
| First of all, you have to import this package: | |||
| ```go | |||
| import "github.com/Unknwon/i18n" | |||
| ``` | |||
| The format of locale files is very like INI format configuration file, which is basically key-value pairs. But this module has some improvements. Every language corresponding to a locale file, for example, under `conf/locale` folder of [gogsweb](https://github.com/gogits/gogsweb/tree/master/conf/locale), there are two files called `locale_en-US.ini` and `locale_zh-CN.ini`. | |||
| The name and extensions of locale files can be anything, but we strongly recommend you to follow the style of gogsweb. | |||
| ## Minimal example | |||
| Here are two simplest locale file examples: | |||
| File `locale_en-US.ini`: | |||
| ```ini | |||
| hi = hello, %s | |||
| bye = goodbye | |||
| ``` | |||
| File `locale_zh-CN.ini`: | |||
| ```ini | |||
| hi = 您好,%s | |||
| bye = 再见 | |||
| ``` | |||
| ### Do Translation | |||
| There are two ways to do translation depends on which way is the best fit for your application or framework. | |||
| Directly use package function to translate: | |||
| ```go | |||
| i18n.Tr("en-US", "hi", "Unknwon") | |||
| i18n.Tr("en-US", "bye") | |||
| ``` | |||
| Or create a struct and embed it: | |||
| ```go | |||
| type MyController struct{ | |||
| // ...other fields | |||
| i18n.Locale | |||
| } | |||
| //... | |||
| func ... { | |||
| c := &MyController{ | |||
| Locale: i18n.Locale{"en-US"}, | |||
| } | |||
| _ = c.Tr("hi", "Unknwon") | |||
| _ = c.Tr("bye") | |||
| } | |||
| ``` | |||
| Code above will produce correspondingly: | |||
| - English `en-US`:`hello, Unknwon`, `goodbye` | |||
| - Chinese `zh-CN`:`您好,Unknwon`, `再见` | |||
| ## Section | |||
| For different pages, one key may map to different values. Therefore, i18n module also uses the section feature of INI format configuration to achieve section. | |||
| For example, the key name is `about`, and we want to show `About` in the home page and `About Us` in about page. Then you can do following: | |||
| Content in locale file: | |||
| ```ini | |||
| about = About | |||
| [about] | |||
| about = About Us | |||
| ``` | |||
| Get `about` in home page: | |||
| ```go | |||
| i18n.Tr("en-US", "about") | |||
| ``` | |||
| Get `about` in about page: | |||
| ```go | |||
| i18n.Tr("en-US", "about.about") | |||
| ``` | |||
| ### Ambiguity | |||
| Because dot `.` is sign of section in both [INI parser](https://github.com/go-ini/ini) and locale files, so when your key name contains `.` will cause ambiguity. At this point, you just need to add one more `.` in front of the key. | |||
| For example, the key name is `about.`, then we can use: | |||
| ```go | |||
| i18n.Tr("en-US", ".about.") | |||
| ``` | |||
| to get expect result. | |||
| ## Helper tool | |||
| Module i18n provides a command line helper tool beei18n for simplify steps of your development. You can install it as follows: | |||
| go get github.com/Unknwon/i18n/ui18n | |||
| ### Sync locale files | |||
| Command `sync` allows you use a exist local file as the template to create or sync other locale files: | |||
| ui18n sync srouce_file.ini other1.ini other2.ini | |||
| This command can operate 1 or more files in one command. | |||
| ## More information | |||
| If the key does not exist, then i18n will return the key string to caller. For instance, when key name is `hi` and it does not exist in locale file, simply return `hi` as output. | |||
| @@ -0,0 +1,225 @@ | |||
| // Copyright 2013 Unknwon | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"): you may | |||
| // not use this file except in compliance with the License. You may obtain | |||
| // a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
| // License for the specific language governing permissions and limitations | |||
| // under the License. | |||
| // Package i18n is for app Internationalization and Localization. | |||
| package i18n | |||
| import ( | |||
| "errors" | |||
| "fmt" | |||
| "reflect" | |||
| "strings" | |||
| "gopkg.in/ini.v1" | |||
| ) | |||
| var ( | |||
| ErrLangAlreadyExist = errors.New("Lang already exists") | |||
| locales = &localeStore{store: make(map[string]*locale)} | |||
| ) | |||
| type locale struct { | |||
| id int | |||
| lang string | |||
| langDesc string | |||
| message *ini.File | |||
| } | |||
| type localeStore struct { | |||
| langs []string | |||
| langDescs []string | |||
| store map[string]*locale | |||
| defaultLang string | |||
| } | |||
| // Get target language string | |||
| func (d *localeStore) Get(lang, section, format string) (string, bool) { | |||
| if locale, ok := d.store[lang]; ok { | |||
| if key, err := locale.message.Section(section).GetKey(format); err == nil { | |||
| return key.Value(), true | |||
| } | |||
| } | |||
| if len(d.defaultLang) > 0 && lang != d.defaultLang { | |||
| return d.Get(d.defaultLang, section, format) | |||
| } | |||
| return "", false | |||
| } | |||
| func (d *localeStore) Add(lc *locale) bool { | |||
| if _, ok := d.store[lc.lang]; ok { | |||
| return false | |||
| } | |||
| lc.id = len(d.langs) | |||
| d.langs = append(d.langs, lc.lang) | |||
| d.langDescs = append(d.langDescs, lc.langDesc) | |||
| d.store[lc.lang] = lc | |||
| return true | |||
| } | |||
| func (d *localeStore) Reload(langs ...string) (err error) { | |||
| if len(langs) == 0 { | |||
| for _, lc := range d.store { | |||
| if err = lc.message.Reload(); err != nil { | |||
| return err | |||
| } | |||
| } | |||
| } else { | |||
| for _, lang := range langs { | |||
| if lc, ok := d.store[lang]; ok { | |||
| if err = lc.message.Reload(); err != nil { | |||
| return err | |||
| } | |||
| } | |||
| } | |||
| } | |||
| return nil | |||
| } | |||
| // SetDefaultLang sets default language which is a indicator that | |||
| // when target language is not found, try find in default language again. | |||
| func SetDefaultLang(lang string) { | |||
| locales.defaultLang = lang | |||
| } | |||
| // ReloadLangs reloads locale files. | |||
| func ReloadLangs(langs ...string) error { | |||
| return locales.Reload(langs...) | |||
| } | |||
| // Count returns number of languages that are registered. | |||
| func Count() int { | |||
| return len(locales.langs) | |||
| } | |||
| // ListLangs returns list of all locale languages. | |||
| func ListLangs() []string { | |||
| langs := make([]string, len(locales.langs)) | |||
| copy(langs, locales.langs) | |||
| return langs | |||
| } | |||
| func ListLangDescs() []string { | |||
| langDescs := make([]string, len(locales.langDescs)) | |||
| copy(langDescs, locales.langDescs) | |||
| return langDescs | |||
| } | |||
| // IsExist returns true if given language locale exists. | |||
| func IsExist(lang string) bool { | |||
| _, ok := locales.store[lang] | |||
| return ok | |||
| } | |||
| // IndexLang returns index of language locale, | |||
| // it returns -1 if locale not exists. | |||
| func IndexLang(lang string) int { | |||
| if lc, ok := locales.store[lang]; ok { | |||
| return lc.id | |||
| } | |||
| return -1 | |||
| } | |||
| // GetLangByIndex return language by given index. | |||
| func GetLangByIndex(index int) string { | |||
| if index < 0 || index >= len(locales.langs) { | |||
| return "" | |||
| } | |||
| return locales.langs[index] | |||
| } | |||
| func GetDescriptionByIndex(index int) string { | |||
| if index < 0 || index >= len(locales.langDescs) { | |||
| return "" | |||
| } | |||
| return locales.langDescs[index] | |||
| } | |||
| func GetDescriptionByLang(lang string) string { | |||
| return GetDescriptionByIndex(IndexLang(lang)) | |||
| } | |||
| func SetMessageWithDesc(lang, langDesc string, localeFile interface{}, otherLocaleFiles ...interface{}) error { | |||
| message, err := ini.Load(localeFile, otherLocaleFiles...) | |||
| if err == nil { | |||
| message.BlockMode = false | |||
| lc := new(locale) | |||
| lc.lang = lang | |||
| lc.langDesc = langDesc | |||
| lc.message = message | |||
| if locales.Add(lc) == false { | |||
| return ErrLangAlreadyExist | |||
| } | |||
| } | |||
| return err | |||
| } | |||
| // SetMessage sets the message file for localization. | |||
| func SetMessage(lang string, localeFile interface{}, otherLocaleFiles ...interface{}) error { | |||
| return SetMessageWithDesc(lang, lang, localeFile, otherLocaleFiles...) | |||
| } | |||
| // Locale represents the information of localization. | |||
| type Locale struct { | |||
| Lang string | |||
| } | |||
| // Tr translates content to target language. | |||
| func (l Locale) Tr(format string, args ...interface{}) string { | |||
| return Tr(l.Lang, format, args...) | |||
| } | |||
| // Index returns lang index of LangStore. | |||
| func (l Locale) Index() int { | |||
| return IndexLang(l.Lang) | |||
| } | |||
| // Tr translates content to target language. | |||
| func Tr(lang, format string, args ...interface{}) string { | |||
| var section string | |||
| parts := strings.SplitN(format, ".", 2) | |||
| if len(parts) == 2 { | |||
| section = parts[0] | |||
| format = parts[1] | |||
| } | |||
| value, ok := locales.Get(lang, section, format) | |||
| if ok { | |||
| format = value | |||
| } | |||
| if len(args) > 0 { | |||
| params := make([]interface{}, 0, len(args)) | |||
| for _, arg := range args { | |||
| if arg != nil { | |||
| val := reflect.ValueOf(arg) | |||
| if val.Kind() == reflect.Slice { | |||
| for i := 0; i < val.Len(); i++ { | |||
| params = append(params, val.Index(i).Interface()) | |||
| } | |||
| } else { | |||
| params = append(params, arg) | |||
| } | |||
| } | |||
| } | |||
| return fmt.Sprintf(format, params...) | |||
| } | |||
| return format | |||
| } | |||
| @@ -0,0 +1,202 @@ | |||
| Apache License | |||
| Version 2.0, January 2004 | |||
| http://www.apache.org/licenses/ | |||
| TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | |||
| 1. Definitions. | |||
| "License" shall mean the terms and conditions for use, reproduction, | |||
| and distribution as defined by Sections 1 through 9 of this document. | |||
| "Licensor" shall mean the copyright owner or entity authorized by | |||
| the copyright owner that is granting the License. | |||
| "Legal Entity" shall mean the union of the acting entity and all | |||
| other entities that control, are controlled by, or are under common | |||
| control with that entity. For the purposes of this definition, | |||
| "control" means (i) the power, direct or indirect, to cause the | |||
| direction or management of such entity, whether by contract or | |||
| otherwise, or (ii) ownership of fifty percent (50%) or more of the | |||
| outstanding shares, or (iii) beneficial ownership of such entity. | |||
| "You" (or "Your") shall mean an individual or Legal Entity | |||
| exercising permissions granted by this License. | |||
| "Source" form shall mean the preferred form for making modifications, | |||
| including but not limited to software source code, documentation | |||
| source, and configuration files. | |||
| "Object" form shall mean any form resulting from mechanical | |||
| transformation or translation of a Source form, including but | |||
| not limited to compiled object code, generated documentation, | |||
| and conversions to other media types. | |||
| "Work" shall mean the work of authorship, whether in Source or | |||
| Object form, made available under the License, as indicated by a | |||
| copyright notice that is included in or attached to the work | |||
| (an example is provided in the Appendix below). | |||
| "Derivative Works" shall mean any work, whether in Source or Object | |||
| form, that is based on (or derived from) the Work and for which the | |||
| editorial revisions, annotations, elaborations, or other modifications | |||
| represent, as a whole, an original work of authorship. For the purposes | |||
| of this License, Derivative Works shall not include works that remain | |||
| separable from, or merely link (or bind by name) to the interfaces of, | |||
| the Work and Derivative Works thereof. | |||
| "Contribution" shall mean any work of authorship, including | |||
| the original version of the Work and any modifications or additions | |||
| to that Work or Derivative Works thereof, that is intentionally | |||
| submitted to Licensor for inclusion in the Work by the copyright owner | |||
| or by an individual or Legal Entity authorized to submit on behalf of | |||
| the copyright owner. For the purposes of this definition, "submitted" | |||
| means any form of electronic, verbal, or written communication sent | |||
| to the Licensor or its representatives, including but not limited to | |||
| communication on electronic mailing lists, source code control systems, | |||
| and issue tracking systems that are managed by, or on behalf of, the | |||
| Licensor for the purpose of discussing and improving the Work, but | |||
| excluding communication that is conspicuously marked or otherwise | |||
| designated in writing by the copyright owner as "Not a Contribution." | |||
| "Contributor" shall mean Licensor and any individual or Legal Entity | |||
| on behalf of whom a Contribution has been received by Licensor and | |||
| subsequently incorporated within the Work. | |||
| 2. Grant of Copyright License. Subject to the terms and conditions of | |||
| this License, each Contributor hereby grants to You a perpetual, | |||
| worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |||
| copyright license to reproduce, prepare Derivative Works of, | |||
| publicly display, publicly perform, sublicense, and distribute the | |||
| Work and such Derivative Works in Source or Object form. | |||
| 3. Grant of Patent License. Subject to the terms and conditions of | |||
| this License, each Contributor hereby grants to You a perpetual, | |||
| worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |||
| (except as stated in this section) patent license to make, have made, | |||
| use, offer to sell, sell, import, and otherwise transfer the Work, | |||
| where such license applies only to those patent claims licensable | |||
| by such Contributor that are necessarily infringed by their | |||
| Contribution(s) alone or by combination of their Contribution(s) | |||
| with the Work to which such Contribution(s) was submitted. If You | |||
| institute patent litigation against any entity (including a | |||
| cross-claim or counterclaim in a lawsuit) alleging that the Work | |||
| or a Contribution incorporated within the Work constitutes direct | |||
| or contributory patent infringement, then any patent licenses | |||
| granted to You under this License for that Work shall terminate | |||
| as of the date such litigation is filed. | |||
| 4. Redistribution. You may reproduce and distribute copies of the | |||
| Work or Derivative Works thereof in any medium, with or without | |||
| modifications, and in Source or Object form, provided that You | |||
| meet the following conditions: | |||
| (a) You must give any other recipients of the Work or | |||
| Derivative Works a copy of this License; and | |||
| (b) You must cause any modified files to carry prominent notices | |||
| stating that You changed the files; and | |||
| (c) You must retain, in the Source form of any Derivative Works | |||
| that You distribute, all copyright, patent, trademark, and | |||
| attribution notices from the Source form of the Work, | |||
| excluding those notices that do not pertain to any part of | |||
| the Derivative Works; and | |||
| (d) If the Work includes a "NOTICE" text file as part of its | |||
| distribution, then any Derivative Works that You distribute must | |||
| include a readable copy of the attribution notices contained | |||
| within such NOTICE file, excluding those notices that do not | |||
| pertain to any part of the Derivative Works, in at least one | |||
| of the following places: within a NOTICE text file distributed | |||
| as part of the Derivative Works; within the Source form or | |||
| documentation, if provided along with the Derivative Works; or, | |||
| within a display generated by the Derivative Works, if and | |||
| wherever such third-party notices normally appear. The contents | |||
| of the NOTICE file are for informational purposes only and | |||
| do not modify the License. You may add Your own attribution | |||
| notices within Derivative Works that You distribute, alongside | |||
| or as an addendum to the NOTICE text from the Work, provided | |||
| that such additional attribution notices cannot be construed | |||
| as modifying the License. | |||
| You may add Your own copyright statement to Your modifications and | |||
| may provide additional or different license terms and conditions | |||
| for use, reproduction, or distribution of Your modifications, or | |||
| for any such Derivative Works as a whole, provided Your use, | |||
| reproduction, and distribution of the Work otherwise complies with | |||
| the conditions stated in this License. | |||
| 5. Submission of Contributions. Unless You explicitly state otherwise, | |||
| any Contribution intentionally submitted for inclusion in the Work | |||
| by You to the Licensor shall be under the terms and conditions of | |||
| this License, without any additional terms or conditions. | |||
| Notwithstanding the above, nothing herein shall supersede or modify | |||
| the terms of any separate license agreement you may have executed | |||
| with Licensor regarding such Contributions. | |||
| 6. Trademarks. This License does not grant permission to use the trade | |||
| names, trademarks, service marks, or product names of the Licensor, | |||
| except as required for reasonable and customary use in describing the | |||
| origin of the Work and reproducing the content of the NOTICE file. | |||
| 7. Disclaimer of Warranty. Unless required by applicable law or | |||
| agreed to in writing, Licensor provides the Work (and each | |||
| Contributor provides its Contributions) on an "AS IS" BASIS, | |||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | |||
| implied, including, without limitation, any warranties or conditions | |||
| of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | |||
| PARTICULAR PURPOSE. You are solely responsible for determining the | |||
| appropriateness of using or redistributing the Work and assume any | |||
| risks associated with Your exercise of permissions under this License. | |||
| 8. Limitation of Liability. In no event and under no legal theory, | |||
| whether in tort (including negligence), contract, or otherwise, | |||
| unless required by applicable law (such as deliberate and grossly | |||
| negligent acts) or agreed to in writing, shall any Contributor be | |||
| liable to You for damages, including any direct, indirect, special, | |||
| incidental, or consequential damages of any character arising as a | |||
| result of this License or out of the use or inability to use the | |||
| Work (including but not limited to damages for loss of goodwill, | |||
| work stoppage, computer failure or malfunction, or any and all | |||
| other commercial damages or losses), even if such Contributor | |||
| has been advised of the possibility of such damages. | |||
| 9. Accepting Warranty or Additional Liability. While redistributing | |||
| the Work or Derivative Works thereof, You may choose to offer, | |||
| and charge a fee for, acceptance of support, warranty, indemnity, | |||
| or other liability obligations and/or rights consistent with this | |||
| License. However, in accepting such obligations, You may act only | |||
| on Your own behalf and on Your sole responsibility, not on behalf | |||
| of any other Contributor, and only if You agree to indemnify, | |||
| defend, and hold each Contributor harmless for any liability | |||
| incurred by, or claims asserted against, such Contributor by reason | |||
| of your accepting any such warranty or additional liability. | |||
| END OF TERMS AND CONDITIONS | |||
| APPENDIX: How to apply the Apache License to your work. | |||
| To apply the Apache License to your work, attach the following | |||
| boilerplate notice, with the fields enclosed by brackets "{}" | |||
| replaced with your own identifying information. (Don't include | |||
| the brackets!) The text should be enclosed in the appropriate | |||
| comment syntax for the file format. We also recommend that a | |||
| file or class name and description of purpose be included on the | |||
| same "printed page" as the copyright notice for easier | |||
| identification within third-party archives. | |||
| Copyright {yyyy} {name of copyright owner} | |||
| Licensed under the Apache License, Version 2.0 (the "License"); | |||
| you may not use this file except in compliance with the License. | |||
| You may obtain a copy of the License at | |||
| http://www.apache.org/licenses/LICENSE-2.0 | |||
| Unless required by applicable law or agreed to in writing, software | |||
| distributed under the License is distributed on an "AS IS" BASIS, | |||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| See the License for the specific language governing permissions and | |||
| limitations under the License. | |||
| @@ -0,0 +1,65 @@ | |||
| Paginater [](https://drone.io/github.com/Unknwon/paginater/latest) [](http://gocover.io/github.com/Unknwon/paginater) | |||
| ========= | |||
| Package paginater is a helper module for custom pagination calculation. | |||
| ## Installation | |||
| go get github.com/Unknwon/paginater | |||
| ## Getting Started | |||
| The following code shows an example of how to use paginater: | |||
| ```go | |||
| package main | |||
| import "github.com/Unknwon/paginater" | |||
| func main() { | |||
| // Arguments: | |||
| // - Total number of rows | |||
| // - Number of rows in one page | |||
| // - Current page number | |||
| // - Number of page links | |||
| p := paginater.New(45, 10, 3, 3) | |||
| // Then use p as a template object named "Page" in "demo.html" | |||
| // ... | |||
| } | |||
| ``` | |||
| `demo.html` | |||
| ```html | |||
| {{if not .Page.IsFirst}}[First](1){{end}} | |||
| {{if .Page.HasPrevious}}[Previous]({{.Page.Previous}}){{end}} | |||
| {{range .Page.Pages}} | |||
| {{if eq .Num -1}} | |||
| ... | |||
| {{else}} | |||
| {{.Num}}{{if .IsCurrent}}(current){{end}} | |||
| {{end}} | |||
| {{end}} | |||
| {{if .Page.HasNext}}[Next]({{.Page.Next}}){{end}} | |||
| {{if not .Page.IsLast}}[Last]({{.Page.TotalPages}}){{end}} | |||
| ``` | |||
| Possible output: | |||
| ``` | |||
| [First](1) [Previous](2) ... 2 3(current) 4 ... [Next](4) [Last](5) | |||
| ``` | |||
| As you may guess, if the `Page` value is `-1`, you should print `...` in the HTML as common practice. | |||
| ## Getting Help | |||
| - [API Documentation](https://gowalker.org/github.com/Unknwon/paginater) | |||
| - [File An Issue](https://github.com/Unknwon/paginater/issues/new) | |||
| ## License | |||
| This project is under Apache v2 License. See the [LICENSE](LICENSE) file for the full license text. | |||
| @@ -0,0 +1,192 @@ | |||
| // Copyright 2015 Unknwon | |||
| // | |||
| // 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 paginater is a helper module for custom pagination calculation. | |||
| package paginater | |||
| // Paginater represents a set of results of pagination calculations. | |||
| type Paginater struct { | |||
| total int | |||
| pagingNum int | |||
| current int | |||
| numPages int | |||
| } | |||
| // New initialize a new pagination calculation and returns a Paginater as result. | |||
| func New(total, pagingNum, current, numPages int) *Paginater { | |||
| if pagingNum <= 0 { | |||
| pagingNum = 1 | |||
| } | |||
| if current <= 0 { | |||
| current = 1 | |||
| } | |||
| p := &Paginater{total, pagingNum, current, numPages} | |||
| if p.current > p.TotalPages() { | |||
| p.current = p.TotalPages() | |||
| } | |||
| return p | |||
| } | |||
| // IsFirst returns true if current page is the first page. | |||
| func (p *Paginater) IsFirst() bool { | |||
| return p.current == 1 | |||
| } | |||
| // HasPrevious returns true if there is a previous page relative to current page. | |||
| func (p *Paginater) HasPrevious() bool { | |||
| return p.current > 1 | |||
| } | |||
| func (p *Paginater) Previous() int { | |||
| if !p.HasPrevious() { | |||
| return p.current | |||
| } | |||
| return p.current - 1 | |||
| } | |||
| // HasNext returns true if there is a next page relative to current page. | |||
| func (p *Paginater) HasNext() bool { | |||
| return p.total > p.current*p.pagingNum | |||
| } | |||
| func (p *Paginater) Next() int { | |||
| if !p.HasNext() { | |||
| return p.current | |||
| } | |||
| return p.current + 1 | |||
| } | |||
| // IsLast returns true if current page is the last page. | |||
| func (p *Paginater) IsLast() bool { | |||
| if p.total == 0 { | |||
| return true | |||
| } | |||
| return p.total > (p.current-1)*p.pagingNum && !p.HasNext() | |||
| } | |||
| // Total returns number of total rows. | |||
| func (p *Paginater) Total() int { | |||
| return p.total | |||
| } | |||
| // TotalPage returns number of total pages. | |||
| func (p *Paginater) TotalPages() int { | |||
| if p.total == 0 { | |||
| return 1 | |||
| } | |||
| if p.total%p.pagingNum == 0 { | |||
| return p.total / p.pagingNum | |||
| } | |||
| return p.total/p.pagingNum + 1 | |||
| } | |||
| // Current returns current page number. | |||
| func (p *Paginater) Current() int { | |||
| return p.current | |||
| } | |||
| // Page presents a page in the paginater. | |||
| type Page struct { | |||
| num int | |||
| isCurrent bool | |||
| } | |||
| func (p *Page) Num() int { | |||
| return p.num | |||
| } | |||
| func (p *Page) IsCurrent() bool { | |||
| return p.isCurrent | |||
| } | |||
| func getMiddleIdx(numPages int) int { | |||
| if numPages%2 == 0 { | |||
| return numPages / 2 | |||
| } | |||
| return numPages/2 + 1 | |||
| } | |||
| // Pages returns a list of nearby page numbers relative to current page. | |||
| // If value is -1 means "..." that more pages are not showing. | |||
| func (p *Paginater) Pages() []*Page { | |||
| if p.numPages == 0 { | |||
| return []*Page{} | |||
| } else if p.numPages == 1 && p.TotalPages() == 1 { | |||
| // Only show current page. | |||
| return []*Page{{1, true}} | |||
| } | |||
| // Total page number is less or equal. | |||
| if p.TotalPages() <= p.numPages { | |||
| pages := make([]*Page, p.TotalPages()) | |||
| for i := range pages { | |||
| pages[i] = &Page{i + 1, i+1 == p.current} | |||
| } | |||
| return pages | |||
| } | |||
| numPages := p.numPages | |||
| maxIdx := numPages - 1 | |||
| offsetIdx := 0 | |||
| hasMoreNext := false | |||
| // Check more previous and next pages. | |||
| previousNum := getMiddleIdx(p.numPages) - 1 | |||
| if previousNum > p.current-1 { | |||
| previousNum -= previousNum - (p.current - 1) | |||
| } | |||
| nextNum := p.numPages - previousNum - 1 | |||
| if p.current+nextNum > p.TotalPages() { | |||
| delta := nextNum - (p.TotalPages() - p.current) | |||
| nextNum -= delta | |||
| previousNum += delta | |||
| } | |||
| offsetVal := p.current - previousNum | |||
| if offsetVal > 1 { | |||
| numPages++ | |||
| maxIdx++ | |||
| offsetIdx = 1 | |||
| } | |||
| if p.current+nextNum < p.TotalPages() { | |||
| numPages++ | |||
| hasMoreNext = true | |||
| } | |||
| pages := make([]*Page, numPages) | |||
| // There are more previous pages. | |||
| if offsetIdx == 1 { | |||
| pages[0] = &Page{-1, false} | |||
| } | |||
| // There are more next pages. | |||
| if hasMoreNext { | |||
| pages[len(pages)-1] = &Page{-1, false} | |||
| } | |||
| // Check previous pages. | |||
| for i := 0; i < previousNum; i++ { | |||
| pages[offsetIdx+i] = &Page{i + offsetVal, false} | |||
| } | |||
| pages[offsetIdx+previousNum] = &Page{p.current, true} | |||
| // Check next pages. | |||
| for i := 1; i <= nextNum; i++ { | |||
| pages[offsetIdx+previousNum+i] = &Page{p.current + i, false} | |||
| } | |||
| return pages | |||
| } | |||
| @@ -0,0 +1,202 @@ | |||
| Apache License | |||
| Version 2.0, January 2004 | |||
| http://www.apache.org/licenses/ | |||
| TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | |||
| 1. Definitions. | |||
| "License" shall mean the terms and conditions for use, reproduction, | |||
| and distribution as defined by Sections 1 through 9 of this document. | |||
| "Licensor" shall mean the copyright owner or entity authorized by | |||
| the copyright owner that is granting the License. | |||
| "Legal Entity" shall mean the union of the acting entity and all | |||
| other entities that control, are controlled by, or are under common | |||
| control with that entity. For the purposes of this definition, | |||
| "control" means (i) the power, direct or indirect, to cause the | |||
| direction or management of such entity, whether by contract or | |||
| otherwise, or (ii) ownership of fifty percent (50%) or more of the | |||
| outstanding shares, or (iii) beneficial ownership of such entity. | |||
| "You" (or "Your") shall mean an individual or Legal Entity | |||
| exercising permissions granted by this License. | |||
| "Source" form shall mean the preferred form for making modifications, | |||
| including but not limited to software source code, documentation | |||
| source, and configuration files. | |||
| "Object" form shall mean any form resulting from mechanical | |||
| transformation or translation of a Source form, including but | |||
| not limited to compiled object code, generated documentation, | |||
| and conversions to other media types. | |||
| "Work" shall mean the work of authorship, whether in Source or | |||
| Object form, made available under the License, as indicated by a | |||
| copyright notice that is included in or attached to the work | |||
| (an example is provided in the Appendix below). | |||
| "Derivative Works" shall mean any work, whether in Source or Object | |||
| form, that is based on (or derived from) the Work and for which the | |||
| editorial revisions, annotations, elaborations, or other modifications | |||
| represent, as a whole, an original work of authorship. For the purposes | |||
| of this License, Derivative Works shall not include works that remain | |||
| separable from, or merely link (or bind by name) to the interfaces of, | |||
| the Work and Derivative Works thereof. | |||
| "Contribution" shall mean any work of authorship, including | |||
| the original version of the Work and any modifications or additions | |||
| to that Work or Derivative Works thereof, that is intentionally | |||
| submitted to Licensor for inclusion in the Work by the copyright owner | |||
| or by an individual or Legal Entity authorized to submit on behalf of | |||
| the copyright owner. For the purposes of this definition, "submitted" | |||
| means any form of electronic, verbal, or written communication sent | |||
| to the Licensor or its representatives, including but not limited to | |||
| communication on electronic mailing lists, source code control systems, | |||
| and issue tracking systems that are managed by, or on behalf of, the | |||
| Licensor for the purpose of discussing and improving the Work, but | |||
| excluding communication that is conspicuously marked or otherwise | |||
| designated in writing by the copyright owner as "Not a Contribution." | |||
| "Contributor" shall mean Licensor and any individual or Legal Entity | |||
| on behalf of whom a Contribution has been received by Licensor and | |||
| subsequently incorporated within the Work. | |||
| 2. Grant of Copyright License. Subject to the terms and conditions of | |||
| this License, each Contributor hereby grants to You a perpetual, | |||
| worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |||
| copyright license to reproduce, prepare Derivative Works of, | |||
| publicly display, publicly perform, sublicense, and distribute the | |||
| Work and such Derivative Works in Source or Object form. | |||
| 3. Grant of Patent License. Subject to the terms and conditions of | |||
| this License, each Contributor hereby grants to You a perpetual, | |||
| worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |||
| (except as stated in this section) patent license to make, have made, | |||
| use, offer to sell, sell, import, and otherwise transfer the Work, | |||
| where such license applies only to those patent claims licensable | |||
| by such Contributor that are necessarily infringed by their | |||
| Contribution(s) alone or by combination of their Contribution(s) | |||
| with the Work to which such Contribution(s) was submitted. If You | |||
| institute patent litigation against any entity (including a | |||
| cross-claim or counterclaim in a lawsuit) alleging that the Work | |||
| or a Contribution incorporated within the Work constitutes direct | |||
| or contributory patent infringement, then any patent licenses | |||
| granted to You under this License for that Work shall terminate | |||
| as of the date such litigation is filed. | |||
| 4. Redistribution. You may reproduce and distribute copies of the | |||
| Work or Derivative Works thereof in any medium, with or without | |||
| modifications, and in Source or Object form, provided that You | |||
| meet the following conditions: | |||
| (a) You must give any other recipients of the Work or | |||
| Derivative Works a copy of this License; and | |||
| (b) You must cause any modified files to carry prominent notices | |||
| stating that You changed the files; and | |||
| (c) You must retain, in the Source form of any Derivative Works | |||
| that You distribute, all copyright, patent, trademark, and | |||
| attribution notices from the Source form of the Work, | |||
| excluding those notices that do not pertain to any part of | |||
| the Derivative Works; and | |||
| (d) If the Work includes a "NOTICE" text file as part of its | |||
| distribution, then any Derivative Works that You distribute must | |||
| include a readable copy of the attribution notices contained | |||
| within such NOTICE file, excluding those notices that do not | |||
| pertain to any part of the Derivative Works, in at least one | |||
| of the following places: within a NOTICE text file distributed | |||
| as part of the Derivative Works; within the Source form or | |||
| documentation, if provided along with the Derivative Works; or, | |||
| within a display generated by the Derivative Works, if and | |||
| wherever such third-party notices normally appear. The contents | |||
| of the NOTICE file are for informational purposes only and | |||
| do not modify the License. You may add Your own attribution | |||
| notices within Derivative Works that You distribute, alongside | |||
| or as an addendum to the NOTICE text from the Work, provided | |||
| that such additional attribution notices cannot be construed | |||
| as modifying the License. | |||
| You may add Your own copyright statement to Your modifications and | |||
| may provide additional or different license terms and conditions | |||
| for use, reproduction, or distribution of Your modifications, or | |||
| for any such Derivative Works as a whole, provided Your use, | |||
| reproduction, and distribution of the Work otherwise complies with | |||
| the conditions stated in this License. | |||
| 5. Submission of Contributions. Unless You explicitly state otherwise, | |||
| any Contribution intentionally submitted for inclusion in the Work | |||
| by You to the Licensor shall be under the terms and conditions of | |||
| this License, without any additional terms or conditions. | |||
| Notwithstanding the above, nothing herein shall supersede or modify | |||
| the terms of any separate license agreement you may have executed | |||
| with Licensor regarding such Contributions. | |||
| 6. Trademarks. This License does not grant permission to use the trade | |||
| names, trademarks, service marks, or product names of the Licensor, | |||
| except as required for reasonable and customary use in describing the | |||
| origin of the Work and reproducing the content of the NOTICE file. | |||
| 7. Disclaimer of Warranty. Unless required by applicable law or | |||
| agreed to in writing, Licensor provides the Work (and each | |||
| Contributor provides its Contributions) on an "AS IS" BASIS, | |||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | |||
| implied, including, without limitation, any warranties or conditions | |||
| of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | |||
| PARTICULAR PURPOSE. You are solely responsible for determining the | |||
| appropriateness of using or redistributing the Work and assume any | |||
| risks associated with Your exercise of permissions under this License. | |||
| 8. Limitation of Liability. In no event and under no legal theory, | |||
| whether in tort (including negligence), contract, or otherwise, | |||
| unless required by applicable law (such as deliberate and grossly | |||
| negligent acts) or agreed to in writing, shall any Contributor be | |||
| liable to You for damages, including any direct, indirect, special, | |||
| incidental, or consequential damages of any character arising as a | |||
| result of this License or out of the use or inability to use the | |||
| Work (including but not limited to damages for loss of goodwill, | |||
| work stoppage, computer failure or malfunction, or any and all | |||
| other commercial damages or losses), even if such Contributor | |||
| has been advised of the possibility of such damages. | |||
| 9. Accepting Warranty or Additional Liability. While redistributing | |||
| the Work or Derivative Works thereof, You may choose to offer, | |||
| and charge a fee for, acceptance of support, warranty, indemnity, | |||
| or other liability obligations and/or rights consistent with this | |||
| License. However, in accepting such obligations, You may act only | |||
| on Your own behalf and on Your sole responsibility, not on behalf | |||
| of any other Contributor, and only if You agree to indemnify, | |||
| defend, and hold each Contributor harmless for any liability | |||
| incurred by, or claims asserted against, such Contributor by reason | |||
| of your accepting any such warranty or additional liability. | |||
| END OF TERMS AND CONDITIONS | |||
| APPENDIX: How to apply the Apache License to your work. | |||
| To apply the Apache License to your work, attach the following | |||
| boilerplate notice, with the fields enclosed by brackets "[]" | |||
| replaced with your own identifying information. (Don't include | |||
| the brackets!) The text should be enclosed in the appropriate | |||
| comment syntax for the file format. We also recommend that a | |||
| file or class name and description of purpose be included on the | |||
| same "printed page" as the copyright notice for easier | |||
| identification within third-party archives. | |||
| Copyright [yyyy] [name of copyright owner] | |||
| Licensed under the Apache License, Version 2.0 (the "License"); | |||
| you may not use this file except in compliance with the License. | |||
| You may obtain a copy of the License at | |||
| http://www.apache.org/licenses/LICENSE-2.0 | |||
| Unless required by applicable law or agreed to in writing, software | |||
| distributed under the License is distributed on an "AS IS" BASIS, | |||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| See the License for the specific language governing permissions and | |||
| limitations under the License. | |||
| @@ -0,0 +1,666 @@ | |||
| /* | |||
| Copyright 2011 Google Inc. | |||
| 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 memcache provides a client for the memcached cache server. | |||
| package memcache | |||
| import ( | |||
| "bufio" | |||
| "bytes" | |||
| "errors" | |||
| "fmt" | |||
| "io" | |||
| "io/ioutil" | |||
| "net" | |||
| "strconv" | |||
| "strings" | |||
| "sync" | |||
| "time" | |||
| ) | |||
| // Similar to: | |||
| // http://code.google.com/appengine/docs/go/memcache/reference.html | |||
| var ( | |||
| // ErrCacheMiss means that a Get failed because the item wasn't present. | |||
| ErrCacheMiss = errors.New("memcache: cache miss") | |||
| // ErrCASConflict means that a CompareAndSwap call failed due to the | |||
| // cached value being modified between the Get and the CompareAndSwap. | |||
| // If the cached value was simply evicted rather than replaced, | |||
| // ErrNotStored will be returned instead. | |||
| ErrCASConflict = errors.New("memcache: compare-and-swap conflict") | |||
| // ErrNotStored means that a conditional write operation (i.e. Add or | |||
| // CompareAndSwap) failed because the condition was not satisfied. | |||
| ErrNotStored = errors.New("memcache: item not stored") | |||
| // ErrServer means that a server error occurred. | |||
| ErrServerError = errors.New("memcache: server error") | |||
| // ErrNoStats means that no statistics were available. | |||
| ErrNoStats = errors.New("memcache: no statistics available") | |||
| // ErrMalformedKey is returned when an invalid key is used. | |||
| // Keys must be at maximum 250 bytes long, ASCII, and not | |||
| // contain whitespace or control characters. | |||
| ErrMalformedKey = errors.New("malformed: key is too long or contains invalid characters") | |||
| // ErrNoServers is returned when no servers are configured or available. | |||
| ErrNoServers = errors.New("memcache: no servers configured or available") | |||
| ) | |||
| // DefaultTimeout is the default socket read/write timeout. | |||
| const DefaultTimeout = 100 * time.Millisecond | |||
| const ( | |||
| buffered = 8 // arbitrary buffered channel size, for readability | |||
| maxIdleConnsPerAddr = 2 // TODO(bradfitz): make this configurable? | |||
| ) | |||
| // resumableError returns true if err is only a protocol-level cache error. | |||
| // This is used to determine whether or not a server connection should | |||
| // be re-used or not. If an error occurs, by default we don't reuse the | |||
| // connection, unless it was just a cache error. | |||
| func resumableError(err error) bool { | |||
| switch err { | |||
| case ErrCacheMiss, ErrCASConflict, ErrNotStored, ErrMalformedKey: | |||
| return true | |||
| } | |||
| return false | |||
| } | |||
| func legalKey(key string) bool { | |||
| if len(key) > 250 { | |||
| return false | |||
| } | |||
| for i := 0; i < len(key); i++ { | |||
| if key[i] <= ' ' || key[i] > 0x7e { | |||
| return false | |||
| } | |||
| } | |||
| return true | |||
| } | |||
| var ( | |||
| crlf = []byte("\r\n") | |||
| space = []byte(" ") | |||
| resultOK = []byte("OK\r\n") | |||
| resultStored = []byte("STORED\r\n") | |||
| resultNotStored = []byte("NOT_STORED\r\n") | |||
| resultExists = []byte("EXISTS\r\n") | |||
| resultNotFound = []byte("NOT_FOUND\r\n") | |||
| resultDeleted = []byte("DELETED\r\n") | |||
| resultEnd = []byte("END\r\n") | |||
| resultOk = []byte("OK\r\n") | |||
| resultTouched = []byte("TOUCHED\r\n") | |||
| resultClientErrorPrefix = []byte("CLIENT_ERROR ") | |||
| ) | |||
| // New returns a memcache client using the provided server(s) | |||
| // with equal weight. If a server is listed multiple times, | |||
| // it gets a proportional amount of weight. | |||
| func New(server ...string) *Client { | |||
| ss := new(ServerList) | |||
| ss.SetServers(server...) | |||
| return NewFromSelector(ss) | |||
| } | |||
| // NewFromSelector returns a new Client using the provided ServerSelector. | |||
| func NewFromSelector(ss ServerSelector) *Client { | |||
| return &Client{selector: ss} | |||
| } | |||
| // Client is a memcache client. | |||
| // It is safe for unlocked use by multiple concurrent goroutines. | |||
| type Client struct { | |||
| // Timeout specifies the socket read/write timeout. | |||
| // If zero, DefaultTimeout is used. | |||
| Timeout time.Duration | |||
| selector ServerSelector | |||
| lk sync.Mutex | |||
| freeconn map[string][]*conn | |||
| } | |||
| // Item is an item to be got or stored in a memcached server. | |||
| type Item struct { | |||
| // Key is the Item's key (250 bytes maximum). | |||
| Key string | |||
| // Value is the Item's value. | |||
| Value []byte | |||
| // Flags are server-opaque flags whose semantics are entirely | |||
| // up to the app. | |||
| Flags uint32 | |||
| // Expiration is the cache expiration time, in seconds: either a relative | |||
| // time from now (up to 1 month), or an absolute Unix epoch time. | |||
| // Zero means the Item has no expiration time. | |||
| Expiration int32 | |||
| // Compare and swap ID. | |||
| casid uint64 | |||
| } | |||
| // conn is a connection to a server. | |||
| type conn struct { | |||
| nc net.Conn | |||
| rw *bufio.ReadWriter | |||
| addr net.Addr | |||
| c *Client | |||
| } | |||
| // release returns this connection back to the client's free pool | |||
| func (cn *conn) release() { | |||
| cn.c.putFreeConn(cn.addr, cn) | |||
| } | |||
| func (cn *conn) extendDeadline() { | |||
| cn.nc.SetDeadline(time.Now().Add(cn.c.netTimeout())) | |||
| } | |||
| // condRelease releases this connection if the error pointed to by err | |||
| // is nil (not an error) or is only a protocol level error (e.g. a | |||
| // cache miss). The purpose is to not recycle TCP connections that | |||
| // are bad. | |||
| func (cn *conn) condRelease(err *error) { | |||
| if *err == nil || resumableError(*err) { | |||
| cn.release() | |||
| } else { | |||
| cn.nc.Close() | |||
| } | |||
| } | |||
| func (c *Client) putFreeConn(addr net.Addr, cn *conn) { | |||
| c.lk.Lock() | |||
| defer c.lk.Unlock() | |||
| if c.freeconn == nil { | |||
| c.freeconn = make(map[string][]*conn) | |||
| } | |||
| freelist := c.freeconn[addr.String()] | |||
| if len(freelist) >= maxIdleConnsPerAddr { | |||
| cn.nc.Close() | |||
| return | |||
| } | |||
| c.freeconn[addr.String()] = append(freelist, cn) | |||
| } | |||
| func (c *Client) getFreeConn(addr net.Addr) (cn *conn, ok bool) { | |||
| c.lk.Lock() | |||
| defer c.lk.Unlock() | |||
| if c.freeconn == nil { | |||
| return nil, false | |||
| } | |||
| freelist, ok := c.freeconn[addr.String()] | |||
| if !ok || len(freelist) == 0 { | |||
| return nil, false | |||
| } | |||
| cn = freelist[len(freelist)-1] | |||
| c.freeconn[addr.String()] = freelist[:len(freelist)-1] | |||
| return cn, true | |||
| } | |||
| func (c *Client) netTimeout() time.Duration { | |||
| if c.Timeout != 0 { | |||
| return c.Timeout | |||
| } | |||
| return DefaultTimeout | |||
| } | |||
| // ConnectTimeoutError is the error type used when it takes | |||
| // too long to connect to the desired host. This level of | |||
| // detail can generally be ignored. | |||
| type ConnectTimeoutError struct { | |||
| Addr net.Addr | |||
| } | |||
| func (cte *ConnectTimeoutError) Error() string { | |||
| return "memcache: connect timeout to " + cte.Addr.String() | |||
| } | |||
| func (c *Client) dial(addr net.Addr) (net.Conn, error) { | |||
| type connError struct { | |||
| cn net.Conn | |||
| err error | |||
| } | |||
| nc, err := net.DialTimeout(addr.Network(), addr.String(), c.netTimeout()) | |||
| if err == nil { | |||
| return nc, nil | |||
| } | |||
| if ne, ok := err.(net.Error); ok && ne.Timeout() { | |||
| return nil, &ConnectTimeoutError{addr} | |||
| } | |||
| return nil, err | |||
| } | |||
| func (c *Client) getConn(addr net.Addr) (*conn, error) { | |||
| cn, ok := c.getFreeConn(addr) | |||
| if ok { | |||
| cn.extendDeadline() | |||
| return cn, nil | |||
| } | |||
| nc, err := c.dial(addr) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| cn = &conn{ | |||
| nc: nc, | |||
| addr: addr, | |||
| rw: bufio.NewReadWriter(bufio.NewReader(nc), bufio.NewWriter(nc)), | |||
| c: c, | |||
| } | |||
| cn.extendDeadline() | |||
| return cn, nil | |||
| } | |||
| func (c *Client) onItem(item *Item, fn func(*Client, *bufio.ReadWriter, *Item) error) error { | |||
| addr, err := c.selector.PickServer(item.Key) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| cn, err := c.getConn(addr) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| defer cn.condRelease(&err) | |||
| if err = fn(c, cn.rw, item); err != nil { | |||
| return err | |||
| } | |||
| return nil | |||
| } | |||
| func (c *Client) FlushAll() error { | |||
| return c.selector.Each(c.flushAllFromAddr) | |||
| } | |||
| // Get gets the item for the given key. ErrCacheMiss is returned for a | |||
| // memcache cache miss. The key must be at most 250 bytes in length. | |||
| func (c *Client) Get(key string) (item *Item, err error) { | |||
| err = c.withKeyAddr(key, func(addr net.Addr) error { | |||
| return c.getFromAddr(addr, []string{key}, func(it *Item) { item = it }) | |||
| }) | |||
| if err == nil && item == nil { | |||
| err = ErrCacheMiss | |||
| } | |||
| return | |||
| } | |||
| // Touch updates the expiry for the given key. The seconds parameter is either | |||
| // a Unix timestamp or, if seconds is less than 1 month, the number of seconds | |||
| // into the future at which time the item will expire. ErrCacheMiss is returned if the | |||
| // key is not in the cache. The key must be at most 250 bytes in length. | |||
| func (c *Client) Touch(key string, seconds int32) (err error) { | |||
| return c.withKeyAddr(key, func(addr net.Addr) error { | |||
| return c.touchFromAddr(addr, []string{key}, seconds) | |||
| }) | |||
| } | |||
| func (c *Client) withKeyAddr(key string, fn func(net.Addr) error) (err error) { | |||
| if !legalKey(key) { | |||
| return ErrMalformedKey | |||
| } | |||
| addr, err := c.selector.PickServer(key) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| return fn(addr) | |||
| } | |||
| func (c *Client) withAddrRw(addr net.Addr, fn func(*bufio.ReadWriter) error) (err error) { | |||
| cn, err := c.getConn(addr) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| defer cn.condRelease(&err) | |||
| return fn(cn.rw) | |||
| } | |||
| func (c *Client) withKeyRw(key string, fn func(*bufio.ReadWriter) error) error { | |||
| return c.withKeyAddr(key, func(addr net.Addr) error { | |||
| return c.withAddrRw(addr, fn) | |||
| }) | |||
| } | |||
| func (c *Client) getFromAddr(addr net.Addr, keys []string, cb func(*Item)) error { | |||
| return c.withAddrRw(addr, func(rw *bufio.ReadWriter) error { | |||
| if _, err := fmt.Fprintf(rw, "gets %s\r\n", strings.Join(keys, " ")); err != nil { | |||
| return err | |||
| } | |||
| if err := rw.Flush(); err != nil { | |||
| return err | |||
| } | |||
| if err := parseGetResponse(rw.Reader, cb); err != nil { | |||
| return err | |||
| } | |||
| return nil | |||
| }) | |||
| } | |||
| // flushAllFromAddr send the flush_all command to the given addr | |||
| func (c *Client) flushAllFromAddr(addr net.Addr) error { | |||
| return c.withAddrRw(addr, func(rw *bufio.ReadWriter) error { | |||
| if _, err := fmt.Fprintf(rw, "flush_all\r\n"); err != nil { | |||
| return err | |||
| } | |||
| if err := rw.Flush(); err != nil { | |||
| return err | |||
| } | |||
| line, err := rw.ReadSlice('\n') | |||
| if err != nil { | |||
| return err | |||
| } | |||
| switch { | |||
| case bytes.Equal(line, resultOk): | |||
| break | |||
| default: | |||
| return fmt.Errorf("memcache: unexpected response line from flush_all: %q", string(line)) | |||
| } | |||
| return nil | |||
| }) | |||
| } | |||
| func (c *Client) touchFromAddr(addr net.Addr, keys []string, expiration int32) error { | |||
| return c.withAddrRw(addr, func(rw *bufio.ReadWriter) error { | |||
| for _, key := range keys { | |||
| if _, err := fmt.Fprintf(rw, "touch %s %d\r\n", key, expiration); err != nil { | |||
| return err | |||
| } | |||
| if err := rw.Flush(); err != nil { | |||
| return err | |||
| } | |||
| line, err := rw.ReadSlice('\n') | |||
| if err != nil { | |||
| return err | |||
| } | |||
| switch { | |||
| case bytes.Equal(line, resultTouched): | |||
| break | |||
| case bytes.Equal(line, resultNotFound): | |||
| return ErrCacheMiss | |||
| default: | |||
| return fmt.Errorf("memcache: unexpected response line from touch: %q", string(line)) | |||
| } | |||
| } | |||
| return nil | |||
| }) | |||
| } | |||
| // GetMulti is a batch version of Get. The returned map from keys to | |||
| // items may have fewer elements than the input slice, due to memcache | |||
| // cache misses. Each key must be at most 250 bytes in length. | |||
| // If no error is returned, the returned map will also be non-nil. | |||
| func (c *Client) GetMulti(keys []string) (map[string]*Item, error) { | |||
| var lk sync.Mutex | |||
| m := make(map[string]*Item) | |||
| addItemToMap := func(it *Item) { | |||
| lk.Lock() | |||
| defer lk.Unlock() | |||
| m[it.Key] = it | |||
| } | |||
| keyMap := make(map[net.Addr][]string) | |||
| for _, key := range keys { | |||
| if !legalKey(key) { | |||
| return nil, ErrMalformedKey | |||
| } | |||
| addr, err := c.selector.PickServer(key) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| keyMap[addr] = append(keyMap[addr], key) | |||
| } | |||
| ch := make(chan error, buffered) | |||
| for addr, keys := range keyMap { | |||
| go func(addr net.Addr, keys []string) { | |||
| ch <- c.getFromAddr(addr, keys, addItemToMap) | |||
| }(addr, keys) | |||
| } | |||
| var err error | |||
| for _ = range keyMap { | |||
| if ge := <-ch; ge != nil { | |||
| err = ge | |||
| } | |||
| } | |||
| return m, err | |||
| } | |||
| // parseGetResponse reads a GET response from r and calls cb for each | |||
| // read and allocated Item | |||
| func parseGetResponse(r *bufio.Reader, cb func(*Item)) error { | |||
| for { | |||
| line, err := r.ReadSlice('\n') | |||
| if err != nil { | |||
| return err | |||
| } | |||
| if bytes.Equal(line, resultEnd) { | |||
| return nil | |||
| } | |||
| it := new(Item) | |||
| size, err := scanGetResponseLine(line, it) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| it.Value, err = ioutil.ReadAll(io.LimitReader(r, int64(size)+2)) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| if !bytes.HasSuffix(it.Value, crlf) { | |||
| return fmt.Errorf("memcache: corrupt get result read") | |||
| } | |||
| it.Value = it.Value[:size] | |||
| cb(it) | |||
| } | |||
| } | |||
| // scanGetResponseLine populates it and returns the declared size of the item. | |||
| // It does not read the bytes of the item. | |||
| func scanGetResponseLine(line []byte, it *Item) (size int, err error) { | |||
| pattern := "VALUE %s %d %d %d\r\n" | |||
| dest := []interface{}{&it.Key, &it.Flags, &size, &it.casid} | |||
| if bytes.Count(line, space) == 3 { | |||
| pattern = "VALUE %s %d %d\r\n" | |||
| dest = dest[:3] | |||
| } | |||
| n, err := fmt.Sscanf(string(line), pattern, dest...) | |||
| if err != nil || n != len(dest) { | |||
| return -1, fmt.Errorf("memcache: unexpected line in get response: %q", line) | |||
| } | |||
| return size, nil | |||
| } | |||
| // Set writes the given item, unconditionally. | |||
| func (c *Client) Set(item *Item) error { | |||
| return c.onItem(item, (*Client).set) | |||
| } | |||
| func (c *Client) set(rw *bufio.ReadWriter, item *Item) error { | |||
| return c.populateOne(rw, "set", item) | |||
| } | |||
| // Add writes the given item, if no value already exists for its | |||
| // key. ErrNotStored is returned if that condition is not met. | |||
| func (c *Client) Add(item *Item) error { | |||
| return c.onItem(item, (*Client).add) | |||
| } | |||
| func (c *Client) add(rw *bufio.ReadWriter, item *Item) error { | |||
| return c.populateOne(rw, "add", item) | |||
| } | |||
| // Replace writes the given item, but only if the server *does* | |||
| // already hold data for this key | |||
| func (c *Client) Replace(item *Item) error { | |||
| return c.onItem(item, (*Client).replace) | |||
| } | |||
| func (c *Client) replace(rw *bufio.ReadWriter, item *Item) error { | |||
| return c.populateOne(rw, "replace", item) | |||
| } | |||
| // CompareAndSwap writes the given item that was previously returned | |||
| // by Get, if the value was neither modified or evicted between the | |||
| // Get and the CompareAndSwap calls. The item's Key should not change | |||
| // between calls but all other item fields may differ. ErrCASConflict | |||
| // is returned if the value was modified in between the | |||
| // calls. ErrNotStored is returned if the value was evicted in between | |||
| // the calls. | |||
| func (c *Client) CompareAndSwap(item *Item) error { | |||
| return c.onItem(item, (*Client).cas) | |||
| } | |||
| func (c *Client) cas(rw *bufio.ReadWriter, item *Item) error { | |||
| return c.populateOne(rw, "cas", item) | |||
| } | |||
| func (c *Client) populateOne(rw *bufio.ReadWriter, verb string, item *Item) error { | |||
| if !legalKey(item.Key) { | |||
| return ErrMalformedKey | |||
| } | |||
| var err error | |||
| if verb == "cas" { | |||
| _, err = fmt.Fprintf(rw, "%s %s %d %d %d %d\r\n", | |||
| verb, item.Key, item.Flags, item.Expiration, len(item.Value), item.casid) | |||
| } else { | |||
| _, err = fmt.Fprintf(rw, "%s %s %d %d %d\r\n", | |||
| verb, item.Key, item.Flags, item.Expiration, len(item.Value)) | |||
| } | |||
| if err != nil { | |||
| return err | |||
| } | |||
| if _, err = rw.Write(item.Value); err != nil { | |||
| return err | |||
| } | |||
| if _, err := rw.Write(crlf); err != nil { | |||
| return err | |||
| } | |||
| if err := rw.Flush(); err != nil { | |||
| return err | |||
| } | |||
| line, err := rw.ReadSlice('\n') | |||
| if err != nil { | |||
| return err | |||
| } | |||
| switch { | |||
| case bytes.Equal(line, resultStored): | |||
| return nil | |||
| case bytes.Equal(line, resultNotStored): | |||
| return ErrNotStored | |||
| case bytes.Equal(line, resultExists): | |||
| return ErrCASConflict | |||
| case bytes.Equal(line, resultNotFound): | |||
| return ErrCacheMiss | |||
| } | |||
| return fmt.Errorf("memcache: unexpected response line from %q: %q", verb, string(line)) | |||
| } | |||
| func writeReadLine(rw *bufio.ReadWriter, format string, args ...interface{}) ([]byte, error) { | |||
| _, err := fmt.Fprintf(rw, format, args...) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| if err := rw.Flush(); err != nil { | |||
| return nil, err | |||
| } | |||
| line, err := rw.ReadSlice('\n') | |||
| return line, err | |||
| } | |||
| func writeExpectf(rw *bufio.ReadWriter, expect []byte, format string, args ...interface{}) error { | |||
| line, err := writeReadLine(rw, format, args...) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| switch { | |||
| case bytes.Equal(line, resultOK): | |||
| return nil | |||
| case bytes.Equal(line, expect): | |||
| return nil | |||
| case bytes.Equal(line, resultNotStored): | |||
| return ErrNotStored | |||
| case bytes.Equal(line, resultExists): | |||
| return ErrCASConflict | |||
| case bytes.Equal(line, resultNotFound): | |||
| return ErrCacheMiss | |||
| } | |||
| return fmt.Errorf("memcache: unexpected response line: %q", string(line)) | |||
| } | |||
| // Delete deletes the item with the provided key. The error ErrCacheMiss is | |||
| // returned if the item didn't already exist in the cache. | |||
| func (c *Client) Delete(key string) error { | |||
| return c.withKeyRw(key, func(rw *bufio.ReadWriter) error { | |||
| return writeExpectf(rw, resultDeleted, "delete %s\r\n", key) | |||
| }) | |||
| } | |||
| // DeleteAll deletes all items in the cache. | |||
| func (c *Client) DeleteAll() error { | |||
| return c.withKeyRw("", func(rw *bufio.ReadWriter) error { | |||
| return writeExpectf(rw, resultDeleted, "flush_all\r\n") | |||
| }) | |||
| } | |||
| // Increment atomically increments key by delta. The return value is | |||
| // the new value after being incremented or an error. If the value | |||
| // didn't exist in memcached the error is ErrCacheMiss. The value in | |||
| // memcached must be an decimal number, or an error will be returned. | |||
| // On 64-bit overflow, the new value wraps around. | |||
| func (c *Client) Increment(key string, delta uint64) (newValue uint64, err error) { | |||
| return c.incrDecr("incr", key, delta) | |||
| } | |||
| // Decrement atomically decrements key by delta. The return value is | |||
| // the new value after being decremented or an error. If the value | |||
| // didn't exist in memcached the error is ErrCacheMiss. The value in | |||
| // memcached must be an decimal number, or an error will be returned. | |||
| // On underflow, the new value is capped at zero and does not wrap | |||
| // around. | |||
| func (c *Client) Decrement(key string, delta uint64) (newValue uint64, err error) { | |||
| return c.incrDecr("decr", key, delta) | |||
| } | |||
| func (c *Client) incrDecr(verb, key string, delta uint64) (uint64, error) { | |||
| var val uint64 | |||
| err := c.withKeyRw(key, func(rw *bufio.ReadWriter) error { | |||
| line, err := writeReadLine(rw, "%s %s %d\r\n", verb, key, delta) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| switch { | |||
| case bytes.Equal(line, resultNotFound): | |||
| return ErrCacheMiss | |||
| case bytes.HasPrefix(line, resultClientErrorPrefix): | |||
| errMsg := line[len(resultClientErrorPrefix) : len(line)-2] | |||
| return errors.New("memcache: client error: " + string(errMsg)) | |||
| } | |||
| val, err = strconv.ParseUint(string(line[:len(line)-2]), 10, 64) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| return nil | |||
| }) | |||
| return val, err | |||
| } | |||
| @@ -0,0 +1,114 @@ | |||
| /* | |||
| Copyright 2011 Google Inc. | |||
| 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 memcache | |||
| import ( | |||
| "hash/crc32" | |||
| "net" | |||
| "strings" | |||
| "sync" | |||
| ) | |||
| // ServerSelector is the interface that selects a memcache server | |||
| // as a function of the item's key. | |||
| // | |||
| // All ServerSelector implementations must be safe for concurrent use | |||
| // by multiple goroutines. | |||
| type ServerSelector interface { | |||
| // PickServer returns the server address that a given item | |||
| // should be shared onto. | |||
| PickServer(key string) (net.Addr, error) | |||
| Each(func(net.Addr) error) error | |||
| } | |||
| // ServerList is a simple ServerSelector. Its zero value is usable. | |||
| type ServerList struct { | |||
| mu sync.RWMutex | |||
| addrs []net.Addr | |||
| } | |||
| // SetServers changes a ServerList's set of servers at runtime and is | |||
| // safe for concurrent use by multiple goroutines. | |||
| // | |||
| // Each server is given equal weight. A server is given more weight | |||
| // if it's listed multiple times. | |||
| // | |||
| // SetServers returns an error if any of the server names fail to | |||
| // resolve. No attempt is made to connect to the server. If any error | |||
| // is returned, no changes are made to the ServerList. | |||
| func (ss *ServerList) SetServers(servers ...string) error { | |||
| naddr := make([]net.Addr, len(servers)) | |||
| for i, server := range servers { | |||
| if strings.Contains(server, "/") { | |||
| addr, err := net.ResolveUnixAddr("unix", server) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| naddr[i] = addr | |||
| } else { | |||
| tcpaddr, err := net.ResolveTCPAddr("tcp", server) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| naddr[i] = tcpaddr | |||
| } | |||
| } | |||
| ss.mu.Lock() | |||
| defer ss.mu.Unlock() | |||
| ss.addrs = naddr | |||
| return nil | |||
| } | |||
| // Each iterates over each server calling the given function | |||
| func (ss *ServerList) Each(f func(net.Addr) error) error { | |||
| ss.mu.RLock() | |||
| defer ss.mu.RUnlock() | |||
| for _, a := range ss.addrs { | |||
| if err := f(a); nil != err { | |||
| return err | |||
| } | |||
| } | |||
| return nil | |||
| } | |||
| // keyBufPool returns []byte buffers for use by PickServer's call to | |||
| // crc32.ChecksumIEEE to avoid allocations. (but doesn't avoid the | |||
| // copies, which at least are bounded in size and small) | |||
| var keyBufPool = sync.Pool{ | |||
| New: func() interface{} { | |||
| b := make([]byte, 256) | |||
| return &b | |||
| }, | |||
| } | |||
| func (ss *ServerList) PickServer(key string) (net.Addr, error) { | |||
| ss.mu.RLock() | |||
| defer ss.mu.RUnlock() | |||
| if len(ss.addrs) == 0 { | |||
| return nil, ErrNoServers | |||
| } | |||
| if len(ss.addrs) == 1 { | |||
| return ss.addrs[0], nil | |||
| } | |||
| bufp := keyBufPool.Get().(*[]byte) | |||
| n := copy(*bufp, key) | |||
| cs := crc32.ChecksumIEEE((*bufp)[:n]) | |||
| keyBufPool.Put(bufp) | |||
| return ss.addrs[cs%uint32(len(ss.addrs))], nil | |||
| } | |||
| @@ -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,20 @@ | |||
| # binding [](https://travis-ci.org/go-macaron/binding) [](http://gocover.io/github.com/go-macaron/binding) | |||
| Middleware binding provides request data binding and validation for [Macaron](https://github.com/go-macaron/macaron). | |||
| ### Installation | |||
| go get github.com/go-macaron/binding | |||
| ## Getting Help | |||
| - [API Reference](https://gowalker.org/github.com/go-macaron/binding) | |||
| - [Documentation](http://go-macaron.com/docs/middlewares/binding) | |||
| ## Credits | |||
| This package is a modified version of [martini-contrib/binding](https://github.com/martini-contrib/binding). | |||
| ## License | |||
| This project is under the Apache License, Version 2.0. See the [LICENSE](LICENSE) file for the full license text. | |||
| @@ -0,0 +1,669 @@ | |||
| // Copyright 2014 Martini Authors | |||
| // Copyright 2014 The Macaron Authors | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"): you may | |||
| // not use this file except in compliance with the License. You may obtain | |||
| // a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
| // License for the specific language governing permissions and limitations | |||
| // under the License. | |||
| // Package binding is a middleware that provides request data binding and validation for Macaron. | |||
| package binding | |||
| import ( | |||
| "encoding/json" | |||
| "fmt" | |||
| "io" | |||
| "mime/multipart" | |||
| "net/http" | |||
| "reflect" | |||
| "regexp" | |||
| "strconv" | |||
| "strings" | |||
| "unicode/utf8" | |||
| "github.com/Unknwon/com" | |||
| "gopkg.in/macaron.v1" | |||
| ) | |||
| const _VERSION = "0.3.2" | |||
| func Version() string { | |||
| return _VERSION | |||
| } | |||
| func bind(ctx *macaron.Context, obj interface{}, ifacePtr ...interface{}) { | |||
| contentType := ctx.Req.Header.Get("Content-Type") | |||
| if ctx.Req.Method == "POST" || ctx.Req.Method == "PUT" || len(contentType) > 0 { | |||
| switch { | |||
| case strings.Contains(contentType, "form-urlencoded"): | |||
| ctx.Invoke(Form(obj, ifacePtr...)) | |||
| case strings.Contains(contentType, "multipart/form-data"): | |||
| ctx.Invoke(MultipartForm(obj, ifacePtr...)) | |||
| case strings.Contains(contentType, "json"): | |||
| ctx.Invoke(Json(obj, ifacePtr...)) | |||
| default: | |||
| var errors Errors | |||
| if contentType == "" { | |||
| errors.Add([]string{}, ERR_CONTENT_TYPE, "Empty Content-Type") | |||
| } else { | |||
| errors.Add([]string{}, ERR_CONTENT_TYPE, "Unsupported Content-Type") | |||
| } | |||
| ctx.Map(errors) | |||
| ctx.Map(obj) // Map a fake struct so handler won't panic. | |||
| } | |||
| } else { | |||
| ctx.Invoke(Form(obj, ifacePtr...)) | |||
| } | |||
| } | |||
| const ( | |||
| _JSON_CONTENT_TYPE = "application/json; charset=utf-8" | |||
| STATUS_UNPROCESSABLE_ENTITY = 422 | |||
| ) | |||
| // errorHandler simply counts the number of errors in the | |||
| // context and, if more than 0, writes a response with an | |||
| // error code and a JSON payload describing the errors. | |||
| // The response will have a JSON content-type. | |||
| // Middleware remaining on the stack will not even see the request | |||
| // if, by this point, there are any errors. | |||
| // This is a "default" handler, of sorts, and you are | |||
| // welcome to use your own instead. The Bind middleware | |||
| // invokes this automatically for convenience. | |||
| func errorHandler(errs Errors, rw http.ResponseWriter) { | |||
| if len(errs) > 0 { | |||
| rw.Header().Set("Content-Type", _JSON_CONTENT_TYPE) | |||
| if errs.Has(ERR_DESERIALIZATION) { | |||
| rw.WriteHeader(http.StatusBadRequest) | |||
| } else if errs.Has(ERR_CONTENT_TYPE) { | |||
| rw.WriteHeader(http.StatusUnsupportedMediaType) | |||
| } else { | |||
| rw.WriteHeader(STATUS_UNPROCESSABLE_ENTITY) | |||
| } | |||
| errOutput, _ := json.Marshal(errs) | |||
| rw.Write(errOutput) | |||
| return | |||
| } | |||
| } | |||
| // Bind wraps up the functionality of the Form and Json middleware | |||
| // according to the Content-Type and verb of the request. | |||
| // A Content-Type is required for POST and PUT requests. | |||
| // Bind invokes the ErrorHandler middleware to bail out if errors | |||
| // occurred. If you want to perform your own error handling, use | |||
| // Form or Json middleware directly. An interface pointer can | |||
| // be added as a second argument in order to map the struct to | |||
| // a specific interface. | |||
| func Bind(obj interface{}, ifacePtr ...interface{}) macaron.Handler { | |||
| return func(ctx *macaron.Context) { | |||
| bind(ctx, obj, ifacePtr...) | |||
| if handler, ok := obj.(ErrorHandler); ok { | |||
| ctx.Invoke(handler.Error) | |||
| } else { | |||
| ctx.Invoke(errorHandler) | |||
| } | |||
| } | |||
| } | |||
| // BindIgnErr will do the exactly same thing as Bind but without any | |||
| // error handling, which user has freedom to deal with them. | |||
| // This allows user take advantages of validation. | |||
| func BindIgnErr(obj interface{}, ifacePtr ...interface{}) macaron.Handler { | |||
| return func(ctx *macaron.Context) { | |||
| bind(ctx, obj, ifacePtr...) | |||
| } | |||
| } | |||
| // Form is middleware to deserialize form-urlencoded data from the request. | |||
| // It gets data from the form-urlencoded body, if present, or from the | |||
| // query string. It uses the http.Request.ParseForm() method | |||
| // to perform deserialization, then reflection is used to map each field | |||
| // into the struct with the proper type. Structs with primitive slice types | |||
| // (bool, float, int, string) can support deserialization of repeated form | |||
| // keys, for example: key=val1&key=val2&key=val3 | |||
| // An interface pointer can be added as a second argument in order | |||
| // to map the struct to a specific interface. | |||
| func Form(formStruct interface{}, ifacePtr ...interface{}) macaron.Handler { | |||
| return func(ctx *macaron.Context) { | |||
| var errors Errors | |||
| ensureNotPointer(formStruct) | |||
| formStruct := reflect.New(reflect.TypeOf(formStruct)) | |||
| parseErr := ctx.Req.ParseForm() | |||
| // Format validation of the request body or the URL would add considerable overhead, | |||
| // and ParseForm does not complain when URL encoding is off. | |||
| // Because an empty request body or url can also mean absence of all needed values, | |||
| // it is not in all cases a bad request, so let's return 422. | |||
| if parseErr != nil { | |||
| errors.Add([]string{}, ERR_DESERIALIZATION, parseErr.Error()) | |||
| } | |||
| mapForm(formStruct, ctx.Req.Form, nil, errors) | |||
| validateAndMap(formStruct, ctx, errors, ifacePtr...) | |||
| } | |||
| } | |||
| // Maximum amount of memory to use when parsing a multipart form. | |||
| // Set this to whatever value you prefer; default is 10 MB. | |||
| var MaxMemory = int64(1024 * 1024 * 10) | |||
| // MultipartForm works much like Form, except it can parse multipart forms | |||
| // and handle file uploads. Like the other deserialization middleware handlers, | |||
| // you can pass in an interface to make the interface available for injection | |||
| // into other handlers later. | |||
| func MultipartForm(formStruct interface{}, ifacePtr ...interface{}) macaron.Handler { | |||
| return func(ctx *macaron.Context) { | |||
| var errors Errors | |||
| ensureNotPointer(formStruct) | |||
| formStruct := reflect.New(reflect.TypeOf(formStruct)) | |||
| // This if check is necessary due to https://github.com/martini-contrib/csrf/issues/6 | |||
| if ctx.Req.MultipartForm == nil { | |||
| // Workaround for multipart forms returning nil instead of an error | |||
| // when content is not multipart; see https://code.google.com/p/go/issues/detail?id=6334 | |||
| if multipartReader, err := ctx.Req.MultipartReader(); err != nil { | |||
| errors.Add([]string{}, ERR_DESERIALIZATION, err.Error()) | |||
| } else { | |||
| form, parseErr := multipartReader.ReadForm(MaxMemory) | |||
| if parseErr != nil { | |||
| errors.Add([]string{}, ERR_DESERIALIZATION, parseErr.Error()) | |||
| } | |||
| if ctx.Req.Form == nil { | |||
| ctx.Req.ParseForm() | |||
| } | |||
| for k, v := range form.Value { | |||
| ctx.Req.Form[k] = append(ctx.Req.Form[k], v...) | |||
| } | |||
| ctx.Req.MultipartForm = form | |||
| } | |||
| } | |||
| mapForm(formStruct, ctx.Req.MultipartForm.Value, ctx.Req.MultipartForm.File, errors) | |||
| validateAndMap(formStruct, ctx, errors, ifacePtr...) | |||
| } | |||
| } | |||
| // Json is middleware to deserialize a JSON payload from the request | |||
| // into the struct that is passed in. The resulting struct is then | |||
| // validated, but no error handling is actually performed here. | |||
| // An interface pointer can be added as a second argument in order | |||
| // to map the struct to a specific interface. | |||
| func Json(jsonStruct interface{}, ifacePtr ...interface{}) macaron.Handler { | |||
| return func(ctx *macaron.Context) { | |||
| var errors Errors | |||
| ensureNotPointer(jsonStruct) | |||
| jsonStruct := reflect.New(reflect.TypeOf(jsonStruct)) | |||
| if ctx.Req.Request.Body != nil { | |||
| defer ctx.Req.Request.Body.Close() | |||
| err := json.NewDecoder(ctx.Req.Request.Body).Decode(jsonStruct.Interface()) | |||
| if err != nil && err != io.EOF { | |||
| errors.Add([]string{}, ERR_DESERIALIZATION, err.Error()) | |||
| } | |||
| } | |||
| validateAndMap(jsonStruct, ctx, errors, ifacePtr...) | |||
| } | |||
| } | |||
| // Validate is middleware to enforce required fields. If the struct | |||
| // passed in implements Validator, then the user-defined Validate method | |||
| // is executed, and its errors are mapped to the context. This middleware | |||
| // performs no error handling: it merely detects errors and maps them. | |||
| func Validate(obj interface{}) macaron.Handler { | |||
| return func(ctx *macaron.Context) { | |||
| var errors Errors | |||
| v := reflect.ValueOf(obj) | |||
| k := v.Kind() | |||
| if k == reflect.Interface || k == reflect.Ptr { | |||
| v = v.Elem() | |||
| k = v.Kind() | |||
| } | |||
| if k == reflect.Slice || k == reflect.Array { | |||
| for i := 0; i < v.Len(); i++ { | |||
| e := v.Index(i).Interface() | |||
| errors = validateStruct(errors, e) | |||
| if validator, ok := e.(Validator); ok { | |||
| errors = validator.Validate(ctx, errors) | |||
| } | |||
| } | |||
| } else { | |||
| errors = validateStruct(errors, obj) | |||
| if validator, ok := obj.(Validator); ok { | |||
| errors = validator.Validate(ctx, errors) | |||
| } | |||
| } | |||
| ctx.Map(errors) | |||
| } | |||
| } | |||
| var ( | |||
| AlphaDashPattern = regexp.MustCompile("[^\\d\\w-_]") | |||
| AlphaDashDotPattern = regexp.MustCompile("[^\\d\\w-_\\.]") | |||
| EmailPattern = regexp.MustCompile("[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[a-zA-Z0-9](?:[\\w-]*[\\w])?") | |||
| URLPattern = regexp.MustCompile(`(http|https):\/\/(?:\\S+(?::\\S*)?@)?[\w\-_]+(\.[\w\-_]+)*([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?`) | |||
| ) | |||
| type ( | |||
| // Rule represents a validation rule. | |||
| Rule struct { | |||
| // IsMatch checks if rule matches. | |||
| IsMatch func(string) bool | |||
| // IsValid applies validation rule to condition. | |||
| IsValid func(Errors, string, interface{}) (bool, Errors) | |||
| } | |||
| // RuleMapper represents a validation rule mapper, | |||
| // it allwos users to add custom validation rules. | |||
| RuleMapper []*Rule | |||
| ) | |||
| var ruleMapper RuleMapper | |||
| // AddRule adds new validation rule. | |||
| func AddRule(r *Rule) { | |||
| ruleMapper = append(ruleMapper, r) | |||
| } | |||
| func in(fieldValue interface{}, arr string) bool { | |||
| val := fmt.Sprintf("%v", fieldValue) | |||
| vals := strings.Split(arr, ",") | |||
| isIn := false | |||
| for _, v := range vals { | |||
| if v == val { | |||
| isIn = true | |||
| break | |||
| } | |||
| } | |||
| return isIn | |||
| } | |||
| func parseFormName(raw, actual string) string { | |||
| if len(actual) > 0 { | |||
| return actual | |||
| } | |||
| return nameMapper(raw) | |||
| } | |||
| // Performs required field checking on a struct | |||
| func validateStruct(errors Errors, obj interface{}) Errors { | |||
| typ := reflect.TypeOf(obj) | |||
| val := reflect.ValueOf(obj) | |||
| if typ.Kind() == reflect.Ptr { | |||
| typ = typ.Elem() | |||
| val = val.Elem() | |||
| } | |||
| for i := 0; i < typ.NumField(); i++ { | |||
| field := typ.Field(i) | |||
| // Allow ignored fields in the struct | |||
| if field.Tag.Get("form") == "-" || !val.Field(i).CanInterface() { | |||
| continue | |||
| } | |||
| fieldVal := val.Field(i) | |||
| fieldValue := fieldVal.Interface() | |||
| zero := reflect.Zero(field.Type).Interface() | |||
| // Validate nested and embedded structs (if pointer, only do so if not nil) | |||
| if field.Type.Kind() == reflect.Struct || | |||
| (field.Type.Kind() == reflect.Ptr && !reflect.DeepEqual(zero, fieldValue) && | |||
| field.Type.Elem().Kind() == reflect.Struct) { | |||
| errors = validateStruct(errors, fieldValue) | |||
| } | |||
| errors = validateField(errors, zero, field, fieldVal, fieldValue) | |||
| } | |||
| return errors | |||
| } | |||
| func validateField(errors Errors, zero interface{}, field reflect.StructField, fieldVal reflect.Value, fieldValue interface{}) Errors { | |||
| if fieldVal.Kind() == reflect.Slice { | |||
| for i := 0; i < fieldVal.Len(); i++ { | |||
| sliceVal := fieldVal.Index(i) | |||
| if sliceVal.Kind() == reflect.Ptr { | |||
| sliceVal = sliceVal.Elem() | |||
| } | |||
| sliceValue := sliceVal.Interface() | |||
| zero := reflect.Zero(sliceVal.Type()).Interface() | |||
| if sliceVal.Kind() == reflect.Struct || | |||
| (sliceVal.Kind() == reflect.Ptr && !reflect.DeepEqual(zero, sliceValue) && | |||
| sliceVal.Elem().Kind() == reflect.Struct) { | |||
| errors = validateStruct(errors, sliceValue) | |||
| } | |||
| /* Apply validation rules to each item in a slice. ISSUE #3 | |||
| else { | |||
| errors = validateField(errors, zero, field, sliceVal, sliceValue) | |||
| }*/ | |||
| } | |||
| } | |||
| VALIDATE_RULES: | |||
| for _, rule := range strings.Split(field.Tag.Get("binding"), ";") { | |||
| if len(rule) == 0 { | |||
| continue | |||
| } | |||
| switch { | |||
| case rule == "OmitEmpty": | |||
| if reflect.DeepEqual(zero, fieldValue) { | |||
| break VALIDATE_RULES | |||
| } | |||
| case rule == "Required": | |||
| if reflect.DeepEqual(zero, fieldValue) { | |||
| errors.Add([]string{field.Name}, ERR_REQUIRED, "Required") | |||
| break VALIDATE_RULES | |||
| } | |||
| case rule == "AlphaDash": | |||
| if AlphaDashPattern.MatchString(fmt.Sprintf("%v", fieldValue)) { | |||
| errors.Add([]string{field.Name}, ERR_ALPHA_DASH, "AlphaDash") | |||
| break VALIDATE_RULES | |||
| } | |||
| case rule == "AlphaDashDot": | |||
| if AlphaDashDotPattern.MatchString(fmt.Sprintf("%v", fieldValue)) { | |||
| errors.Add([]string{field.Name}, ERR_ALPHA_DASH_DOT, "AlphaDashDot") | |||
| break VALIDATE_RULES | |||
| } | |||
| case strings.HasPrefix(rule, "Size("): | |||
| size, _ := strconv.Atoi(rule[5 : len(rule)-1]) | |||
| if str, ok := fieldValue.(string); ok && utf8.RuneCountInString(str) != size { | |||
| errors.Add([]string{field.Name}, ERR_SIZE, "Size") | |||
| break VALIDATE_RULES | |||
| } | |||
| v := reflect.ValueOf(fieldValue) | |||
| if v.Kind() == reflect.Slice && v.Len() != size { | |||
| errors.Add([]string{field.Name}, ERR_SIZE, "Size") | |||
| break VALIDATE_RULES | |||
| } | |||
| case strings.HasPrefix(rule, "MinSize("): | |||
| min, _ := strconv.Atoi(rule[8 : len(rule)-1]) | |||
| if str, ok := fieldValue.(string); ok && utf8.RuneCountInString(str) < min { | |||
| errors.Add([]string{field.Name}, ERR_MIN_SIZE, "MinSize") | |||
| break VALIDATE_RULES | |||
| } | |||
| v := reflect.ValueOf(fieldValue) | |||
| if v.Kind() == reflect.Slice && v.Len() < min { | |||
| errors.Add([]string{field.Name}, ERR_MIN_SIZE, "MinSize") | |||
| break VALIDATE_RULES | |||
| } | |||
| case strings.HasPrefix(rule, "MaxSize("): | |||
| max, _ := strconv.Atoi(rule[8 : len(rule)-1]) | |||
| if str, ok := fieldValue.(string); ok && utf8.RuneCountInString(str) > max { | |||
| errors.Add([]string{field.Name}, ERR_MAX_SIZE, "MaxSize") | |||
| break VALIDATE_RULES | |||
| } | |||
| v := reflect.ValueOf(fieldValue) | |||
| if v.Kind() == reflect.Slice && v.Len() > max { | |||
| errors.Add([]string{field.Name}, ERR_MAX_SIZE, "MaxSize") | |||
| break VALIDATE_RULES | |||
| } | |||
| case strings.HasPrefix(rule, "Range("): | |||
| nums := strings.Split(rule[6:len(rule)-1], ",") | |||
| if len(nums) != 2 { | |||
| break VALIDATE_RULES | |||
| } | |||
| val := com.StrTo(fmt.Sprintf("%v", fieldValue)).MustInt() | |||
| if val < com.StrTo(nums[0]).MustInt() || val > com.StrTo(nums[1]).MustInt() { | |||
| errors.Add([]string{field.Name}, ERR_RANGE, "Range") | |||
| break VALIDATE_RULES | |||
| } | |||
| case rule == "Email": | |||
| if !EmailPattern.MatchString(fmt.Sprintf("%v", fieldValue)) { | |||
| errors.Add([]string{field.Name}, ERR_EMAIL, "Email") | |||
| break VALIDATE_RULES | |||
| } | |||
| case rule == "Url": | |||
| str := fmt.Sprintf("%v", fieldValue) | |||
| if len(str) == 0 { | |||
| continue | |||
| } else if !URLPattern.MatchString(str) { | |||
| errors.Add([]string{field.Name}, ERR_URL, "Url") | |||
| break VALIDATE_RULES | |||
| } | |||
| case strings.HasPrefix(rule, "In("): | |||
| if !in(fieldValue, rule[3:len(rule)-1]) { | |||
| errors.Add([]string{field.Name}, ERR_IN, "In") | |||
| break VALIDATE_RULES | |||
| } | |||
| case strings.HasPrefix(rule, "NotIn("): | |||
| if in(fieldValue, rule[6:len(rule)-1]) { | |||
| errors.Add([]string{field.Name}, ERR_NOT_INT, "NotIn") | |||
| break VALIDATE_RULES | |||
| } | |||
| case strings.HasPrefix(rule, "Include("): | |||
| if !strings.Contains(fmt.Sprintf("%v", fieldValue), rule[8:len(rule)-1]) { | |||
| errors.Add([]string{field.Name}, ERR_INCLUDE, "Include") | |||
| break VALIDATE_RULES | |||
| } | |||
| case strings.HasPrefix(rule, "Exclude("): | |||
| if strings.Contains(fmt.Sprintf("%v", fieldValue), rule[8:len(rule)-1]) { | |||
| errors.Add([]string{field.Name}, ERR_EXCLUDE, "Exclude") | |||
| break VALIDATE_RULES | |||
| } | |||
| case strings.HasPrefix(rule, "Default("): | |||
| if reflect.DeepEqual(zero, fieldValue) { | |||
| if fieldVal.CanAddr() { | |||
| setWithProperType(field.Type.Kind(), rule[8:len(rule)-1], fieldVal, field.Tag.Get("form"), errors) | |||
| } else { | |||
| errors.Add([]string{field.Name}, ERR_EXCLUDE, "Default") | |||
| break VALIDATE_RULES | |||
| } | |||
| } | |||
| default: | |||
| // Apply custom validation rules. | |||
| var isValid bool | |||
| for i := range ruleMapper { | |||
| if ruleMapper[i].IsMatch(rule) { | |||
| isValid, errors = ruleMapper[i].IsValid(errors, field.Name, fieldValue) | |||
| if !isValid { | |||
| break VALIDATE_RULES | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| return errors | |||
| } | |||
| // NameMapper represents a form tag name mapper. | |||
| type NameMapper func(string) string | |||
| var ( | |||
| nameMapper = func(field string) string { | |||
| newstr := make([]rune, 0, len(field)) | |||
| for i, chr := range field { | |||
| if isUpper := 'A' <= chr && chr <= 'Z'; isUpper { | |||
| if i > 0 { | |||
| newstr = append(newstr, '_') | |||
| } | |||
| chr -= ('A' - 'a') | |||
| } | |||
| newstr = append(newstr, chr) | |||
| } | |||
| return string(newstr) | |||
| } | |||
| ) | |||
| // SetNameMapper sets name mapper. | |||
| func SetNameMapper(nm NameMapper) { | |||
| nameMapper = nm | |||
| } | |||
| // Takes values from the form data and puts them into a struct | |||
| func mapForm(formStruct reflect.Value, form map[string][]string, | |||
| formfile map[string][]*multipart.FileHeader, errors Errors) { | |||
| if formStruct.Kind() == reflect.Ptr { | |||
| formStruct = formStruct.Elem() | |||
| } | |||
| typ := formStruct.Type() | |||
| for i := 0; i < typ.NumField(); i++ { | |||
| typeField := typ.Field(i) | |||
| structField := formStruct.Field(i) | |||
| if typeField.Type.Kind() == reflect.Ptr && typeField.Anonymous { | |||
| structField.Set(reflect.New(typeField.Type.Elem())) | |||
| mapForm(structField.Elem(), form, formfile, errors) | |||
| if reflect.DeepEqual(structField.Elem().Interface(), reflect.Zero(structField.Elem().Type()).Interface()) { | |||
| structField.Set(reflect.Zero(structField.Type())) | |||
| } | |||
| } else if typeField.Type.Kind() == reflect.Struct { | |||
| mapForm(structField, form, formfile, errors) | |||
| } | |||
| inputFieldName := parseFormName(typeField.Name, typeField.Tag.Get("form")) | |||
| if len(inputFieldName) == 0 || !structField.CanSet() { | |||
| continue | |||
| } | |||
| inputValue, exists := form[inputFieldName] | |||
| if exists { | |||
| numElems := len(inputValue) | |||
| if structField.Kind() == reflect.Slice && numElems > 0 { | |||
| sliceOf := structField.Type().Elem().Kind() | |||
| slice := reflect.MakeSlice(structField.Type(), numElems, numElems) | |||
| for i := 0; i < numElems; i++ { | |||
| setWithProperType(sliceOf, inputValue[i], slice.Index(i), inputFieldName, errors) | |||
| } | |||
| formStruct.Field(i).Set(slice) | |||
| } else { | |||
| setWithProperType(typeField.Type.Kind(), inputValue[0], structField, inputFieldName, errors) | |||
| } | |||
| continue | |||
| } | |||
| inputFile, exists := formfile[inputFieldName] | |||
| if !exists { | |||
| continue | |||
| } | |||
| fhType := reflect.TypeOf((*multipart.FileHeader)(nil)) | |||
| numElems := len(inputFile) | |||
| if structField.Kind() == reflect.Slice && numElems > 0 && structField.Type().Elem() == fhType { | |||
| slice := reflect.MakeSlice(structField.Type(), numElems, numElems) | |||
| for i := 0; i < numElems; i++ { | |||
| slice.Index(i).Set(reflect.ValueOf(inputFile[i])) | |||
| } | |||
| structField.Set(slice) | |||
| } else if structField.Type() == fhType { | |||
| structField.Set(reflect.ValueOf(inputFile[0])) | |||
| } | |||
| } | |||
| } | |||
| // This sets the value in a struct of an indeterminate type to the | |||
| // matching value from the request (via Form middleware) in the | |||
| // same type, so that not all deserialized values have to be strings. | |||
| // Supported types are string, int, float, and bool. | |||
| func setWithProperType(valueKind reflect.Kind, val string, structField reflect.Value, nameInTag string, errors Errors) { | |||
| switch valueKind { | |||
| case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | |||
| if val == "" { | |||
| val = "0" | |||
| } | |||
| intVal, err := strconv.ParseInt(val, 10, 64) | |||
| if err != nil { | |||
| errors.Add([]string{nameInTag}, ERR_INTERGER_TYPE, "Value could not be parsed as integer") | |||
| } else { | |||
| structField.SetInt(intVal) | |||
| } | |||
| case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: | |||
| if val == "" { | |||
| val = "0" | |||
| } | |||
| uintVal, err := strconv.ParseUint(val, 10, 64) | |||
| if err != nil { | |||
| errors.Add([]string{nameInTag}, ERR_INTERGER_TYPE, "Value could not be parsed as unsigned integer") | |||
| } else { | |||
| structField.SetUint(uintVal) | |||
| } | |||
| case reflect.Bool: | |||
| if val == "on" { | |||
| structField.SetBool(true) | |||
| return | |||
| } | |||
| if val == "" { | |||
| val = "false" | |||
| } | |||
| boolVal, err := strconv.ParseBool(val) | |||
| if err != nil { | |||
| errors.Add([]string{nameInTag}, ERR_BOOLEAN_TYPE, "Value could not be parsed as boolean") | |||
| } else if boolVal { | |||
| structField.SetBool(true) | |||
| } | |||
| case reflect.Float32: | |||
| if val == "" { | |||
| val = "0.0" | |||
| } | |||
| floatVal, err := strconv.ParseFloat(val, 32) | |||
| if err != nil { | |||
| errors.Add([]string{nameInTag}, ERR_FLOAT_TYPE, "Value could not be parsed as 32-bit float") | |||
| } else { | |||
| structField.SetFloat(floatVal) | |||
| } | |||
| case reflect.Float64: | |||
| if val == "" { | |||
| val = "0.0" | |||
| } | |||
| floatVal, err := strconv.ParseFloat(val, 64) | |||
| if err != nil { | |||
| errors.Add([]string{nameInTag}, ERR_FLOAT_TYPE, "Value could not be parsed as 64-bit float") | |||
| } else { | |||
| structField.SetFloat(floatVal) | |||
| } | |||
| case reflect.String: | |||
| structField.SetString(val) | |||
| } | |||
| } | |||
| // Don't pass in pointers to bind to. Can lead to bugs. | |||
| func ensureNotPointer(obj interface{}) { | |||
| if reflect.TypeOf(obj).Kind() == reflect.Ptr { | |||
| panic("Pointers are not accepted as binding models") | |||
| } | |||
| } | |||
| // Performs validation and combines errors from validation | |||
| // with errors from deserialization, then maps both the | |||
| // resulting struct and the errors to the context. | |||
| func validateAndMap(obj reflect.Value, ctx *macaron.Context, errors Errors, ifacePtr ...interface{}) { | |||
| ctx.Invoke(Validate(obj.Interface())) | |||
| errors = append(errors, getErrors(ctx)...) | |||
| ctx.Map(errors) | |||
| ctx.Map(obj.Elem().Interface()) | |||
| if len(ifacePtr) > 0 { | |||
| ctx.MapTo(obj.Elem().Interface(), ifacePtr[0]) | |||
| } | |||
| } | |||
| // getErrors simply gets the errors from the context (it's kind of a chore) | |||
| func getErrors(ctx *macaron.Context) Errors { | |||
| return ctx.GetVal(reflect.TypeOf(Errors{})).Interface().(Errors) | |||
| } | |||
| type ( | |||
| // ErrorHandler is the interface that has custom error handling process. | |||
| ErrorHandler interface { | |||
| // Error handles validation errors with custom process. | |||
| Error(*macaron.Context, Errors) | |||
| } | |||
| // Validator is the interface that handles some rudimentary | |||
| // request validation logic so your application doesn't have to. | |||
| Validator interface { | |||
| // Validate validates that the request is OK. It is recommended | |||
| // that validation be limited to checking values for syntax and | |||
| // semantics, enough to know that you can make sense of the request | |||
| // in your application. For example, you might verify that a credit | |||
| // card number matches a valid pattern, but you probably wouldn't | |||
| // perform an actual credit card authorization here. | |||
| Validate(*macaron.Context, Errors) Errors | |||
| } | |||
| ) | |||
| @@ -0,0 +1,159 @@ | |||
| // Copyright 2014 Martini Authors | |||
| // Copyright 2014 The Macaron Authors | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"): you may | |||
| // not use this file except in compliance with the License. You may obtain | |||
| // a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
| // License for the specific language governing permissions and limitations | |||
| // under the License. | |||
| package binding | |||
| const ( | |||
| // Type mismatch errors. | |||
| ERR_CONTENT_TYPE = "ContentTypeError" | |||
| ERR_DESERIALIZATION = "DeserializationError" | |||
| ERR_INTERGER_TYPE = "IntegerTypeError" | |||
| ERR_BOOLEAN_TYPE = "BooleanTypeError" | |||
| ERR_FLOAT_TYPE = "FloatTypeError" | |||
| // Validation errors. | |||
| ERR_REQUIRED = "RequiredError" | |||
| ERR_ALPHA_DASH = "AlphaDashError" | |||
| ERR_ALPHA_DASH_DOT = "AlphaDashDotError" | |||
| ERR_SIZE = "SizeError" | |||
| ERR_MIN_SIZE = "MinSizeError" | |||
| ERR_MAX_SIZE = "MaxSizeError" | |||
| ERR_RANGE = "RangeError" | |||
| ERR_EMAIL = "EmailError" | |||
| ERR_URL = "UrlError" | |||
| ERR_IN = "InError" | |||
| ERR_NOT_INT = "NotInError" | |||
| ERR_INCLUDE = "IncludeError" | |||
| ERR_EXCLUDE = "ExcludeError" | |||
| ERR_DEFAULT = "DefaultError" | |||
| ) | |||
| type ( | |||
| // Errors may be generated during deserialization, binding, | |||
| // or validation. This type is mapped to the context so you | |||
| // can inject it into your own handlers and use it in your | |||
| // application if you want all your errors to look the same. | |||
| Errors []Error | |||
| Error struct { | |||
| // An error supports zero or more field names, because an | |||
| // error can morph three ways: (1) it can indicate something | |||
| // wrong with the request as a whole, (2) it can point to a | |||
| // specific problem with a particular input field, or (3) it | |||
| // can span multiple related input fields. | |||
| FieldNames []string `json:"fieldNames,omitempty"` | |||
| // The classification is like an error code, convenient to | |||
| // use when processing or categorizing an error programmatically. | |||
| // It may also be called the "kind" of error. | |||
| Classification string `json:"classification,omitempty"` | |||
| // Message should be human-readable and detailed enough to | |||
| // pinpoint and resolve the problem, but it should be brief. For | |||
| // example, a payload of 100 objects in a JSON array might have | |||
| // an error in the 41st object. The message should help the | |||
| // end user find and fix the error with their request. | |||
| Message string `json:"message,omitempty"` | |||
| } | |||
| ) | |||
| // Add adds an error associated with the fields indicated | |||
| // by fieldNames, with the given classification and message. | |||
| func (e *Errors) Add(fieldNames []string, classification, message string) { | |||
| *e = append(*e, Error{ | |||
| FieldNames: fieldNames, | |||
| Classification: classification, | |||
| Message: message, | |||
| }) | |||
| } | |||
| // Len returns the number of errors. | |||
| func (e *Errors) Len() int { | |||
| return len(*e) | |||
| } | |||
| // Has determines whether an Errors slice has an Error with | |||
| // a given classification in it; it does not search on messages | |||
| // or field names. | |||
| func (e *Errors) Has(class string) bool { | |||
| for _, err := range *e { | |||
| if err.Kind() == class { | |||
| return true | |||
| } | |||
| } | |||
| return false | |||
| } | |||
| /* | |||
| // WithClass gets a copy of errors that are classified by the | |||
| // the given classification. | |||
| func (e *Errors) WithClass(classification string) Errors { | |||
| var errs Errors | |||
| for _, err := range *e { | |||
| if err.Kind() == classification { | |||
| errs = append(errs, err) | |||
| } | |||
| } | |||
| return errs | |||
| } | |||
| // ForField gets a copy of errors that are associated with the | |||
| // field by the given name. | |||
| func (e *Errors) ForField(name string) Errors { | |||
| var errs Errors | |||
| for _, err := range *e { | |||
| for _, fieldName := range err.Fields() { | |||
| if fieldName == name { | |||
| errs = append(errs, err) | |||
| break | |||
| } | |||
| } | |||
| } | |||
| return errs | |||
| } | |||
| // Get gets errors of a particular class for the specified | |||
| // field name. | |||
| func (e *Errors) Get(class, fieldName string) Errors { | |||
| var errs Errors | |||
| for _, err := range *e { | |||
| if err.Kind() == class { | |||
| for _, nameOfField := range err.Fields() { | |||
| if nameOfField == fieldName { | |||
| errs = append(errs, err) | |||
| break | |||
| } | |||
| } | |||
| } | |||
| } | |||
| return errs | |||
| } | |||
| */ | |||
| // Fields returns the list of field names this error is | |||
| // associated with. | |||
| func (e Error) Fields() []string { | |||
| return e.FieldNames | |||
| } | |||
| // Kind returns this error's classification. | |||
| func (e Error) Kind() string { | |||
| return e.Classification | |||
| } | |||
| // Error returns this error's message. | |||
| func (e Error) Error() string { | |||
| return e.Message | |||
| } | |||
| @@ -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,20 @@ | |||
| # cache [](https://travis-ci.org/go-macaron/cache) [](http://gocover.io/github.com/go-macaron/cache) | |||
| Middleware cache provides cache management for [Macaron](https://github.com/go-macaron/macaron). It can use many cache adapters, including memory, file, Redis, Memcache, PostgreSQL, MySQL, Ledis and Nodb. | |||
| ### Installation | |||
| go get github.com/go-macaron/cache | |||
| ## Getting Help | |||
| - [API Reference](https://gowalker.org/github.com/go-macaron/cache) | |||
| - [Documentation](http://go-macaron.com/docs/middlewares/cache) | |||
| ## Credits | |||
| This package is a modified version of [beego/cache](https://github.com/astaxie/beego/tree/master/cache). | |||
| ## License | |||
| This project is under the Apache License, Version 2.0. See the [LICENSE](LICENSE) file for the full license text. | |||
| @@ -0,0 +1,122 @@ | |||
| // Copyright 2013 Beego Authors | |||
| // Copyright 2014 The Macaron Authors | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"): you may | |||
| // not use this file except in compliance with the License. You may obtain | |||
| // a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
| // License for the specific language governing permissions and limitations | |||
| // under the License. | |||
| // Package cache is a middleware that provides the cache management of Macaron. | |||
| package cache | |||
| import ( | |||
| "fmt" | |||
| "gopkg.in/macaron.v1" | |||
| ) | |||
| const _VERSION = "0.3.0" | |||
| func Version() string { | |||
| return _VERSION | |||
| } | |||
| // Cache is the interface that operates the cache data. | |||
| type Cache interface { | |||
| // Put puts value into cache with key and expire time. | |||
| Put(key string, val interface{}, timeout int64) error | |||
| // Get gets cached value by given key. | |||
| Get(key string) interface{} | |||
| // Delete deletes cached value by given key. | |||
| Delete(key string) error | |||
| // Incr increases cached int-type value by given key as a counter. | |||
| Incr(key string) error | |||
| // Decr decreases cached int-type value by given key as a counter. | |||
| Decr(key string) error | |||
| // IsExist returns true if cached value exists. | |||
| IsExist(key string) bool | |||
| // Flush deletes all cached data. | |||
| Flush() error | |||
| // StartAndGC starts GC routine based on config string settings. | |||
| StartAndGC(opt Options) error | |||
| } | |||
| // Options represents a struct for specifying configuration options for the cache middleware. | |||
| type Options struct { | |||
| // Name of adapter. Default is "memory". | |||
| Adapter string | |||
| // Adapter configuration, it's corresponding to adapter. | |||
| AdapterConfig string | |||
| // GC interval time in seconds. Default is 60. | |||
| Interval int | |||
| // Occupy entire database. Default is false. | |||
| OccupyMode bool | |||
| // Configuration section name. Default is "cache". | |||
| Section string | |||
| } | |||
| func prepareOptions(options []Options) Options { | |||
| var opt Options | |||
| if len(options) > 0 { | |||
| opt = options[0] | |||
| } | |||
| if len(opt.Section) == 0 { | |||
| opt.Section = "cache" | |||
| } | |||
| sec := macaron.Config().Section(opt.Section) | |||
| if len(opt.Adapter) == 0 { | |||
| opt.Adapter = sec.Key("ADAPTER").MustString("memory") | |||
| } | |||
| if opt.Interval == 0 { | |||
| opt.Interval = sec.Key("INTERVAL").MustInt(60) | |||
| } | |||
| if len(opt.AdapterConfig) == 0 { | |||
| opt.AdapterConfig = sec.Key("ADAPTER_CONFIG").MustString("data/caches") | |||
| } | |||
| return opt | |||
| } | |||
| // NewCacher creates and returns a new cacher by given adapter name and configuration. | |||
| // It panics when given adapter isn't registered and starts GC automatically. | |||
| func NewCacher(name string, opt Options) (Cache, error) { | |||
| adapter, ok := adapters[name] | |||
| if !ok { | |||
| return nil, fmt.Errorf("cache: unknown adapter '%s'(forgot to import?)", name) | |||
| } | |||
| return adapter, adapter.StartAndGC(opt) | |||
| } | |||
| // Cacher is a middleware that maps a cache.Cache service into the Macaron handler chain. | |||
| // An single variadic cache.Options struct can be optionally provided to configure. | |||
| func Cacher(options ...Options) macaron.Handler { | |||
| opt := prepareOptions(options) | |||
| cache, err := NewCacher(opt.Adapter, opt) | |||
| if err != nil { | |||
| panic(err) | |||
| } | |||
| return func(ctx *macaron.Context) { | |||
| ctx.Map(cache) | |||
| } | |||
| } | |||
| var adapters = make(map[string]Cache) | |||
| // Register registers a adapter. | |||
| func Register(name string, adapter Cache) { | |||
| if adapter == nil { | |||
| panic("cache: cannot register adapter with nil value") | |||
| } | |||
| if _, dup := adapters[name]; dup { | |||
| panic(fmt.Errorf("cache: cannot register adapter '%s' twice", name)) | |||
| } | |||
| adapters[name] = adapter | |||
| } | |||
| @@ -0,0 +1,208 @@ | |||
| // Copyright 2013 Beego Authors | |||
| // Copyright 2014 The Macaron Authors | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"): you may | |||
| // not use this file except in compliance with the License. You may obtain | |||
| // a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
| // License for the specific language governing permissions and limitations | |||
| // under the License. | |||
| package cache | |||
| import ( | |||
| "crypto/md5" | |||
| "encoding/hex" | |||
| "fmt" | |||
| "io/ioutil" | |||
| "log" | |||
| "os" | |||
| "path/filepath" | |||
| "sync" | |||
| "time" | |||
| "github.com/Unknwon/com" | |||
| "gopkg.in/macaron.v1" | |||
| ) | |||
| // Item represents a cache item. | |||
| type Item struct { | |||
| Val interface{} | |||
| Created int64 | |||
| Expire int64 | |||
| } | |||
| func (item *Item) hasExpired() bool { | |||
| return item.Expire > 0 && | |||
| (time.Now().Unix()-item.Created) >= item.Expire | |||
| } | |||
| // FileCacher represents a file cache adapter implementation. | |||
| type FileCacher struct { | |||
| lock sync.Mutex | |||
| rootPath string | |||
| interval int // GC interval. | |||
| } | |||
| // NewFileCacher creates and returns a new file cacher. | |||
| func NewFileCacher() *FileCacher { | |||
| return &FileCacher{} | |||
| } | |||
| func (c *FileCacher) filepath(key string) string { | |||
| m := md5.Sum([]byte(key)) | |||
| hash := hex.EncodeToString(m[:]) | |||
| return filepath.Join(c.rootPath, string(hash[0]), string(hash[1]), hash) | |||
| } | |||
| // Put puts value into cache with key and expire time. | |||
| // If expired is 0, it will be deleted by next GC operation. | |||
| func (c *FileCacher) Put(key string, val interface{}, expire int64) error { | |||
| filename := c.filepath(key) | |||
| item := &Item{val, time.Now().Unix(), expire} | |||
| data, err := EncodeGob(item) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| os.MkdirAll(filepath.Dir(filename), os.ModePerm) | |||
| return ioutil.WriteFile(filename, data, os.ModePerm) | |||
| } | |||
| func (c *FileCacher) read(key string) (*Item, error) { | |||
| filename := c.filepath(key) | |||
| data, err := ioutil.ReadFile(filename) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| item := new(Item) | |||
| return item, DecodeGob(data, item) | |||
| } | |||
| // Get gets cached value by given key. | |||
| func (c *FileCacher) Get(key string) interface{} { | |||
| item, err := c.read(key) | |||
| if err != nil { | |||
| return nil | |||
| } | |||
| if item.hasExpired() { | |||
| os.Remove(c.filepath(key)) | |||
| return nil | |||
| } | |||
| return item.Val | |||
| } | |||
| // Delete deletes cached value by given key. | |||
| func (c *FileCacher) Delete(key string) error { | |||
| return os.Remove(c.filepath(key)) | |||
| } | |||
| // Incr increases cached int-type value by given key as a counter. | |||
| func (c *FileCacher) Incr(key string) error { | |||
| item, err := c.read(key) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| item.Val, err = Incr(item.Val) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| return c.Put(key, item.Val, item.Expire) | |||
| } | |||
| // Decrease cached int value. | |||
| func (c *FileCacher) Decr(key string) error { | |||
| item, err := c.read(key) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| item.Val, err = Decr(item.Val) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| return c.Put(key, item.Val, item.Expire) | |||
| } | |||
| // IsExist returns true if cached value exists. | |||
| func (c *FileCacher) IsExist(key string) bool { | |||
| return com.IsExist(c.filepath(key)) | |||
| } | |||
| // Flush deletes all cached data. | |||
| func (c *FileCacher) Flush() error { | |||
| return os.RemoveAll(c.rootPath) | |||
| } | |||
| func (c *FileCacher) startGC() { | |||
| c.lock.Lock() | |||
| defer c.lock.Unlock() | |||
| if c.interval < 1 { | |||
| return | |||
| } | |||
| if err := filepath.Walk(c.rootPath, func(path string, fi os.FileInfo, err error) error { | |||
| if err != nil { | |||
| return fmt.Errorf("Walk: %v", err) | |||
| } | |||
| if fi.IsDir() { | |||
| return nil | |||
| } | |||
| data, err := ioutil.ReadFile(path) | |||
| if err != nil && !os.IsNotExist(err) { | |||
| fmt.Errorf("ReadFile: %v", err) | |||
| } | |||
| item := new(Item) | |||
| if err = DecodeGob(data, item); err != nil { | |||
| return err | |||
| } | |||
| if item.hasExpired() { | |||
| if err = os.Remove(path); err != nil && !os.IsNotExist(err) { | |||
| return fmt.Errorf("Remove: %v", err) | |||
| } | |||
| } | |||
| return nil | |||
| }); err != nil { | |||
| log.Printf("error garbage collecting cache files: %v", err) | |||
| } | |||
| time.AfterFunc(time.Duration(c.interval)*time.Second, func() { c.startGC() }) | |||
| } | |||
| // StartAndGC starts GC routine based on config string settings. | |||
| func (c *FileCacher) StartAndGC(opt Options) error { | |||
| c.lock.Lock() | |||
| c.rootPath = opt.AdapterConfig | |||
| c.interval = opt.Interval | |||
| if !filepath.IsAbs(c.rootPath) { | |||
| c.rootPath = filepath.Join(macaron.Root, c.rootPath) | |||
| } | |||
| c.lock.Unlock() | |||
| if err := os.MkdirAll(c.rootPath, os.ModePerm); err != nil { | |||
| return err | |||
| } | |||
| go c.startGC() | |||
| return nil | |||
| } | |||
| func init() { | |||
| Register("file", NewFileCacher()) | |||
| } | |||
| @@ -0,0 +1,92 @@ | |||
| // Copyright 2013 Beego Authors | |||
| // Copyright 2014 The Macaron Authors | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"): you may | |||
| // not use this file except in compliance with the License. You may obtain | |||
| // a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
| // License for the specific language governing permissions and limitations | |||
| // under the License. | |||
| package cache | |||
| import ( | |||
| "strings" | |||
| "github.com/Unknwon/com" | |||
| "github.com/bradfitz/gomemcache/memcache" | |||
| "github.com/go-macaron/cache" | |||
| ) | |||
| // MemcacheCacher represents a memcache cache adapter implementation. | |||
| type MemcacheCacher struct { | |||
| c *memcache.Client | |||
| } | |||
| func NewItem(key string, data []byte, expire int32) *memcache.Item { | |||
| return &memcache.Item{ | |||
| Key: key, | |||
| Value: data, | |||
| Expiration: expire, | |||
| } | |||
| } | |||
| // Put puts value into cache with key and expire time. | |||
| // If expired is 0, it lives forever. | |||
| func (c *MemcacheCacher) Put(key string, val interface{}, expire int64) error { | |||
| return c.c.Set(NewItem(key, []byte(com.ToStr(val)), int32(expire))) | |||
| } | |||
| // Get gets cached value by given key. | |||
| func (c *MemcacheCacher) Get(key string) interface{} { | |||
| item, err := c.c.Get(key) | |||
| if err != nil { | |||
| return nil | |||
| } | |||
| return string(item.Value) | |||
| } | |||
| // Delete deletes cached value by given key. | |||
| func (c *MemcacheCacher) Delete(key string) error { | |||
| return c.c.Delete(key) | |||
| } | |||
| // Incr increases cached int-type value by given key as a counter. | |||
| func (c *MemcacheCacher) Incr(key string) error { | |||
| _, err := c.c.Increment(key, 1) | |||
| return err | |||
| } | |||
| // Decr decreases cached int-type value by given key as a counter. | |||
| func (c *MemcacheCacher) Decr(key string) error { | |||
| _, err := c.c.Decrement(key, 1) | |||
| return err | |||
| } | |||
| // IsExist returns true if cached value exists. | |||
| func (c *MemcacheCacher) IsExist(key string) bool { | |||
| _, err := c.c.Get(key) | |||
| return err == nil | |||
| } | |||
| // Flush deletes all cached data. | |||
| func (c *MemcacheCacher) Flush() error { | |||
| return c.c.FlushAll() | |||
| } | |||
| // StartAndGC starts GC routine based on config string settings. | |||
| // AdapterConfig: 127.0.0.1:9090;127.0.0.1:9091 | |||
| func (c *MemcacheCacher) StartAndGC(opt cache.Options) error { | |||
| c.c = memcache.New(strings.Split(opt.AdapterConfig, ";")...) | |||
| return nil | |||
| } | |||
| func init() { | |||
| cache.Register("memcache", &MemcacheCacher{}) | |||
| } | |||
| @@ -0,0 +1 @@ | |||
| ignore | |||
| @@ -0,0 +1,179 @@ | |||
| // Copyright 2013 Beego Authors | |||
| // Copyright 2014 The Macaron Authors | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"): you may | |||
| // not use this file except in compliance with the License. You may obtain | |||
| // a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
| // License for the specific language governing permissions and limitations | |||
| // under the License. | |||
| package cache | |||
| import ( | |||
| "errors" | |||
| "sync" | |||
| "time" | |||
| ) | |||
| // MemoryItem represents a memory cache item. | |||
| type MemoryItem struct { | |||
| val interface{} | |||
| created int64 | |||
| expire int64 | |||
| } | |||
| func (item *MemoryItem) hasExpired() bool { | |||
| return item.expire > 0 && | |||
| (time.Now().Unix()-item.created) >= item.expire | |||
| } | |||
| // MemoryCacher represents a memory cache adapter implementation. | |||
| type MemoryCacher struct { | |||
| lock sync.RWMutex | |||
| items map[string]*MemoryItem | |||
| interval int // GC interval. | |||
| } | |||
| // NewMemoryCacher creates and returns a new memory cacher. | |||
| func NewMemoryCacher() *MemoryCacher { | |||
| return &MemoryCacher{items: make(map[string]*MemoryItem)} | |||
| } | |||
| // Put puts value into cache with key and expire time. | |||
| // If expired is 0, it will be deleted by next GC operation. | |||
| func (c *MemoryCacher) Put(key string, val interface{}, expire int64) error { | |||
| c.lock.Lock() | |||
| defer c.lock.Unlock() | |||
| c.items[key] = &MemoryItem{ | |||
| val: val, | |||
| created: time.Now().Unix(), | |||
| expire: expire, | |||
| } | |||
| return nil | |||
| } | |||
| // Get gets cached value by given key. | |||
| func (c *MemoryCacher) Get(key string) interface{} { | |||
| c.lock.RLock() | |||
| defer c.lock.RUnlock() | |||
| item, ok := c.items[key] | |||
| if !ok { | |||
| return nil | |||
| } | |||
| if item.hasExpired() { | |||
| go c.Delete(key) | |||
| return nil | |||
| } | |||
| return item.val | |||
| } | |||
| // Delete deletes cached value by given key. | |||
| func (c *MemoryCacher) Delete(key string) error { | |||
| c.lock.Lock() | |||
| defer c.lock.Unlock() | |||
| delete(c.items, key) | |||
| return nil | |||
| } | |||
| // Incr increases cached int-type value by given key as a counter. | |||
| func (c *MemoryCacher) Incr(key string) (err error) { | |||
| c.lock.RLock() | |||
| defer c.lock.RUnlock() | |||
| item, ok := c.items[key] | |||
| if !ok { | |||
| return errors.New("key not exist") | |||
| } | |||
| item.val, err = Incr(item.val) | |||
| return err | |||
| } | |||
| // Decr decreases cached int-type value by given key as a counter. | |||
| func (c *MemoryCacher) Decr(key string) (err error) { | |||
| c.lock.RLock() | |||
| defer c.lock.RUnlock() | |||
| item, ok := c.items[key] | |||
| if !ok { | |||
| return errors.New("key not exist") | |||
| } | |||
| item.val, err = Decr(item.val) | |||
| return err | |||
| } | |||
| // IsExist returns true if cached value exists. | |||
| func (c *MemoryCacher) IsExist(key string) bool { | |||
| c.lock.RLock() | |||
| defer c.lock.RUnlock() | |||
| _, ok := c.items[key] | |||
| return ok | |||
| } | |||
| // Flush deletes all cached data. | |||
| func (c *MemoryCacher) Flush() error { | |||
| c.lock.Lock() | |||
| defer c.lock.Unlock() | |||
| c.items = make(map[string]*MemoryItem) | |||
| return nil | |||
| } | |||
| func (c *MemoryCacher) checkRawExpiration(key string) { | |||
| item, ok := c.items[key] | |||
| if !ok { | |||
| return | |||
| } | |||
| if item.hasExpired() { | |||
| delete(c.items, key) | |||
| } | |||
| } | |||
| func (c *MemoryCacher) checkExpiration(key string) { | |||
| c.lock.Lock() | |||
| defer c.lock.Unlock() | |||
| c.checkRawExpiration(key) | |||
| } | |||
| func (c *MemoryCacher) startGC() { | |||
| c.lock.Lock() | |||
| defer c.lock.Unlock() | |||
| if c.interval < 1 { | |||
| return | |||
| } | |||
| if c.items != nil { | |||
| for key, _ := range c.items { | |||
| c.checkRawExpiration(key) | |||
| } | |||
| } | |||
| time.AfterFunc(time.Duration(c.interval)*time.Second, func() { c.startGC() }) | |||
| } | |||
| // StartAndGC starts GC routine based on config string settings. | |||
| func (c *MemoryCacher) StartAndGC(opt Options) error { | |||
| c.lock.Lock() | |||
| c.interval = opt.Interval | |||
| c.lock.Unlock() | |||
| go c.startGC() | |||
| return nil | |||
| } | |||
| func init() { | |||
| Register("memory", NewMemoryCacher()) | |||
| } | |||
| @@ -0,0 +1,178 @@ | |||
| // Copyright 2013 Beego Authors | |||
| // Copyright 2014 The Macaron Authors | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"): you may | |||
| // not use this file except in compliance with the License. You may obtain | |||
| // a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
| // License for the specific language governing permissions and limitations | |||
| // under the License. | |||
| package cache | |||
| import ( | |||
| "fmt" | |||
| "strings" | |||
| "time" | |||
| "github.com/Unknwon/com" | |||
| "gopkg.in/ini.v1" | |||
| "gopkg.in/redis.v2" | |||
| "github.com/go-macaron/cache" | |||
| ) | |||
| // RedisCacher represents a redis cache adapter implementation. | |||
| type RedisCacher struct { | |||
| c *redis.Client | |||
| prefix string | |||
| hsetName string | |||
| occupyMode bool | |||
| } | |||
| // Put puts value into cache with key and expire time. | |||
| // If expired is 0, it lives forever. | |||
| func (c *RedisCacher) Put(key string, val interface{}, expire int64) error { | |||
| key = c.prefix + key | |||
| if expire == 0 { | |||
| if err := c.c.Set(key, com.ToStr(val)).Err(); err != nil { | |||
| return err | |||
| } | |||
| } else { | |||
| dur, err := time.ParseDuration(com.ToStr(expire) + "s") | |||
| if err != nil { | |||
| return err | |||
| } | |||
| if err = c.c.SetEx(key, dur, com.ToStr(val)).Err(); err != nil { | |||
| return err | |||
| } | |||
| } | |||
| if c.occupyMode { | |||
| return nil | |||
| } | |||
| return c.c.HSet(c.hsetName, key, "0").Err() | |||
| } | |||
| // Get gets cached value by given key. | |||
| func (c *RedisCacher) Get(key string) interface{} { | |||
| val, err := c.c.Get(c.prefix + key).Result() | |||
| if err != nil { | |||
| return nil | |||
| } | |||
| return val | |||
| } | |||
| // Delete deletes cached value by given key. | |||
| func (c *RedisCacher) Delete(key string) error { | |||
| key = c.prefix + key | |||
| if err := c.c.Del(key).Err(); err != nil { | |||
| return err | |||
| } | |||
| if c.occupyMode { | |||
| return nil | |||
| } | |||
| return c.c.HDel(c.hsetName, key).Err() | |||
| } | |||
| // Incr increases cached int-type value by given key as a counter. | |||
| func (c *RedisCacher) Incr(key string) error { | |||
| if !c.IsExist(key) { | |||
| return fmt.Errorf("key '%s' not exist", key) | |||
| } | |||
| return c.c.Incr(c.prefix + key).Err() | |||
| } | |||
| // Decr decreases cached int-type value by given key as a counter. | |||
| func (c *RedisCacher) Decr(key string) error { | |||
| if !c.IsExist(key) { | |||
| return fmt.Errorf("key '%s' not exist", key) | |||
| } | |||
| return c.c.Decr(c.prefix + key).Err() | |||
| } | |||
| // IsExist returns true if cached value exists. | |||
| func (c *RedisCacher) IsExist(key string) bool { | |||
| if c.c.Exists(c.prefix + key).Val() { | |||
| return true | |||
| } | |||
| if !c.occupyMode { | |||
| c.c.HDel(c.hsetName, c.prefix+key) | |||
| } | |||
| return false | |||
| } | |||
| // Flush deletes all cached data. | |||
| func (c *RedisCacher) Flush() error { | |||
| if c.occupyMode { | |||
| return c.c.FlushDb().Err() | |||
| } | |||
| keys, err := c.c.HKeys(c.hsetName).Result() | |||
| if err != nil { | |||
| return err | |||
| } | |||
| if err = c.c.Del(keys...).Err(); err != nil { | |||
| return err | |||
| } | |||
| return c.c.Del(c.hsetName).Err() | |||
| } | |||
| // StartAndGC starts GC routine based on config string settings. | |||
| // AdapterConfig: network=tcp,addr=:6379,password=macaron,db=0,pool_size=100,idle_timeout=180,hset_name=MacaronCache,prefix=cache: | |||
| func (c *RedisCacher) StartAndGC(opts cache.Options) error { | |||
| c.hsetName = "MacaronCache" | |||
| c.occupyMode = opts.OccupyMode | |||
| cfg, err := ini.Load([]byte(strings.Replace(opts.AdapterConfig, ",", "\n", -1))) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| opt := &redis.Options{ | |||
| Network: "tcp", | |||
| } | |||
| for k, v := range cfg.Section("").KeysHash() { | |||
| switch k { | |||
| case "network": | |||
| opt.Network = v | |||
| case "addr": | |||
| opt.Addr = v | |||
| case "password": | |||
| opt.Password = v | |||
| case "db": | |||
| opt.DB = com.StrTo(v).MustInt64() | |||
| case "pool_size": | |||
| opt.PoolSize = com.StrTo(v).MustInt() | |||
| case "idle_timeout": | |||
| opt.IdleTimeout, err = time.ParseDuration(v + "s") | |||
| if err != nil { | |||
| return fmt.Errorf("error parsing idle timeout: %v", err) | |||
| } | |||
| case "hset_name": | |||
| c.hsetName = v | |||
| case "prefix": | |||
| c.prefix = v | |||
| default: | |||
| return fmt.Errorf("session/redis: unsupported option '%s'", k) | |||
| } | |||
| } | |||
| c.c = redis.NewClient(opt) | |||
| if err = c.c.Ping().Err(); err != nil { | |||
| return err | |||
| } | |||
| return nil | |||
| } | |||
| func init() { | |||
| cache.Register("redis", &RedisCacher{}) | |||
| } | |||
| @@ -0,0 +1 @@ | |||
| ignore | |||
| @@ -0,0 +1,84 @@ | |||
| // Copyright 2014 The Macaron Authors | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"): you may | |||
| // not use this file except in compliance with the License. You may obtain | |||
| // a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
| // License for the specific language governing permissions and limitations | |||
| // under the License. | |||
| package cache | |||
| import ( | |||
| "bytes" | |||
| "encoding/gob" | |||
| "errors" | |||
| ) | |||
| func EncodeGob(item *Item) ([]byte, error) { | |||
| buf := bytes.NewBuffer(nil) | |||
| err := gob.NewEncoder(buf).Encode(item) | |||
| return buf.Bytes(), err | |||
| } | |||
| func DecodeGob(data []byte, out *Item) error { | |||
| buf := bytes.NewBuffer(data) | |||
| return gob.NewDecoder(buf).Decode(&out) | |||
| } | |||
| func Incr(val interface{}) (interface{}, error) { | |||
| switch val.(type) { | |||
| case int: | |||
| val = val.(int) + 1 | |||
| case int32: | |||
| val = val.(int32) + 1 | |||
| case int64: | |||
| val = val.(int64) + 1 | |||
| case uint: | |||
| val = val.(uint) + 1 | |||
| case uint32: | |||
| val = val.(uint32) + 1 | |||
| case uint64: | |||
| val = val.(uint64) + 1 | |||
| default: | |||
| return val, errors.New("item value is not int-type") | |||
| } | |||
| return val, nil | |||
| } | |||
| func Decr(val interface{}) (interface{}, error) { | |||
| switch val.(type) { | |||
| case int: | |||
| val = val.(int) - 1 | |||
| case int32: | |||
| val = val.(int32) - 1 | |||
| case int64: | |||
| val = val.(int64) - 1 | |||
| case uint: | |||
| if val.(uint) > 0 { | |||
| val = val.(uint) - 1 | |||
| } else { | |||
| return val, errors.New("item value is less than 0") | |||
| } | |||
| case uint32: | |||
| if val.(uint32) > 0 { | |||
| val = val.(uint32) - 1 | |||
| } else { | |||
| return val, errors.New("item value is less than 0") | |||
| } | |||
| case uint64: | |||
| if val.(uint64) > 0 { | |||
| val = val.(uint64) - 1 | |||
| } else { | |||
| return val, errors.New("item value is less than 0") | |||
| } | |||
| default: | |||
| return val, errors.New("item value is not int-type") | |||
| } | |||
| return val, nil | |||
| } | |||
| @@ -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,16 @@ | |||
| # captcha [](https://travis-ci.org/go-macaron/captcha) | |||
| Middleware captcha provides captcha service for [Macaron](https://github.com/go-macaron/macaron). | |||
| ### Installation | |||
| go get github.com/go-macaron/captcha | |||
| ## Getting Help | |||
| - [API Reference](https://gowalker.org/github.com/go-macaron/captcha) | |||
| - [Documentation](http://go-macaron.com/docs/middlewares/captcha) | |||
| ## License | |||
| This project is under the Apache License, Version 2.0. See the [LICENSE](LICENSE) file for the full license text. | |||
| @@ -0,0 +1,241 @@ | |||
| // Copyright 2013 Beego Authors | |||
| // Copyright 2014 The Macaron Authors | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"): you may | |||
| // not use this file except in compliance with the License. You may obtain | |||
| // a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
| // License for the specific language governing permissions and limitations | |||
| // under the License. | |||
| // Package captcha a middleware that provides captcha service for Macaron. | |||
| package captcha | |||
| import ( | |||
| "fmt" | |||
| "html/template" | |||
| "path" | |||
| "strings" | |||
| "github.com/Unknwon/com" | |||
| "github.com/go-macaron/cache" | |||
| "gopkg.in/macaron.v1" | |||
| ) | |||
| const _VERSION = "0.1.0" | |||
| func Version() string { | |||
| return _VERSION | |||
| } | |||
| var ( | |||
| defaultChars = []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} | |||
| ) | |||
| // Captcha represents a captcha service. | |||
| type Captcha struct { | |||
| store cache.Cache | |||
| SubURL string | |||
| URLPrefix string | |||
| FieldIdName string | |||
| FieldCaptchaName string | |||
| StdWidth int | |||
| StdHeight int | |||
| ChallengeNums int | |||
| Expiration int64 | |||
| CachePrefix string | |||
| } | |||
| // generate key string | |||
| func (c *Captcha) key(id string) string { | |||
| return c.CachePrefix + id | |||
| } | |||
| // generate rand chars with default chars | |||
| func (c *Captcha) genRandChars() string { | |||
| return string(com.RandomCreateBytes(c.ChallengeNums, defaultChars...)) | |||
| } | |||
| // tempalte func for output html | |||
| func (c *Captcha) CreateHtml() template.HTML { | |||
| value, err := c.CreateCaptcha() | |||
| if err != nil { | |||
| panic(fmt.Errorf("fail to create captcha: %v", err)) | |||
| } | |||
| return template.HTML(fmt.Sprintf(`<input type="hidden" name="%s" value="%s"> | |||
| <a class="captcha" href="javascript:"> | |||
| <img onclick="this.src=('%s%s%s.png?reload='+(new Date()).getTime())" class="captcha-img" src="%s%s%s.png"> | |||
| </a>`, c.FieldIdName, value, c.SubURL, c.URLPrefix, value, c.SubURL, c.URLPrefix, value)) | |||
| } | |||
| // create a new captcha id | |||
| func (c *Captcha) CreateCaptcha() (string, error) { | |||
| id := string(com.RandomCreateBytes(15)) | |||
| if err := c.store.Put(c.key(id), c.genRandChars(), c.Expiration); err != nil { | |||
| return "", err | |||
| } | |||
| return id, nil | |||
| } | |||
| // verify from a request | |||
| func (c *Captcha) VerifyReq(req macaron.Request) bool { | |||
| req.ParseForm() | |||
| return c.Verify(req.Form.Get(c.FieldIdName), req.Form.Get(c.FieldCaptchaName)) | |||
| } | |||
| // direct verify id and challenge string | |||
| func (c *Captcha) Verify(id string, challenge string) bool { | |||
| if len(challenge) == 0 || len(id) == 0 { | |||
| return false | |||
| } | |||
| var chars string | |||
| key := c.key(id) | |||
| if v, ok := c.store.Get(key).(string); ok { | |||
| chars = v | |||
| } else { | |||
| return false | |||
| } | |||
| defer c.store.Delete(key) | |||
| if len(chars) != len(challenge) { | |||
| return false | |||
| } | |||
| // verify challenge | |||
| for i, c := range []byte(chars) { | |||
| if c != challenge[i]-48 { | |||
| return false | |||
| } | |||
| } | |||
| return true | |||
| } | |||
| type Options struct { | |||
| // Suburl path. Default is empty. | |||
| SubURL string | |||
| // URL prefix of getting captcha pictures. Default is "/captcha/". | |||
| URLPrefix string | |||
| // Hidden input element ID. Default is "captcha_id". | |||
| FieldIdName string | |||
| // User input value element name in request form. Default is "captcha". | |||
| FieldCaptchaName string | |||
| // Challenge number. Default is 6. | |||
| ChallengeNums int | |||
| // Captcha image width. Default is 240. | |||
| Width int | |||
| // Captcha image height. Default is 80. | |||
| Height int | |||
| // Captcha expiration time in seconds. Default is 600. | |||
| Expiration int64 | |||
| // Cache key prefix captcha characters. Default is "captcha_". | |||
| CachePrefix string | |||
| } | |||
| func prepareOptions(options []Options) Options { | |||
| var opt Options | |||
| if len(options) > 0 { | |||
| opt = options[0] | |||
| } | |||
| opt.SubURL = strings.TrimSuffix(opt.SubURL, "/") | |||
| // Defaults. | |||
| if len(opt.URLPrefix) == 0 { | |||
| opt.URLPrefix = "/captcha/" | |||
| } else if opt.URLPrefix[len(opt.URLPrefix)-1] != '/' { | |||
| opt.URLPrefix += "/" | |||
| } | |||
| if len(opt.FieldIdName) == 0 { | |||
| opt.FieldIdName = "captcha_id" | |||
| } | |||
| if len(opt.FieldCaptchaName) == 0 { | |||
| opt.FieldCaptchaName = "captcha" | |||
| } | |||
| if opt.ChallengeNums == 0 { | |||
| opt.ChallengeNums = 6 | |||
| } | |||
| if opt.Width == 0 { | |||
| opt.Width = stdWidth | |||
| } | |||
| if opt.Height == 0 { | |||
| opt.Height = stdHeight | |||
| } | |||
| if opt.Expiration == 0 { | |||
| opt.Expiration = 600 | |||
| } | |||
| if len(opt.CachePrefix) == 0 { | |||
| opt.CachePrefix = "captcha_" | |||
| } | |||
| return opt | |||
| } | |||
| // NewCaptcha initializes and returns a captcha with given options. | |||
| func NewCaptcha(opt Options) *Captcha { | |||
| return &Captcha{ | |||
| SubURL: opt.SubURL, | |||
| URLPrefix: opt.URLPrefix, | |||
| FieldIdName: opt.FieldIdName, | |||
| FieldCaptchaName: opt.FieldCaptchaName, | |||
| StdWidth: opt.Width, | |||
| StdHeight: opt.Height, | |||
| ChallengeNums: opt.ChallengeNums, | |||
| Expiration: opt.Expiration, | |||
| CachePrefix: opt.CachePrefix, | |||
| } | |||
| } | |||
| // Captchaer is a middleware that maps a captcha.Captcha service into the Macaron handler chain. | |||
| // An single variadic captcha.Options struct can be optionally provided to configure. | |||
| // This should be register after cache.Cacher. | |||
| func Captchaer(options ...Options) macaron.Handler { | |||
| return func(ctx *macaron.Context, cache cache.Cache) { | |||
| cpt := NewCaptcha(prepareOptions(options)) | |||
| cpt.store = cache | |||
| if strings.HasPrefix(ctx.Req.URL.Path, cpt.URLPrefix) { | |||
| var chars string | |||
| id := path.Base(ctx.Req.URL.Path) | |||
| if i := strings.Index(id, "."); i > -1 { | |||
| id = id[:i] | |||
| } | |||
| key := cpt.key(id) | |||
| // Reload captcha. | |||
| if len(ctx.Query("reload")) > 0 { | |||
| chars = cpt.genRandChars() | |||
| if err := cpt.store.Put(key, chars, cpt.Expiration); err != nil { | |||
| ctx.Status(500) | |||
| ctx.Write([]byte("captcha reload error")) | |||
| panic(fmt.Errorf("reload captcha: %v", err)) | |||
| } | |||
| } else { | |||
| if v, ok := cpt.store.Get(key).(string); ok { | |||
| chars = v | |||
| } else { | |||
| ctx.Status(404) | |||
| ctx.Write([]byte("captcha not found")) | |||
| return | |||
| } | |||
| } | |||
| if _, err := NewImage([]byte(chars), cpt.StdWidth, cpt.StdHeight).WriteTo(ctx.Resp); err != nil { | |||
| panic(fmt.Errorf("write captcha: %v", err)) | |||
| } | |||
| return | |||
| } | |||
| ctx.Data["Captcha"] = cpt | |||
| ctx.Map(cpt) | |||
| } | |||
| } | |||
| @@ -0,0 +1,498 @@ | |||
| // Copyright 2013 Beego Authors | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"): you may | |||
| // not use this file except in compliance with the License. You may obtain | |||
| // a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
| // License for the specific language governing permissions and limitations | |||
| // under the License. | |||
| package captcha | |||
| import ( | |||
| "bytes" | |||
| "image" | |||
| "image/color" | |||
| "image/png" | |||
| "io" | |||
| "math" | |||
| ) | |||
| const ( | |||
| fontWidth = 11 | |||
| fontHeight = 18 | |||
| blackChar = 1 | |||
| // Standard width and height of a captcha image. | |||
| stdWidth = 240 | |||
| stdHeight = 80 | |||
| // Maximum absolute skew factor of a single digit. | |||
| maxSkew = 0.7 | |||
| // Number of background circles. | |||
| circleCount = 20 | |||
| ) | |||
| var font = [][]byte{ | |||
| { // 0 | |||
| 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, | |||
| 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, | |||
| 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, | |||
| 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, | |||
| 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, | |||
| 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, | |||
| 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, | |||
| 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, | |||
| 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, | |||
| 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, | |||
| 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, | |||
| 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, | |||
| 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, | |||
| 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, | |||
| 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, | |||
| 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, | |||
| 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, | |||
| 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, | |||
| }, | |||
| { // 1 | |||
| 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, | |||
| 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, | |||
| 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, | |||
| 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, | |||
| 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, | |||
| 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, | |||
| 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, | |||
| 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, | |||
| 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, | |||
| 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, | |||
| 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, | |||
| 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, | |||
| 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, | |||
| 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, | |||
| 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, | |||
| 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, | |||
| 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, | |||
| 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, | |||
| }, | |||
| { // 2 | |||
| 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, | |||
| 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, | |||
| 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, | |||
| 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, | |||
| 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, | |||
| 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, | |||
| 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, | |||
| 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, | |||
| 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, | |||
| 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, | |||
| 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, | |||
| 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, | |||
| 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, | |||
| 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, | |||
| 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, | |||
| 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, | |||
| 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, | |||
| 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, | |||
| }, | |||
| { // 3 | |||
| 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, | |||
| 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, | |||
| 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, | |||
| 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, | |||
| 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, | |||
| 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, | |||
| 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, | |||
| 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, | |||
| 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, | |||
| 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, | |||
| 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, | |||
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, | |||
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, | |||
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, | |||
| 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, | |||
| 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, | |||
| 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, | |||
| 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, | |||
| }, | |||
| { // 4 | |||
| 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, | |||
| 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, | |||
| 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, | |||
| 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, | |||
| 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, | |||
| 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, | |||
| 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, | |||
| 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, | |||
| 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, | |||
| 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, | |||
| 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, | |||
| 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, | |||
| 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, | |||
| 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, | |||
| 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, | |||
| 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, | |||
| 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, | |||
| 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, | |||
| }, | |||
| { // 5 | |||
| 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, | |||
| 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, | |||
| 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, | |||
| 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, | |||
| 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, | |||
| 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, | |||
| 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, | |||
| 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, | |||
| 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, | |||
| 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, | |||
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, | |||
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, | |||
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, | |||
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, | |||
| 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, | |||
| 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, | |||
| 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, | |||
| 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, | |||
| }, | |||
| { // 6 | |||
| 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, | |||
| 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, | |||
| 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, | |||
| 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, | |||
| 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, | |||
| 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |||
| 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, | |||
| 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, | |||
| 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, | |||
| 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, | |||
| 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, | |||
| 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, | |||
| 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, | |||
| 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, | |||
| 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, | |||
| 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, | |||
| 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, | |||
| 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, | |||
| }, | |||
| { // 7 | |||
| 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, | |||
| 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, | |||
| 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, | |||
| 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, | |||
| 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, | |||
| 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, | |||
| 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, | |||
| 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, | |||
| 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, | |||
| 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, | |||
| 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, | |||
| 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, | |||
| 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, | |||
| 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, | |||
| 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, | |||
| 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, | |||
| 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, | |||
| 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, | |||
| }, | |||
| { // 8 | |||
| 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, | |||
| 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, | |||
| 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, | |||
| 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, | |||
| 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, | |||
| 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, | |||
| 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, | |||
| 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, | |||
| 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, | |||
| 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, | |||
| 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, | |||
| 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, | |||
| 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, | |||
| 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, | |||
| 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, | |||
| 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, | |||
| 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, | |||
| 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, | |||
| }, | |||
| { // 9 | |||
| 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, | |||
| 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, | |||
| 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, | |||
| 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, | |||
| 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, | |||
| 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, | |||
| 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, | |||
| 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, | |||
| 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, | |||
| 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, | |||
| 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, | |||
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, | |||
| 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, | |||
| 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, | |||
| 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, | |||
| 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, | |||
| 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, | |||
| 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, | |||
| }, | |||
| } | |||
| type Image struct { | |||
| *image.Paletted | |||
| numWidth int | |||
| numHeight int | |||
| dotSize int | |||
| } | |||
| var prng = &siprng{} | |||
| // randIntn returns a pseudorandom non-negative int in range [0, n). | |||
| func randIntn(n int) int { | |||
| return prng.Intn(n) | |||
| } | |||
| // randInt returns a pseudorandom int in range [from, to]. | |||
| func randInt(from, to int) int { | |||
| return prng.Intn(to+1-from) + from | |||
| } | |||
| // randFloat returns a pseudorandom float64 in range [from, to]. | |||
| func randFloat(from, to float64) float64 { | |||
| return (to-from)*prng.Float64() + from | |||
| } | |||
| func randomPalette() color.Palette { | |||
| p := make([]color.Color, circleCount+1) | |||
| // Transparent color. | |||
| p[0] = color.RGBA{0xFF, 0xFF, 0xFF, 0x00} | |||
| // Primary color. | |||
| prim := color.RGBA{ | |||
| uint8(randIntn(129)), | |||
| uint8(randIntn(129)), | |||
| uint8(randIntn(129)), | |||
| 0xFF, | |||
| } | |||
| p[1] = prim | |||
| // Circle colors. | |||
| for i := 2; i <= circleCount; i++ { | |||
| p[i] = randomBrightness(prim, 255) | |||
| } | |||
| return p | |||
| } | |||
| // NewImage returns a new captcha image of the given width and height with the | |||
| // given digits, where each digit must be in range 0-9. | |||
| func NewImage(digits []byte, width, height int) *Image { | |||
| m := new(Image) | |||
| m.Paletted = image.NewPaletted(image.Rect(0, 0, width, height), randomPalette()) | |||
| m.calculateSizes(width, height, len(digits)) | |||
| // Randomly position captcha inside the image. | |||
| maxx := width - (m.numWidth+m.dotSize)*len(digits) - m.dotSize | |||
| maxy := height - m.numHeight - m.dotSize*2 | |||
| var border int | |||
| if width > height { | |||
| border = height / 5 | |||
| } else { | |||
| border = width / 5 | |||
| } | |||
| x := randInt(border, maxx-border) | |||
| y := randInt(border, maxy-border) | |||
| // Draw digits. | |||
| for _, n := range digits { | |||
| m.drawDigit(font[n], x, y) | |||
| x += m.numWidth + m.dotSize | |||
| } | |||
| // Draw strike-through line. | |||
| m.strikeThrough() | |||
| // Apply wave distortion. | |||
| m.distort(randFloat(5, 10), randFloat(100, 200)) | |||
| // Fill image with random circles. | |||
| m.fillWithCircles(circleCount, m.dotSize) | |||
| return m | |||
| } | |||
| // encodedPNG encodes an image to PNG and returns | |||
| // the result as a byte slice. | |||
| func (m *Image) encodedPNG() []byte { | |||
| var buf bytes.Buffer | |||
| if err := png.Encode(&buf, m.Paletted); err != nil { | |||
| panic(err.Error()) | |||
| } | |||
| return buf.Bytes() | |||
| } | |||
| // WriteTo writes captcha image in PNG format into the given writer. | |||
| func (m *Image) WriteTo(w io.Writer) (int64, error) { | |||
| n, err := w.Write(m.encodedPNG()) | |||
| return int64(n), err | |||
| } | |||
| func (m *Image) calculateSizes(width, height, ncount int) { | |||
| // Goal: fit all digits inside the image. | |||
| var border int | |||
| if width > height { | |||
| border = height / 4 | |||
| } else { | |||
| border = width / 4 | |||
| } | |||
| // Convert everything to floats for calculations. | |||
| w := float64(width - border*2) | |||
| h := float64(height - border*2) | |||
| // fw takes into account 1-dot spacing between digits. | |||
| fw := float64(fontWidth + 1) | |||
| fh := float64(fontHeight) | |||
| nc := float64(ncount) | |||
| // Calculate the width of a single digit taking into account only the | |||
| // width of the image. | |||
| nw := w / nc | |||
| // Calculate the height of a digit from this width. | |||
| nh := nw * fh / fw | |||
| // Digit too high? | |||
| if nh > h { | |||
| // Fit digits based on height. | |||
| nh = h | |||
| nw = fw / fh * nh | |||
| } | |||
| // Calculate dot size. | |||
| m.dotSize = int(nh / fh) | |||
| // Save everything, making the actual width smaller by 1 dot to account | |||
| // for spacing between digits. | |||
| m.numWidth = int(nw) - m.dotSize | |||
| m.numHeight = int(nh) | |||
| } | |||
| func (m *Image) drawHorizLine(fromX, toX, y int, colorIdx uint8) { | |||
| for x := fromX; x <= toX; x++ { | |||
| m.SetColorIndex(x, y, colorIdx) | |||
| } | |||
| } | |||
| func (m *Image) drawCircle(x, y, radius int, colorIdx uint8) { | |||
| f := 1 - radius | |||
| dfx := 1 | |||
| dfy := -2 * radius | |||
| xo := 0 | |||
| yo := radius | |||
| m.SetColorIndex(x, y+radius, colorIdx) | |||
| m.SetColorIndex(x, y-radius, colorIdx) | |||
| m.drawHorizLine(x-radius, x+radius, y, colorIdx) | |||
| for xo < yo { | |||
| if f >= 0 { | |||
| yo-- | |||
| dfy += 2 | |||
| f += dfy | |||
| } | |||
| xo++ | |||
| dfx += 2 | |||
| f += dfx | |||
| m.drawHorizLine(x-xo, x+xo, y+yo, colorIdx) | |||
| m.drawHorizLine(x-xo, x+xo, y-yo, colorIdx) | |||
| m.drawHorizLine(x-yo, x+yo, y+xo, colorIdx) | |||
| m.drawHorizLine(x-yo, x+yo, y-xo, colorIdx) | |||
| } | |||
| } | |||
| func (m *Image) fillWithCircles(n, maxradius int) { | |||
| maxx := m.Bounds().Max.X | |||
| maxy := m.Bounds().Max.Y | |||
| for i := 0; i < n; i++ { | |||
| colorIdx := uint8(randInt(1, circleCount-1)) | |||
| r := randInt(1, maxradius) | |||
| m.drawCircle(randInt(r, maxx-r), randInt(r, maxy-r), r, colorIdx) | |||
| } | |||
| } | |||
| func (m *Image) strikeThrough() { | |||
| maxx := m.Bounds().Max.X | |||
| maxy := m.Bounds().Max.Y | |||
| y := randInt(maxy/3, maxy-maxy/3) | |||
| amplitude := randFloat(5, 20) | |||
| period := randFloat(80, 180) | |||
| dx := 2.0 * math.Pi / period | |||
| for x := 0; x < maxx; x++ { | |||
| xo := amplitude * math.Cos(float64(y)*dx) | |||
| yo := amplitude * math.Sin(float64(x)*dx) | |||
| for yn := 0; yn < m.dotSize; yn++ { | |||
| r := randInt(0, m.dotSize) | |||
| m.drawCircle(x+int(xo), y+int(yo)+(yn*m.dotSize), r/2, 1) | |||
| } | |||
| } | |||
| } | |||
| func (m *Image) drawDigit(digit []byte, x, y int) { | |||
| skf := randFloat(-maxSkew, maxSkew) | |||
| xs := float64(x) | |||
| r := m.dotSize / 2 | |||
| y += randInt(-r, r) | |||
| for yo := 0; yo < fontHeight; yo++ { | |||
| for xo := 0; xo < fontWidth; xo++ { | |||
| if digit[yo*fontWidth+xo] != blackChar { | |||
| continue | |||
| } | |||
| m.drawCircle(x+xo*m.dotSize, y+yo*m.dotSize, r, 1) | |||
| } | |||
| xs += skf | |||
| x = int(xs) | |||
| } | |||
| } | |||
| func (m *Image) distort(amplude float64, period float64) { | |||
| w := m.Bounds().Max.X | |||
| h := m.Bounds().Max.Y | |||
| oldm := m.Paletted | |||
| newm := image.NewPaletted(image.Rect(0, 0, w, h), oldm.Palette) | |||
| dx := 2.0 * math.Pi / period | |||
| for x := 0; x < w; x++ { | |||
| for y := 0; y < h; y++ { | |||
| xo := amplude * math.Sin(float64(y)*dx) | |||
| yo := amplude * math.Cos(float64(x)*dx) | |||
| newm.SetColorIndex(x, y, oldm.ColorIndexAt(x+int(xo), y+int(yo))) | |||
| } | |||
| } | |||
| m.Paletted = newm | |||
| } | |||
| func randomBrightness(c color.RGBA, max uint8) color.RGBA { | |||
| minc := min3(c.R, c.G, c.B) | |||
| maxc := max3(c.R, c.G, c.B) | |||
| if maxc > max { | |||
| return c | |||
| } | |||
| n := randIntn(int(max-maxc)) - int(minc) | |||
| return color.RGBA{ | |||
| uint8(int(c.R) + n), | |||
| uint8(int(c.G) + n), | |||
| uint8(int(c.B) + n), | |||
| uint8(c.A), | |||
| } | |||
| } | |||
| func min3(x, y, z uint8) (m uint8) { | |||
| m = x | |||
| if y < m { | |||
| m = y | |||
| } | |||
| if z < m { | |||
| m = z | |||
| } | |||
| return | |||
| } | |||
| func max3(x, y, z uint8) (m uint8) { | |||
| m = x | |||
| if y > m { | |||
| m = y | |||
| } | |||
| if z > m { | |||
| m = z | |||
| } | |||
| return | |||
| } | |||
| @@ -0,0 +1,277 @@ | |||
| // Copyright 2013 Beego Authors | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"): you may | |||
| // not use this file except in compliance with the License. You may obtain | |||
| // a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
| // License for the specific language governing permissions and limitations | |||
| // under the License. | |||
| package captcha | |||
| import ( | |||
| "crypto/rand" | |||
| "encoding/binary" | |||
| "io" | |||
| "sync" | |||
| ) | |||
| // siprng is PRNG based on SipHash-2-4. | |||
| type siprng struct { | |||
| mu sync.Mutex | |||
| k0, k1, ctr uint64 | |||
| } | |||
| // siphash implements SipHash-2-4, accepting a uint64 as a message. | |||
| func siphash(k0, k1, m uint64) uint64 { | |||
| // Initialization. | |||
| v0 := k0 ^ 0x736f6d6570736575 | |||
| v1 := k1 ^ 0x646f72616e646f6d | |||
| v2 := k0 ^ 0x6c7967656e657261 | |||
| v3 := k1 ^ 0x7465646279746573 | |||
| t := uint64(8) << 56 | |||
| // Compression. | |||
| v3 ^= m | |||
| // Round 1. | |||
| v0 += v1 | |||
| v1 = v1<<13 | v1>>(64-13) | |||
| v1 ^= v0 | |||
| v0 = v0<<32 | v0>>(64-32) | |||
| v2 += v3 | |||
| v3 = v3<<16 | v3>>(64-16) | |||
| v3 ^= v2 | |||
| v0 += v3 | |||
| v3 = v3<<21 | v3>>(64-21) | |||
| v3 ^= v0 | |||
| v2 += v1 | |||
| v1 = v1<<17 | v1>>(64-17) | |||
| v1 ^= v2 | |||
| v2 = v2<<32 | v2>>(64-32) | |||
| // Round 2. | |||
| v0 += v1 | |||
| v1 = v1<<13 | v1>>(64-13) | |||
| v1 ^= v0 | |||
| v0 = v0<<32 | v0>>(64-32) | |||
| v2 += v3 | |||
| v3 = v3<<16 | v3>>(64-16) | |||
| v3 ^= v2 | |||
| v0 += v3 | |||
| v3 = v3<<21 | v3>>(64-21) | |||
| v3 ^= v0 | |||
| v2 += v1 | |||
| v1 = v1<<17 | v1>>(64-17) | |||
| v1 ^= v2 | |||
| v2 = v2<<32 | v2>>(64-32) | |||
| v0 ^= m | |||
| // Compress last block. | |||
| v3 ^= t | |||
| // Round 1. | |||
| v0 += v1 | |||
| v1 = v1<<13 | v1>>(64-13) | |||
| v1 ^= v0 | |||
| v0 = v0<<32 | v0>>(64-32) | |||
| v2 += v3 | |||
| v3 = v3<<16 | v3>>(64-16) | |||
| v3 ^= v2 | |||
| v0 += v3 | |||
| v3 = v3<<21 | v3>>(64-21) | |||
| v3 ^= v0 | |||
| v2 += v1 | |||
| v1 = v1<<17 | v1>>(64-17) | |||
| v1 ^= v2 | |||
| v2 = v2<<32 | v2>>(64-32) | |||
| // Round 2. | |||
| v0 += v1 | |||
| v1 = v1<<13 | v1>>(64-13) | |||
| v1 ^= v0 | |||
| v0 = v0<<32 | v0>>(64-32) | |||
| v2 += v3 | |||
| v3 = v3<<16 | v3>>(64-16) | |||
| v3 ^= v2 | |||
| v0 += v3 | |||
| v3 = v3<<21 | v3>>(64-21) | |||
| v3 ^= v0 | |||
| v2 += v1 | |||
| v1 = v1<<17 | v1>>(64-17) | |||
| v1 ^= v2 | |||
| v2 = v2<<32 | v2>>(64-32) | |||
| v0 ^= t | |||
| // Finalization. | |||
| v2 ^= 0xff | |||
| // Round 1. | |||
| v0 += v1 | |||
| v1 = v1<<13 | v1>>(64-13) | |||
| v1 ^= v0 | |||
| v0 = v0<<32 | v0>>(64-32) | |||
| v2 += v3 | |||
| v3 = v3<<16 | v3>>(64-16) | |||
| v3 ^= v2 | |||
| v0 += v3 | |||
| v3 = v3<<21 | v3>>(64-21) | |||
| v3 ^= v0 | |||
| v2 += v1 | |||
| v1 = v1<<17 | v1>>(64-17) | |||
| v1 ^= v2 | |||
| v2 = v2<<32 | v2>>(64-32) | |||
| // Round 2. | |||
| v0 += v1 | |||
| v1 = v1<<13 | v1>>(64-13) | |||
| v1 ^= v0 | |||
| v0 = v0<<32 | v0>>(64-32) | |||
| v2 += v3 | |||
| v3 = v3<<16 | v3>>(64-16) | |||
| v3 ^= v2 | |||
| v0 += v3 | |||
| v3 = v3<<21 | v3>>(64-21) | |||
| v3 ^= v0 | |||
| v2 += v1 | |||
| v1 = v1<<17 | v1>>(64-17) | |||
| v1 ^= v2 | |||
| v2 = v2<<32 | v2>>(64-32) | |||
| // Round 3. | |||
| v0 += v1 | |||
| v1 = v1<<13 | v1>>(64-13) | |||
| v1 ^= v0 | |||
| v0 = v0<<32 | v0>>(64-32) | |||
| v2 += v3 | |||
| v3 = v3<<16 | v3>>(64-16) | |||
| v3 ^= v2 | |||
| v0 += v3 | |||
| v3 = v3<<21 | v3>>(64-21) | |||
| v3 ^= v0 | |||
| v2 += v1 | |||
| v1 = v1<<17 | v1>>(64-17) | |||
| v1 ^= v2 | |||
| v2 = v2<<32 | v2>>(64-32) | |||
| // Round 4. | |||
| v0 += v1 | |||
| v1 = v1<<13 | v1>>(64-13) | |||
| v1 ^= v0 | |||
| v0 = v0<<32 | v0>>(64-32) | |||
| v2 += v3 | |||
| v3 = v3<<16 | v3>>(64-16) | |||
| v3 ^= v2 | |||
| v0 += v3 | |||
| v3 = v3<<21 | v3>>(64-21) | |||
| v3 ^= v0 | |||
| v2 += v1 | |||
| v1 = v1<<17 | v1>>(64-17) | |||
| v1 ^= v2 | |||
| v2 = v2<<32 | v2>>(64-32) | |||
| return v0 ^ v1 ^ v2 ^ v3 | |||
| } | |||
| // rekey sets a new PRNG key, which is read from crypto/rand. | |||
| func (p *siprng) rekey() { | |||
| var k [16]byte | |||
| if _, err := io.ReadFull(rand.Reader, k[:]); err != nil { | |||
| panic(err.Error()) | |||
| } | |||
| p.k0 = binary.LittleEndian.Uint64(k[0:8]) | |||
| p.k1 = binary.LittleEndian.Uint64(k[8:16]) | |||
| p.ctr = 1 | |||
| } | |||
| // Uint64 returns a new pseudorandom uint64. | |||
| // It rekeys PRNG on the first call and every 64 MB of generated data. | |||
| func (p *siprng) Uint64() uint64 { | |||
| p.mu.Lock() | |||
| if p.ctr == 0 || p.ctr > 8*1024*1024 { | |||
| p.rekey() | |||
| } | |||
| v := siphash(p.k0, p.k1, p.ctr) | |||
| p.ctr++ | |||
| p.mu.Unlock() | |||
| return v | |||
| } | |||
| func (p *siprng) Int63() int64 { | |||
| return int64(p.Uint64() & 0x7fffffffffffffff) | |||
| } | |||
| func (p *siprng) Uint32() uint32 { | |||
| return uint32(p.Uint64()) | |||
| } | |||
| func (p *siprng) Int31() int32 { | |||
| return int32(p.Uint32() & 0x7fffffff) | |||
| } | |||
| func (p *siprng) Intn(n int) int { | |||
| if n <= 0 { | |||
| panic("invalid argument to Intn") | |||
| } | |||
| if n <= 1<<31-1 { | |||
| return int(p.Int31n(int32(n))) | |||
| } | |||
| return int(p.Int63n(int64(n))) | |||
| } | |||
| func (p *siprng) Int63n(n int64) int64 { | |||
| if n <= 0 { | |||
| panic("invalid argument to Int63n") | |||
| } | |||
| max := int64((1 << 63) - 1 - (1<<63)%uint64(n)) | |||
| v := p.Int63() | |||
| for v > max { | |||
| v = p.Int63() | |||
| } | |||
| return v % n | |||
| } | |||
| func (p *siprng) Int31n(n int32) int32 { | |||
| if n <= 0 { | |||
| panic("invalid argument to Int31n") | |||
| } | |||
| max := int32((1 << 31) - 1 - (1<<31)%uint32(n)) | |||
| v := p.Int31() | |||
| for v > max { | |||
| v = p.Int31() | |||
| } | |||
| return v % n | |||
| } | |||
| func (p *siprng) Float64() float64 { return float64(p.Int63()) / (1 << 63) } | |||
| @@ -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,18 @@ | |||
| # csrf [](https://travis-ci.org/go-macaron/csrf) [](http://gocover.io/github.com/go-macaron/csrf) | |||
| Middleware csrf generates and validates CSRF tokens for [Macaron](https://github.com/go-macaron/macaron). | |||
| [API Reference](https://gowalker.org/github.com/go-macaron/csrf) | |||
| ### Installation | |||
| go get github.com/go-macaron/csrf | |||
| ## Getting Help | |||
| - [API Reference](https://gowalker.org/github.com/go-macaron/csrf) | |||
| - [Documentation](http://go-macaron.com/docs/middlewares/csrf) | |||
| ## License | |||
| This project is under the Apache License, Version 2.0. See the [LICENSE](LICENSE) file for the full license text. | |||
| @@ -0,0 +1,251 @@ | |||
| // Copyright 2013 Martini Authors | |||
| // Copyright 2014 The Macaron Authors | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"): you may | |||
| // not use this file except in compliance with the License. You may obtain | |||
| // a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
| // License for the specific language governing permissions and limitations | |||
| // under the License. | |||
| // Package csrf is a middleware that generates and validates CSRF tokens for Macaron. | |||
| package csrf | |||
| import ( | |||
| "net/http" | |||
| "time" | |||
| "github.com/Unknwon/com" | |||
| "github.com/go-macaron/session" | |||
| "gopkg.in/macaron.v1" | |||
| ) | |||
| const _VERSION = "0.1.0" | |||
| func Version() string { | |||
| return _VERSION | |||
| } | |||
| // CSRF represents a CSRF service and is used to get the current token and validate a suspect token. | |||
| type CSRF interface { | |||
| // Return HTTP header to search for token. | |||
| GetHeaderName() string | |||
| // Return form value to search for token. | |||
| GetFormName() string | |||
| // Return cookie name to search for token. | |||
| GetCookieName() string | |||
| // Return cookie path | |||
| GetCookiePath() string | |||
| // Return the token. | |||
| GetToken() string | |||
| // Validate by token. | |||
| ValidToken(t string) bool | |||
| // Error replies to the request with a custom function when ValidToken fails. | |||
| Error(w http.ResponseWriter) | |||
| } | |||
| type csrf struct { | |||
| // Header name value for setting and getting csrf token. | |||
| Header string | |||
| // Form name value for setting and getting csrf token. | |||
| Form string | |||
| // Cookie name value for setting and getting csrf token. | |||
| Cookie string | |||
| //Cookie path | |||
| CookiePath string | |||
| // Token generated to pass via header, cookie, or hidden form value. | |||
| Token string | |||
| // This value must be unique per user. | |||
| ID string | |||
| // Secret used along with the unique id above to generate the Token. | |||
| Secret string | |||
| // ErrorFunc is the custom function that replies to the request when ValidToken fails. | |||
| ErrorFunc func(w http.ResponseWriter) | |||
| } | |||
| // GetHeaderName returns the name of the HTTP header for csrf token. | |||
| func (c *csrf) GetHeaderName() string { | |||
| return c.Header | |||
| } | |||
| // GetFormName returns the name of the form value for csrf token. | |||
| func (c *csrf) GetFormName() string { | |||
| return c.Form | |||
| } | |||
| // GetCookieName returns the name of the cookie for csrf token. | |||
| func (c *csrf) GetCookieName() string { | |||
| return c.Cookie | |||
| } | |||
| // GetCookiePath returns the path of the cookie for csrf token. | |||
| func (c *csrf) GetCookiePath() string { | |||
| return c.CookiePath | |||
| } | |||
| // GetToken returns the current token. This is typically used | |||
| // to populate a hidden form in an HTML template. | |||
| func (c *csrf) GetToken() string { | |||
| return c.Token | |||
| } | |||
| // ValidToken validates the passed token against the existing Secret and ID. | |||
| func (c *csrf) ValidToken(t string) bool { | |||
| return ValidToken(t, c.Secret, c.ID, "POST") | |||
| } | |||
| // Error replies to the request when ValidToken fails. | |||
| func (c *csrf) Error(w http.ResponseWriter) { | |||
| c.ErrorFunc(w) | |||
| } | |||
| // Options maintains options to manage behavior of Generate. | |||
| type Options struct { | |||
| // The global secret value used to generate Tokens. | |||
| Secret string | |||
| // HTTP header used to set and get token. | |||
| Header string | |||
| // Form value used to set and get token. | |||
| Form string | |||
| // Cookie value used to set and get token. | |||
| Cookie string | |||
| // Cookie path. | |||
| CookiePath string | |||
| // Key used for getting the unique ID per user. | |||
| SessionKey string | |||
| // oldSeesionKey saves old value corresponding to SessionKey. | |||
| oldSeesionKey string | |||
| // If true, send token via X-CSRFToken header. | |||
| SetHeader bool | |||
| // If true, send token via _csrf cookie. | |||
| SetCookie bool | |||
| // Set the Secure flag to true on the cookie. | |||
| Secure bool | |||
| // Disallow Origin appear in request header. | |||
| Origin bool | |||
| // The function called when Validate fails. | |||
| ErrorFunc func(w http.ResponseWriter) | |||
| } | |||
| func prepareOptions(options []Options) Options { | |||
| var opt Options | |||
| if len(options) > 0 { | |||
| opt = options[0] | |||
| } | |||
| // Defaults. | |||
| if len(opt.Secret) == 0 { | |||
| opt.Secret = string(com.RandomCreateBytes(10)) | |||
| } | |||
| if len(opt.Header) == 0 { | |||
| opt.Header = "X-CSRFToken" | |||
| } | |||
| if len(opt.Form) == 0 { | |||
| opt.Form = "_csrf" | |||
| } | |||
| if len(opt.Cookie) == 0 { | |||
| opt.Cookie = "_csrf" | |||
| } | |||
| if len(opt.CookiePath) == 0 { | |||
| opt.CookiePath = "/" | |||
| } | |||
| if len(opt.SessionKey) == 0 { | |||
| opt.SessionKey = "uid" | |||
| } | |||
| opt.oldSeesionKey = "_old_" + opt.SessionKey | |||
| if opt.ErrorFunc == nil { | |||
| opt.ErrorFunc = func(w http.ResponseWriter) { | |||
| http.Error(w, "Invalid csrf token.", http.StatusBadRequest) | |||
| } | |||
| } | |||
| return opt | |||
| } | |||
| // Generate maps CSRF to each request. If this request is a Get request, it will generate a new token. | |||
| // Additionally, depending on options set, generated tokens will be sent via Header and/or Cookie. | |||
| func Generate(options ...Options) macaron.Handler { | |||
| opt := prepareOptions(options) | |||
| return func(ctx *macaron.Context, sess session.Store) { | |||
| x := &csrf{ | |||
| Secret: opt.Secret, | |||
| Header: opt.Header, | |||
| Form: opt.Form, | |||
| Cookie: opt.Cookie, | |||
| CookiePath: opt.CookiePath, | |||
| ErrorFunc: opt.ErrorFunc, | |||
| } | |||
| ctx.MapTo(x, (*CSRF)(nil)) | |||
| if opt.Origin && len(ctx.Req.Header.Get("Origin")) > 0 { | |||
| return | |||
| } | |||
| x.ID = "0" | |||
| uid := sess.Get(opt.SessionKey) | |||
| if uid != nil { | |||
| x.ID = com.ToStr(uid) | |||
| } | |||
| needsNew := false | |||
| oldUid := sess.Get(opt.oldSeesionKey) | |||
| if oldUid == nil || oldUid.(string) != x.ID { | |||
| needsNew = true | |||
| sess.Set(opt.oldSeesionKey, x.ID) | |||
| } else { | |||
| // If cookie present, map existing token, else generate a new one. | |||
| if val := ctx.GetCookie(opt.Cookie); len(val) > 0 { | |||
| // FIXME: test coverage. | |||
| x.Token = val | |||
| } else { | |||
| needsNew = true | |||
| } | |||
| } | |||
| if needsNew { | |||
| // FIXME: actionId. | |||
| x.Token = GenerateToken(x.Secret, x.ID, "POST") | |||
| if opt.SetCookie { | |||
| ctx.SetCookie(opt.Cookie, x.Token, 0, opt.CookiePath, "", false, true, time.Now().AddDate(0, 0, 1)) | |||
| } | |||
| } | |||
| if opt.SetHeader { | |||
| ctx.Resp.Header().Add(opt.Header, x.Token) | |||
| } | |||
| } | |||
| } | |||
| // Csrfer maps CSRF to each request. If this request is a Get request, it will generate a new token. | |||
| // Additionally, depending on options set, generated tokens will be sent via Header and/or Cookie. | |||
| func Csrfer(options ...Options) macaron.Handler { | |||
| return Generate(options...) | |||
| } | |||
| // Validate should be used as a per route middleware. It attempts to get a token from a "X-CSRFToken" | |||
| // HTTP header and then a "_csrf" form value. If one of these is found, the token will be validated | |||
| // using ValidToken. If this validation fails, custom Error is sent in the reply. | |||
| // If neither a header or form value is found, http.StatusBadRequest is sent. | |||
| func Validate(ctx *macaron.Context, x CSRF) { | |||
| if token := ctx.Req.Header.Get(x.GetHeaderName()); len(token) > 0 { | |||
| if !x.ValidToken(token) { | |||
| ctx.SetCookie(x.GetCookieName(), "", -1, x.GetCookiePath()) | |||
| x.Error(ctx.Resp) | |||
| } | |||
| return | |||
| } | |||
| if token := ctx.Req.FormValue(x.GetFormName()); len(token) > 0 { | |||
| if !x.ValidToken(token) { | |||
| ctx.SetCookie(x.GetCookieName(), "", -1, x.GetCookiePath()) | |||
| x.Error(ctx.Resp) | |||
| } | |||
| return | |||
| } | |||
| http.Error(ctx.Resp, "Bad Request: no CSRF token present", http.StatusBadRequest) | |||
| } | |||
| @@ -0,0 +1,97 @@ | |||
| // Copyright 2012 Google Inc. All Rights Reserved. | |||
| // Copyright 2014 The Macaron Authors | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||
| // you may not use this file except in compliance with the License. | |||
| // You may obtain a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| // See the License for the specific language governing permissions and | |||
| // limitations under the License. | |||
| package csrf | |||
| import ( | |||
| "bytes" | |||
| "crypto/hmac" | |||
| "crypto/sha1" | |||
| "crypto/subtle" | |||
| "encoding/base64" | |||
| "fmt" | |||
| "strconv" | |||
| "strings" | |||
| "time" | |||
| ) | |||
| // The duration that XSRF tokens are valid. | |||
| // It is exported so clients may set cookie timeouts that match generated tokens. | |||
| const TIMEOUT = 24 * time.Hour | |||
| // clean sanitizes a string for inclusion in a token by replacing all ":"s. | |||
| func clean(s string) string { | |||
| return strings.Replace(s, ":", "_", -1) | |||
| } | |||
| // GenerateToken returns a URL-safe secure XSRF token that expires in 24 hours. | |||
| // | |||
| // key is a secret key for your application. | |||
| // userID is a unique identifier for the user. | |||
| // actionID is the action the user is taking (e.g. POSTing to a particular path). | |||
| func GenerateToken(key, userID, actionID string) string { | |||
| return generateTokenAtTime(key, userID, actionID, time.Now()) | |||
| } | |||
| // generateTokenAtTime is like Generate, but returns a token that expires 24 hours from now. | |||
| func generateTokenAtTime(key, userID, actionID string, now time.Time) string { | |||
| h := hmac.New(sha1.New, []byte(key)) | |||
| fmt.Fprintf(h, "%s:%s:%d", clean(userID), clean(actionID), now.UnixNano()) | |||
| tok := fmt.Sprintf("%s:%d", h.Sum(nil), now.UnixNano()) | |||
| return base64.URLEncoding.EncodeToString([]byte(tok)) | |||
| } | |||
| // Valid returns true if token is a valid, unexpired token returned by Generate. | |||
| func ValidToken(token, key, userID, actionID string) bool { | |||
| return validTokenAtTime(token, key, userID, actionID, time.Now()) | |||
| } | |||
| // validTokenAtTime is like Valid, but it uses now to check if the token is expired. | |||
| func validTokenAtTime(token, key, userID, actionID string, now time.Time) bool { | |||
| // Decode the token. | |||
| data, err := base64.URLEncoding.DecodeString(token) | |||
| if err != nil { | |||
| return false | |||
| } | |||
| // Extract the issue time of the token. | |||
| sep := bytes.LastIndex(data, []byte{':'}) | |||
| if sep < 0 { | |||
| return false | |||
| } | |||
| nanos, err := strconv.ParseInt(string(data[sep+1:]), 10, 64) | |||
| if err != nil { | |||
| return false | |||
| } | |||
| issueTime := time.Unix(0, nanos) | |||
| // Check that the token is not expired. | |||
| if now.Sub(issueTime) >= TIMEOUT { | |||
| return false | |||
| } | |||
| // Check that the token is not from the future. | |||
| // Allow 1 minute grace period in case the token is being verified on a | |||
| // machine whose clock is behind the machine that issued the token. | |||
| if issueTime.After(now.Add(1 * time.Minute)) { | |||
| return false | |||
| } | |||
| expected := generateTokenAtTime(key, userID, actionID, issueTime) | |||
| // Check that the token matches the expected value. | |||
| // Use constant time comparison to avoid timing attacks. | |||
| return subtle.ConstantTimeCompare([]byte(token), []byte(expected)) == 1 | |||
| } | |||
| @@ -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,20 @@ | |||
| # gzip [](https://travis-ci.org/go-macaron/gzip) [](http://gocover.io/github.com/go-macaron/gzip) | |||
| Middleware gzip provides compress to responses for [Macaron](https://github.com/go-macaron/macaron). | |||
| ### Installation | |||
| go get github.com/go-macaron/gzip | |||
| ## Getting Help | |||
| - [API Reference](https://gowalker.org/github.com/go-macaron/gzip) | |||
| - [Documentation](http://go-macaron.com/docs/middlewares/gzip) | |||
| ## Credits | |||
| This package is a modified version of [martini-contrib/gzip](https://github.com/martini-contrib/gzip). | |||
| ## License | |||
| This project is under the Apache License, Version 2.0. See the [LICENSE](LICENSE) file for the full license text. | |||
| @@ -0,0 +1,121 @@ | |||
| // Copyright 2013 Martini Authors | |||
| // 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 gzip | |||
| import ( | |||
| "bufio" | |||
| "fmt" | |||
| "net" | |||
| "net/http" | |||
| "strings" | |||
| "github.com/klauspost/compress/gzip" | |||
| "gopkg.in/macaron.v1" | |||
| ) | |||
| const ( | |||
| _HEADER_ACCEPT_ENCODING = "Accept-Encoding" | |||
| _HEADER_CONTENT_ENCODING = "Content-Encoding" | |||
| _HEADER_CONTENT_LENGTH = "Content-Length" | |||
| _HEADER_CONTENT_TYPE = "Content-Type" | |||
| _HEADER_VARY = "Vary" | |||
| ) | |||
| // Options represents a struct for specifying configuration options for the GZip middleware. | |||
| type Options struct { | |||
| // Compression level. Can be DefaultCompression(-1), ConstantCompression(-2) | |||
| // or any integer value between BestSpeed(1) and BestCompression(9) inclusive. | |||
| CompressionLevel int | |||
| } | |||
| func isCompressionLevelValid(level int) bool { | |||
| return level == gzip.DefaultCompression || | |||
| level == gzip.ConstantCompression || | |||
| (level >= gzip.BestSpeed && level <= gzip.BestCompression) | |||
| } | |||
| func prepareOptions(options []Options) Options { | |||
| var opt Options | |||
| if len(options) > 0 { | |||
| opt = options[0] | |||
| } | |||
| if !isCompressionLevelValid(opt.CompressionLevel) { | |||
| // For web content, level 4 seems to be a sweet spot. | |||
| opt.CompressionLevel = 4 | |||
| } | |||
| return opt | |||
| } | |||
| // Gziper returns a Handler that adds gzip compression to all requests. | |||
| // Make sure to include the Gzip middleware above other middleware | |||
| // that alter the response body (like the render middleware). | |||
| func Gziper(options ...Options) macaron.Handler { | |||
| opt := prepareOptions(options) | |||
| return func(ctx *macaron.Context) { | |||
| if !strings.Contains(ctx.Req.Header.Get(_HEADER_ACCEPT_ENCODING), "gzip") { | |||
| return | |||
| } | |||
| headers := ctx.Resp.Header() | |||
| headers.Set(_HEADER_CONTENT_ENCODING, "gzip") | |||
| headers.Set(_HEADER_VARY, _HEADER_ACCEPT_ENCODING) | |||
| // We've made sure compression level is valid in prepareGzipOptions, | |||
| // no need to check same error again. | |||
| gz, err := gzip.NewWriterLevel(ctx.Resp, opt.CompressionLevel) | |||
| if err != nil { | |||
| panic(err.Error()) | |||
| } | |||
| defer gz.Close() | |||
| gzw := gzipResponseWriter{gz, ctx.Resp} | |||
| ctx.Resp = gzw | |||
| ctx.MapTo(gzw, (*http.ResponseWriter)(nil)) | |||
| // Check if render middleware has been registered, | |||
| // if yes, we need to modify ResponseWriter for it as well. | |||
| if _, ok := ctx.Render.(*macaron.DummyRender); !ok { | |||
| ctx.Render.SetResponseWriter(gzw) | |||
| } | |||
| ctx.Next() | |||
| // delete content length after we know we have been written to | |||
| gzw.Header().Del("Content-Length") | |||
| } | |||
| } | |||
| type gzipResponseWriter struct { | |||
| w *gzip.Writer | |||
| macaron.ResponseWriter | |||
| } | |||
| func (grw gzipResponseWriter) Write(p []byte) (int, error) { | |||
| if len(grw.Header().Get(_HEADER_CONTENT_TYPE)) == 0 { | |||
| grw.Header().Set(_HEADER_CONTENT_TYPE, http.DetectContentType(p)) | |||
| } | |||
| return grw.w.Write(p) | |||
| } | |||
| func (grw gzipResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { | |||
| hijacker, ok := grw.ResponseWriter.(http.Hijacker) | |||
| if !ok { | |||
| return nil, nil, fmt.Errorf("the ResponseWriter doesn't support the Hijacker interface") | |||
| } | |||
| return hijacker.Hijack() | |||
| } | |||
| @@ -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,16 @@ | |||
| # i18n [](https://travis-ci.org/go-macaron/i18n) [](http://gocover.io/github.com/go-macaron/i18n) | |||
| Middleware i18n provides app Internationalization and Localization for [Macaron](https://github.com/go-macaron/macaron). | |||
| ### Installation | |||
| go get github.com/go-macaron/i18n | |||
| ## Getting Help | |||
| - [API Reference](https://gowalker.org/github.com/go-macaron/i18n) | |||
| - [Documentation](http://go-macaron.com/docs/middlewares/i18n) | |||
| ## License | |||
| This project is under the Apache License, Version 2.0. See the [LICENSE](LICENSE) file for the full license text. | |||
| @@ -0,0 +1,225 @@ | |||
| // Copyright 2014 The Macaron Authors | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"): you may | |||
| // not use this file except in compliance with the License. You may obtain | |||
| // a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
| // License for the specific language governing permissions and limitations | |||
| // under the License. | |||
| // Package i18n is a middleware that provides app Internationalization and Localization of Macaron. | |||
| package i18n | |||
| import ( | |||
| "fmt" | |||
| "path" | |||
| "strings" | |||
| "github.com/Unknwon/com" | |||
| "github.com/Unknwon/i18n" | |||
| "golang.org/x/text/language" | |||
| "gopkg.in/macaron.v1" | |||
| ) | |||
| const _VERSION = "0.3.0" | |||
| func Version() string { | |||
| return _VERSION | |||
| } | |||
| // initLocales initializes language type list and Accept-Language header matcher. | |||
| func initLocales(opt Options) language.Matcher { | |||
| tags := make([]language.Tag, len(opt.Langs)) | |||
| for i, lang := range opt.Langs { | |||
| tags[i] = language.Raw.Make(lang) | |||
| fname := fmt.Sprintf(opt.Format, lang) | |||
| // Append custom locale file. | |||
| custom := []interface{}{} | |||
| customPath := path.Join(opt.CustomDirectory, fname) | |||
| if com.IsFile(customPath) { | |||
| custom = append(custom, customPath) | |||
| } | |||
| var locale interface{} | |||
| if data, ok := opt.Files[fname]; ok { | |||
| locale = data | |||
| } else { | |||
| locale = path.Join(opt.Directory, fname) | |||
| } | |||
| err := i18n.SetMessageWithDesc(lang, opt.Names[i], locale, custom...) | |||
| if err != nil && err != i18n.ErrLangAlreadyExist { | |||
| panic(fmt.Errorf("fail to set message file(%s): %v", lang, err)) | |||
| } | |||
| } | |||
| return language.NewMatcher(tags) | |||
| } | |||
| // A Locale describles the information of localization. | |||
| type Locale struct { | |||
| i18n.Locale | |||
| } | |||
| // Language returns language current locale represents. | |||
| func (l Locale) Language() string { | |||
| return l.Lang | |||
| } | |||
| // Options represents a struct for specifying configuration options for the i18n middleware. | |||
| type Options struct { | |||
| // Suburl of path. Default is empty. | |||
| SubURL string | |||
| // Directory to load locale files. Default is "conf/locale" | |||
| Directory string | |||
| // File stores actual data of locale files. Used for in-memory purpose. | |||
| Files map[string][]byte | |||
| // Custom directory to overload locale files. Default is "custom/conf/locale" | |||
| CustomDirectory string | |||
| // Langauges that will be supported, order is meaningful. | |||
| Langs []string | |||
| // Human friendly names corresponding to Langs list. | |||
| Names []string | |||
| // Default language locale, leave empty to remain unset. | |||
| DefaultLang string | |||
| // Locale file naming style. Default is "locale_%s.ini". | |||
| Format string | |||
| // Name of language parameter name in URL. Default is "lang". | |||
| Parameter string | |||
| // Redirect when user uses get parameter to specify language. | |||
| Redirect bool | |||
| // Name that maps into template variable. Default is "i18n". | |||
| TmplName string | |||
| // Configuration section name. Default is "i18n". | |||
| Section string | |||
| } | |||
| func prepareOptions(options []Options) Options { | |||
| var opt Options | |||
| if len(options) > 0 { | |||
| opt = options[0] | |||
| } | |||
| if len(opt.Section) == 0 { | |||
| opt.Section = "i18n" | |||
| } | |||
| sec := macaron.Config().Section(opt.Section) | |||
| opt.SubURL = strings.TrimSuffix(opt.SubURL, "/") | |||
| if len(opt.Langs) == 0 { | |||
| opt.Langs = sec.Key("LANGS").Strings(",") | |||
| } | |||
| if len(opt.Names) == 0 { | |||
| opt.Names = sec.Key("NAMES").Strings(",") | |||
| } | |||
| if len(opt.Langs) == 0 { | |||
| panic("no language is specified") | |||
| } else if len(opt.Langs) != len(opt.Names) { | |||
| panic("length of langs is not same as length of names") | |||
| } | |||
| i18n.SetDefaultLang(opt.DefaultLang) | |||
| if len(opt.Directory) == 0 { | |||
| opt.Directory = sec.Key("DIRECTORY").MustString("conf/locale") | |||
| } | |||
| if len(opt.CustomDirectory) == 0 { | |||
| opt.CustomDirectory = sec.Key("CUSTOM_DIRECTORY").MustString("custom/conf/locale") | |||
| } | |||
| if len(opt.Format) == 0 { | |||
| opt.Format = sec.Key("FORMAT").MustString("locale_%s.ini") | |||
| } | |||
| if len(opt.Parameter) == 0 { | |||
| opt.Parameter = sec.Key("PARAMETER").MustString("lang") | |||
| } | |||
| if !opt.Redirect { | |||
| opt.Redirect = sec.Key("REDIRECT").MustBool() | |||
| } | |||
| if len(opt.TmplName) == 0 { | |||
| opt.TmplName = sec.Key("TMPL_NAME").MustString("i18n") | |||
| } | |||
| return opt | |||
| } | |||
| type LangType struct { | |||
| Lang, Name string | |||
| } | |||
| // I18n is a middleware provides localization layer for your application. | |||
| // Paramenter langs must be in the form of "en-US", "zh-CN", etc. | |||
| // Otherwise it may not recognize browser input. | |||
| func I18n(options ...Options) macaron.Handler { | |||
| opt := prepareOptions(options) | |||
| m := initLocales(opt) | |||
| return func(ctx *macaron.Context) { | |||
| isNeedRedir := false | |||
| hasCookie := false | |||
| // 1. Check URL arguments. | |||
| lang := ctx.Query(opt.Parameter) | |||
| // 2. Get language information from cookies. | |||
| if len(lang) == 0 { | |||
| lang = ctx.GetCookie("lang") | |||
| hasCookie = true | |||
| } else { | |||
| isNeedRedir = true | |||
| } | |||
| // Check again in case someone modify by purpose. | |||
| if !i18n.IsExist(lang) { | |||
| lang = "" | |||
| isNeedRedir = false | |||
| hasCookie = false | |||
| } | |||
| // 3. Get language information from 'Accept-Language'. | |||
| // The first element in the list is chosen to be the default language automatically. | |||
| if len(lang) == 0 { | |||
| tags, _, _ := language.ParseAcceptLanguage(ctx.Req.Header.Get("Accept-Language")) | |||
| tag, _, _ := m.Match(tags...) | |||
| lang = tag.String() | |||
| isNeedRedir = false | |||
| } | |||
| curLang := LangType{ | |||
| Lang: lang, | |||
| } | |||
| // Save language information in cookies. | |||
| if !hasCookie { | |||
| ctx.SetCookie("lang", curLang.Lang, 1<<31-1, "/"+strings.TrimPrefix(opt.SubURL, "/")) | |||
| } | |||
| restLangs := make([]LangType, 0, i18n.Count()-1) | |||
| langs := i18n.ListLangs() | |||
| names := i18n.ListLangDescs() | |||
| for i, v := range langs { | |||
| if lang != v { | |||
| restLangs = append(restLangs, LangType{v, names[i]}) | |||
| } else { | |||
| curLang.Name = names[i] | |||
| } | |||
| } | |||
| // Set language properties. | |||
| locale := Locale{i18n.Locale{lang}} | |||
| ctx.Map(locale) | |||
| ctx.Locale = locale | |||
| ctx.Data[opt.TmplName] = locale | |||
| ctx.Data["Tr"] = i18n.Tr | |||
| ctx.Data["Lang"] = locale.Lang | |||
| ctx.Data["LangName"] = curLang.Name | |||
| ctx.Data["AllLangs"] = append([]LangType{curLang}, restLangs...) | |||
| ctx.Data["RestLangs"] = restLangs | |||
| if opt.Redirect && isNeedRedir { | |||
| ctx.Redirect(opt.SubURL + ctx.Req.RequestURI[:strings.Index(ctx.Req.RequestURI, "?")]) | |||
| } | |||
| } | |||
| } | |||
| @@ -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,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,20 @@ | |||
| # session [](https://travis-ci.org/go-macaron/session) [](http://gocover.io/github.com/go-macaron/session) | |||
| Middleware session provides session management for [Macaron](https://github.com/go-macaron/macaron). It can use many session providers, including memory, file, Redis, Memcache, PostgreSQL, MySQL, Couchbase, Ledis and Nodb. | |||
| ### Installation | |||
| go get github.com/go-macaron/session | |||
| ## Getting Help | |||
| - [API Reference](https://gowalker.org/github.com/go-macaron/session) | |||
| - [Documentation](http://go-macaron.com/docs/middlewares/session) | |||
| ## Credits | |||
| This package is a modified version of [beego/session](https://github.com/astaxie/beego/tree/master/session). | |||
| ## License | |||
| This project is under the Apache License, Version 2.0. See the [LICENSE](LICENSE) file for the full license text. | |||
| @@ -0,0 +1,261 @@ | |||
| // Copyright 2013 Beego Authors | |||
| // Copyright 2014 The Macaron Authors | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"): you may | |||
| // not use this file except in compliance with the License. You may obtain | |||
| // a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
| // License for the specific language governing permissions and limitations | |||
| // under the License. | |||
| package session | |||
| import ( | |||
| "fmt" | |||
| "io/ioutil" | |||
| "log" | |||
| "os" | |||
| "path" | |||
| "path/filepath" | |||
| "sync" | |||
| "time" | |||
| "github.com/Unknwon/com" | |||
| ) | |||
| // FileStore represents a file session store implementation. | |||
| type FileStore struct { | |||
| p *FileProvider | |||
| sid string | |||
| lock sync.RWMutex | |||
| data map[interface{}]interface{} | |||
| } | |||
| // NewFileStore creates and returns a file session store. | |||
| func NewFileStore(p *FileProvider, sid string, kv map[interface{}]interface{}) *FileStore { | |||
| return &FileStore{ | |||
| p: p, | |||
| sid: sid, | |||
| data: kv, | |||
| } | |||
| } | |||
| // Set sets value to given key in session. | |||
| func (s *FileStore) Set(key, val interface{}) error { | |||
| s.lock.Lock() | |||
| defer s.lock.Unlock() | |||
| s.data[key] = val | |||
| return nil | |||
| } | |||
| // Get gets value by given key in session. | |||
| func (s *FileStore) Get(key interface{}) interface{} { | |||
| s.lock.RLock() | |||
| defer s.lock.RUnlock() | |||
| return s.data[key] | |||
| } | |||
| // Delete delete a key from session. | |||
| func (s *FileStore) Delete(key interface{}) error { | |||
| s.lock.Lock() | |||
| defer s.lock.Unlock() | |||
| delete(s.data, key) | |||
| return nil | |||
| } | |||
| // ID returns current session ID. | |||
| func (s *FileStore) ID() string { | |||
| return s.sid | |||
| } | |||
| // Release releases resource and save data to provider. | |||
| func (s *FileStore) Release() error { | |||
| s.p.lock.Lock() | |||
| defer s.p.lock.Unlock() | |||
| data, err := EncodeGob(s.data) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| return ioutil.WriteFile(s.p.filepath(s.sid), data, os.ModePerm) | |||
| } | |||
| // Flush deletes all session data. | |||
| func (s *FileStore) Flush() error { | |||
| s.lock.Lock() | |||
| defer s.lock.Unlock() | |||
| s.data = make(map[interface{}]interface{}) | |||
| return nil | |||
| } | |||
| // FileProvider represents a file session provider implementation. | |||
| type FileProvider struct { | |||
| lock sync.RWMutex | |||
| maxlifetime int64 | |||
| rootPath string | |||
| } | |||
| // Init initializes file session provider with given root path. | |||
| func (p *FileProvider) Init(maxlifetime int64, rootPath string) error { | |||
| p.lock.Lock() | |||
| p.maxlifetime = maxlifetime | |||
| p.rootPath = rootPath | |||
| p.lock.Unlock() | |||
| return nil | |||
| } | |||
| func (p *FileProvider) filepath(sid string) string { | |||
| return path.Join(p.rootPath, string(sid[0]), string(sid[1]), sid) | |||
| } | |||
| // Read returns raw session store by session ID. | |||
| func (p *FileProvider) Read(sid string) (_ RawStore, err error) { | |||
| filename := p.filepath(sid) | |||
| if err = os.MkdirAll(path.Dir(filename), os.ModePerm); err != nil { | |||
| return nil, err | |||
| } | |||
| p.lock.RLock() | |||
| defer p.lock.RUnlock() | |||
| var f *os.File | |||
| if com.IsFile(filename) { | |||
| f, err = os.OpenFile(filename, os.O_RDWR, os.ModePerm) | |||
| } else { | |||
| f, err = os.Create(filename) | |||
| } | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| defer f.Close() | |||
| if err = os.Chtimes(filename, time.Now(), time.Now()); err != nil { | |||
| return nil, err | |||
| } | |||
| var kv map[interface{}]interface{} | |||
| data, err := ioutil.ReadAll(f) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| if len(data) == 0 { | |||
| kv = make(map[interface{}]interface{}) | |||
| } else { | |||
| kv, err = DecodeGob(data) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| } | |||
| return NewFileStore(p, sid, kv), nil | |||
| } | |||
| // Exist returns true if session with given ID exists. | |||
| func (p *FileProvider) Exist(sid string) bool { | |||
| p.lock.RLock() | |||
| defer p.lock.RUnlock() | |||
| return com.IsFile(p.filepath(sid)) | |||
| } | |||
| // Destory deletes a session by session ID. | |||
| func (p *FileProvider) Destory(sid string) error { | |||
| p.lock.Lock() | |||
| defer p.lock.Unlock() | |||
| return os.Remove(p.filepath(sid)) | |||
| } | |||
| func (p *FileProvider) regenerate(oldsid, sid string) (err error) { | |||
| p.lock.Lock() | |||
| defer p.lock.Unlock() | |||
| filename := p.filepath(sid) | |||
| if com.IsExist(filename) { | |||
| return fmt.Errorf("new sid '%s' already exists", sid) | |||
| } | |||
| oldname := p.filepath(oldsid) | |||
| if !com.IsFile(oldname) { | |||
| data, err := EncodeGob(make(map[interface{}]interface{})) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| if err = os.MkdirAll(path.Dir(oldname), os.ModePerm); err != nil { | |||
| return err | |||
| } | |||
| if err = ioutil.WriteFile(oldname, data, os.ModePerm); err != nil { | |||
| return err | |||
| } | |||
| } | |||
| if err = os.MkdirAll(path.Dir(filename), os.ModePerm); err != nil { | |||
| return err | |||
| } | |||
| if err = os.Rename(oldname, filename); err != nil { | |||
| return err | |||
| } | |||
| return nil | |||
| } | |||
| // Regenerate regenerates a session store from old session ID to new one. | |||
| func (p *FileProvider) Regenerate(oldsid, sid string) (_ RawStore, err error) { | |||
| if err := p.regenerate(oldsid, sid); err != nil { | |||
| return nil, err | |||
| } | |||
| return p.Read(sid) | |||
| } | |||
| // Count counts and returns number of sessions. | |||
| func (p *FileProvider) Count() int { | |||
| count := 0 | |||
| if err := filepath.Walk(p.rootPath, func(path string, fi os.FileInfo, err error) error { | |||
| if err != nil { | |||
| return err | |||
| } | |||
| if !fi.IsDir() { | |||
| count++ | |||
| } | |||
| return nil | |||
| }); err != nil { | |||
| log.Printf("error counting session files: %v", err) | |||
| return 0 | |||
| } | |||
| return count | |||
| } | |||
| // GC calls GC to clean expired sessions. | |||
| func (p *FileProvider) GC() { | |||
| p.lock.RLock() | |||
| defer p.lock.RUnlock() | |||
| if !com.IsExist(p.rootPath) { | |||
| return | |||
| } | |||
| if err := filepath.Walk(p.rootPath, func(path string, fi os.FileInfo, err error) error { | |||
| if err != nil { | |||
| return err | |||
| } | |||
| if !fi.IsDir() && | |||
| (fi.ModTime().Unix()+p.maxlifetime) < time.Now().Unix() { | |||
| return os.Remove(path) | |||
| } | |||
| return nil | |||
| }); err != nil { | |||
| log.Printf("error garbage collecting session files: %v", err) | |||
| } | |||
| } | |||
| func init() { | |||
| Register("file", &FileProvider{}) | |||
| } | |||
| @@ -0,0 +1,217 @@ | |||
| // Copyright 2013 Beego Authors | |||
| // Copyright 2014 The Macaron Authors | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"): you may | |||
| // not use this file except in compliance with the License. You may obtain | |||
| // a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
| // License for the specific language governing permissions and limitations | |||
| // under the License. | |||
| package session | |||
| import ( | |||
| "container/list" | |||
| "fmt" | |||
| "sync" | |||
| "time" | |||
| ) | |||
| // MemStore represents a in-memory session store implementation. | |||
| type MemStore struct { | |||
| sid string | |||
| lock sync.RWMutex | |||
| data map[interface{}]interface{} | |||
| lastAccess time.Time | |||
| } | |||
| // NewMemStore creates and returns a memory session store. | |||
| func NewMemStore(sid string) *MemStore { | |||
| return &MemStore{ | |||
| sid: sid, | |||
| data: make(map[interface{}]interface{}), | |||
| lastAccess: time.Now(), | |||
| } | |||
| } | |||
| // Set sets value to given key in session. | |||
| func (s *MemStore) Set(key, val interface{}) error { | |||
| s.lock.Lock() | |||
| defer s.lock.Unlock() | |||
| s.data[key] = val | |||
| return nil | |||
| } | |||
| // Get gets value by given key in session. | |||
| func (s *MemStore) Get(key interface{}) interface{} { | |||
| s.lock.RLock() | |||
| defer s.lock.RUnlock() | |||
| return s.data[key] | |||
| } | |||
| // Delete deletes a key from session. | |||
| func (s *MemStore) Delete(key interface{}) error { | |||
| s.lock.Lock() | |||
| defer s.lock.Unlock() | |||
| delete(s.data, key) | |||
| return nil | |||
| } | |||
| // ID returns current session ID. | |||
| func (s *MemStore) ID() string { | |||
| return s.sid | |||
| } | |||
| // Release releases resource and save data to provider. | |||
| func (_ *MemStore) Release() error { | |||
| return nil | |||
| } | |||
| // Flush deletes all session data. | |||
| func (s *MemStore) Flush() error { | |||
| s.lock.Lock() | |||
| defer s.lock.Unlock() | |||
| s.data = make(map[interface{}]interface{}) | |||
| return nil | |||
| } | |||
| // MemProvider represents a in-memory session provider implementation. | |||
| type MemProvider struct { | |||
| lock sync.RWMutex | |||
| maxLifetime int64 | |||
| data map[string]*list.Element | |||
| // A priority list whose lastAccess newer gets higer priority. | |||
| list *list.List | |||
| } | |||
| // Init initializes memory session provider. | |||
| func (p *MemProvider) Init(maxLifetime int64, _ string) error { | |||
| p.lock.Lock() | |||
| p.maxLifetime = maxLifetime | |||
| p.lock.Unlock() | |||
| return nil | |||
| } | |||
| // update expands time of session store by given ID. | |||
| func (p *MemProvider) update(sid string) error { | |||
| p.lock.Lock() | |||
| defer p.lock.Unlock() | |||
| if e, ok := p.data[sid]; ok { | |||
| e.Value.(*MemStore).lastAccess = time.Now() | |||
| p.list.MoveToFront(e) | |||
| return nil | |||
| } | |||
| return nil | |||
| } | |||
| // Read returns raw session store by session ID. | |||
| func (p *MemProvider) Read(sid string) (_ RawStore, err error) { | |||
| p.lock.RLock() | |||
| e, ok := p.data[sid] | |||
| p.lock.RUnlock() | |||
| if ok { | |||
| if err = p.update(sid); err != nil { | |||
| return nil, err | |||
| } | |||
| return e.Value.(*MemStore), nil | |||
| } | |||
| // Create a new session. | |||
| p.lock.Lock() | |||
| defer p.lock.Unlock() | |||
| s := NewMemStore(sid) | |||
| p.data[sid] = p.list.PushBack(s) | |||
| return s, nil | |||
| } | |||
| // Exist returns true if session with given ID exists. | |||
| func (p *MemProvider) Exist(sid string) bool { | |||
| p.lock.RLock() | |||
| defer p.lock.RUnlock() | |||
| _, ok := p.data[sid] | |||
| return ok | |||
| } | |||
| // Destory deletes a session by session ID. | |||
| func (p *MemProvider) Destory(sid string) error { | |||
| p.lock.Lock() | |||
| defer p.lock.Unlock() | |||
| e, ok := p.data[sid] | |||
| if !ok { | |||
| return nil | |||
| } | |||
| p.list.Remove(e) | |||
| delete(p.data, sid) | |||
| return nil | |||
| } | |||
| // Regenerate regenerates a session store from old session ID to new one. | |||
| func (p *MemProvider) Regenerate(oldsid, sid string) (RawStore, error) { | |||
| if p.Exist(sid) { | |||
| return nil, fmt.Errorf("new sid '%s' already exists", sid) | |||
| } | |||
| s, err := p.Read(oldsid) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| if err = p.Destory(oldsid); err != nil { | |||
| return nil, err | |||
| } | |||
| s.(*MemStore).sid = sid | |||
| p.lock.Lock() | |||
| defer p.lock.Unlock() | |||
| p.data[sid] = p.list.PushBack(s) | |||
| return s, nil | |||
| } | |||
| // Count counts and returns number of sessions. | |||
| func (p *MemProvider) Count() int { | |||
| return p.list.Len() | |||
| } | |||
| // GC calls GC to clean expired sessions. | |||
| func (p *MemProvider) GC() { | |||
| p.lock.RLock() | |||
| for { | |||
| // No session in the list. | |||
| e := p.list.Back() | |||
| if e == nil { | |||
| break | |||
| } | |||
| if (e.Value.(*MemStore).lastAccess.Unix() + p.maxLifetime) < time.Now().Unix() { | |||
| p.lock.RUnlock() | |||
| p.lock.Lock() | |||
| p.list.Remove(e) | |||
| delete(p.data, e.Value.(*MemStore).sid) | |||
| p.lock.Unlock() | |||
| p.lock.RLock() | |||
| } else { | |||
| break | |||
| } | |||
| } | |||
| p.lock.RUnlock() | |||
| } | |||
| func init() { | |||
| Register("memory", &MemProvider{list: list.New(), data: make(map[string]*list.Element)}) | |||
| } | |||
| @@ -0,0 +1,235 @@ | |||
| // Copyright 2013 Beego Authors | |||
| // Copyright 2014 The Macaron Authors | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"): you may | |||
| // not use this file except in compliance with the License. You may obtain | |||
| // a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
| // License for the specific language governing permissions and limitations | |||
| // under the License. | |||
| package session | |||
| import ( | |||
| "fmt" | |||
| "strings" | |||
| "sync" | |||
| "time" | |||
| "github.com/Unknwon/com" | |||
| "gopkg.in/ini.v1" | |||
| "gopkg.in/redis.v2" | |||
| "github.com/go-macaron/session" | |||
| ) | |||
| // RedisStore represents a redis session store implementation. | |||
| type RedisStore struct { | |||
| c *redis.Client | |||
| prefix, sid string | |||
| duration time.Duration | |||
| lock sync.RWMutex | |||
| data map[interface{}]interface{} | |||
| } | |||
| // NewRedisStore creates and returns a redis session store. | |||
| func NewRedisStore(c *redis.Client, prefix, sid string, dur time.Duration, kv map[interface{}]interface{}) *RedisStore { | |||
| return &RedisStore{ | |||
| c: c, | |||
| prefix: prefix, | |||
| sid: sid, | |||
| duration: dur, | |||
| data: kv, | |||
| } | |||
| } | |||
| // Set sets value to given key in session. | |||
| func (s *RedisStore) Set(key, val interface{}) error { | |||
| s.lock.Lock() | |||
| defer s.lock.Unlock() | |||
| s.data[key] = val | |||
| return nil | |||
| } | |||
| // Get gets value by given key in session. | |||
| func (s *RedisStore) Get(key interface{}) interface{} { | |||
| s.lock.RLock() | |||
| defer s.lock.RUnlock() | |||
| return s.data[key] | |||
| } | |||
| // Delete delete a key from session. | |||
| func (s *RedisStore) Delete(key interface{}) error { | |||
| s.lock.Lock() | |||
| defer s.lock.Unlock() | |||
| delete(s.data, key) | |||
| return nil | |||
| } | |||
| // ID returns current session ID. | |||
| func (s *RedisStore) ID() string { | |||
| return s.sid | |||
| } | |||
| // Release releases resource and save data to provider. | |||
| func (s *RedisStore) Release() error { | |||
| data, err := session.EncodeGob(s.data) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| return s.c.SetEx(s.prefix+s.sid, s.duration, string(data)).Err() | |||
| } | |||
| // Flush deletes all session data. | |||
| func (s *RedisStore) Flush() error { | |||
| s.lock.Lock() | |||
| defer s.lock.Unlock() | |||
| s.data = make(map[interface{}]interface{}) | |||
| return nil | |||
| } | |||
| // RedisProvider represents a redis session provider implementation. | |||
| type RedisProvider struct { | |||
| c *redis.Client | |||
| duration time.Duration | |||
| prefix string | |||
| } | |||
| // Init initializes redis session provider. | |||
| // configs: network=tcp,addr=:6379,password=macaron,db=0,pool_size=100,idle_timeout=180,prefix=session; | |||
| func (p *RedisProvider) Init(maxlifetime int64, configs string) (err error) { | |||
| p.duration, err = time.ParseDuration(fmt.Sprintf("%ds", maxlifetime)) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| cfg, err := ini.Load([]byte(strings.Replace(configs, ",", "\n", -1))) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| opt := &redis.Options{ | |||
| Network: "tcp", | |||
| } | |||
| for k, v := range cfg.Section("").KeysHash() { | |||
| switch k { | |||
| case "network": | |||
| opt.Network = v | |||
| case "addr": | |||
| opt.Addr = v | |||
| case "password": | |||
| opt.Password = v | |||
| case "db": | |||
| opt.DB = com.StrTo(v).MustInt64() | |||
| case "pool_size": | |||
| opt.PoolSize = com.StrTo(v).MustInt() | |||
| case "idle_timeout": | |||
| opt.IdleTimeout, err = time.ParseDuration(v + "s") | |||
| if err != nil { | |||
| return fmt.Errorf("error parsing idle timeout: %v", err) | |||
| } | |||
| case "prefix": | |||
| p.prefix = v | |||
| default: | |||
| return fmt.Errorf("session/redis: unsupported option '%s'", k) | |||
| } | |||
| } | |||
| p.c = redis.NewClient(opt) | |||
| return p.c.Ping().Err() | |||
| } | |||
| // Read returns raw session store by session ID. | |||
| func (p *RedisProvider) Read(sid string) (session.RawStore, error) { | |||
| psid := p.prefix + sid | |||
| if !p.Exist(sid) { | |||
| if err := p.c.Set(psid, "").Err(); err != nil { | |||
| return nil, err | |||
| } | |||
| } | |||
| var kv map[interface{}]interface{} | |||
| kvs, err := p.c.Get(psid).Result() | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| if len(kvs) == 0 { | |||
| kv = make(map[interface{}]interface{}) | |||
| } else { | |||
| kv, err = session.DecodeGob([]byte(kvs)) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| } | |||
| return NewRedisStore(p.c, p.prefix, sid, p.duration, kv), nil | |||
| } | |||
| // Exist returns true if session with given ID exists. | |||
| func (p *RedisProvider) Exist(sid string) bool { | |||
| has, err := p.c.Exists(p.prefix + sid).Result() | |||
| return err == nil && has | |||
| } | |||
| // Destory deletes a session by session ID. | |||
| func (p *RedisProvider) Destory(sid string) error { | |||
| return p.c.Del(p.prefix + sid).Err() | |||
| } | |||
| // Regenerate regenerates a session store from old session ID to new one. | |||
| func (p *RedisProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err error) { | |||
| poldsid := p.prefix + oldsid | |||
| psid := p.prefix + sid | |||
| if p.Exist(sid) { | |||
| return nil, fmt.Errorf("new sid '%s' already exists", sid) | |||
| } else if !p.Exist(oldsid) { | |||
| // Make a fake old session. | |||
| if err = p.c.SetEx(poldsid, p.duration, "").Err(); err != nil { | |||
| return nil, err | |||
| } | |||
| } | |||
| if err = p.c.Rename(poldsid, psid).Err(); err != nil { | |||
| return nil, err | |||
| } | |||
| var kv map[interface{}]interface{} | |||
| kvs, err := p.c.Get(psid).Result() | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| if len(kvs) == 0 { | |||
| kv = make(map[interface{}]interface{}) | |||
| } else { | |||
| kv, err = session.DecodeGob([]byte(kvs)) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| } | |||
| return NewRedisStore(p.c, p.prefix, sid, p.duration, kv), nil | |||
| } | |||
| // Count counts and returns number of sessions. | |||
| func (p *RedisProvider) Count() int { | |||
| return int(p.c.DbSize().Val()) | |||
| } | |||
| // GC calls GC to clean expired sessions. | |||
| func (_ *RedisProvider) GC() {} | |||
| func init() { | |||
| session.Register("redis", &RedisProvider{}) | |||
| } | |||
| @@ -0,0 +1 @@ | |||
| ignore | |||
| @@ -0,0 +1,399 @@ | |||
| // Copyright 2013 Beego Authors | |||
| // Copyright 2014 The Macaron Authors | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"): you may | |||
| // not use this file except in compliance with the License. You may obtain | |||
| // a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
| // License for the specific language governing permissions and limitations | |||
| // under the License. | |||
| // Package session a middleware that provides the session management of Macaron. | |||
| package session | |||
| import ( | |||
| "encoding/hex" | |||
| "fmt" | |||
| "net/http" | |||
| "net/url" | |||
| "time" | |||
| "gopkg.in/macaron.v1" | |||
| ) | |||
| const _VERSION = "0.3.0" | |||
| func Version() string { | |||
| return _VERSION | |||
| } | |||
| // RawStore is the interface that operates the session data. | |||
| type RawStore interface { | |||
| // Set sets value to given key in session. | |||
| Set(interface{}, interface{}) error | |||
| // Get gets value by given key in session. | |||
| Get(interface{}) interface{} | |||
| // Delete deletes a key from session. | |||
| Delete(interface{}) error | |||
| // ID returns current session ID. | |||
| ID() string | |||
| // Release releases session resource and save data to provider. | |||
| Release() error | |||
| // Flush deletes all session data. | |||
| Flush() error | |||
| } | |||
| // Store is the interface that contains all data for one session process with specific ID. | |||
| type Store interface { | |||
| RawStore | |||
| // Read returns raw session store by session ID. | |||
| Read(string) (RawStore, error) | |||
| // Destory deletes a session. | |||
| Destory(*macaron.Context) error | |||
| // RegenerateId regenerates a session store from old session ID to new one. | |||
| RegenerateId(*macaron.Context) (RawStore, error) | |||
| // Count counts and returns number of sessions. | |||
| Count() int | |||
| // GC calls GC to clean expired sessions. | |||
| GC() | |||
| } | |||
| type store struct { | |||
| RawStore | |||
| *Manager | |||
| } | |||
| var _ Store = &store{} | |||
| // Options represents a struct for specifying configuration options for the session middleware. | |||
| type Options struct { | |||
| // Name of provider. Default is "memory". | |||
| Provider string | |||
| // Provider configuration, it's corresponding to provider. | |||
| ProviderConfig string | |||
| // Cookie name to save session ID. Default is "MacaronSession". | |||
| CookieName string | |||
| // Cookie path to store. Default is "/". | |||
| CookiePath string | |||
| // GC interval time in seconds. Default is 3600. | |||
| Gclifetime int64 | |||
| // Max life time in seconds. Default is whatever GC interval time is. | |||
| Maxlifetime int64 | |||
| // Use HTTPS only. Default is false. | |||
| Secure bool | |||
| // Cookie life time. Default is 0. | |||
| CookieLifeTime int | |||
| // Cookie domain name. Default is empty. | |||
| Domain string | |||
| // Session ID length. Default is 16. | |||
| IDLength int | |||
| // Configuration section name. Default is "session". | |||
| Section string | |||
| } | |||
| func prepareOptions(options []Options) Options { | |||
| var opt Options | |||
| if len(options) > 0 { | |||
| opt = options[0] | |||
| } | |||
| if len(opt.Section) == 0 { | |||
| opt.Section = "session" | |||
| } | |||
| sec := macaron.Config().Section(opt.Section) | |||
| if len(opt.Provider) == 0 { | |||
| opt.Provider = sec.Key("PROVIDER").MustString("memory") | |||
| } | |||
| if len(opt.ProviderConfig) == 0 { | |||
| opt.ProviderConfig = sec.Key("PROVIDER_CONFIG").MustString("data/sessions") | |||
| } | |||
| if len(opt.CookieName) == 0 { | |||
| opt.CookieName = sec.Key("COOKIE_NAME").MustString("MacaronSession") | |||
| } | |||
| if len(opt.CookiePath) == 0 { | |||
| opt.CookiePath = sec.Key("COOKIE_PATH").MustString("/") | |||
| } | |||
| if opt.Gclifetime == 0 { | |||
| opt.Gclifetime = sec.Key("GC_INTERVAL_TIME").MustInt64(3600) | |||
| } | |||
| if opt.Maxlifetime == 0 { | |||
| opt.Maxlifetime = sec.Key("MAX_LIFE_TIME").MustInt64(opt.Gclifetime) | |||
| } | |||
| if !opt.Secure { | |||
| opt.Secure = sec.Key("SECURE").MustBool() | |||
| } | |||
| if opt.CookieLifeTime == 0 { | |||
| opt.CookieLifeTime = sec.Key("COOKIE_LIFE_TIME").MustInt() | |||
| } | |||
| if len(opt.Domain) == 0 { | |||
| opt.Domain = sec.Key("DOMAIN").String() | |||
| } | |||
| if opt.IDLength == 0 { | |||
| opt.IDLength = sec.Key("ID_LENGTH").MustInt(16) | |||
| } | |||
| return opt | |||
| } | |||
| // Sessioner is a middleware that maps a session.SessionStore service into the Macaron handler chain. | |||
| // An single variadic session.Options struct can be optionally provided to configure. | |||
| func Sessioner(options ...Options) macaron.Handler { | |||
| opt := prepareOptions(options) | |||
| manager, err := NewManager(opt.Provider, opt) | |||
| if err != nil { | |||
| panic(err) | |||
| } | |||
| go manager.startGC() | |||
| return func(ctx *macaron.Context) { | |||
| sess, err := manager.Start(ctx) | |||
| if err != nil { | |||
| panic("session(start): " + err.Error()) | |||
| } | |||
| // Get flash. | |||
| vals, _ := url.ParseQuery(ctx.GetCookie("macaron_flash")) | |||
| if len(vals) > 0 { | |||
| f := &Flash{Values: vals} | |||
| f.ErrorMsg = f.Get("error") | |||
| f.SuccessMsg = f.Get("success") | |||
| f.InfoMsg = f.Get("info") | |||
| f.WarningMsg = f.Get("warning") | |||
| ctx.Data["Flash"] = f | |||
| ctx.SetCookie("macaron_flash", "", -1, opt.CookiePath) | |||
| } | |||
| f := &Flash{ctx, url.Values{}, "", "", "", ""} | |||
| ctx.Resp.Before(func(macaron.ResponseWriter) { | |||
| if flash := f.Encode(); len(flash) > 0 { | |||
| ctx.SetCookie("macaron_flash", flash, 0, opt.CookiePath) | |||
| } | |||
| }) | |||
| ctx.Map(f) | |||
| s := store{ | |||
| RawStore: sess, | |||
| Manager: manager, | |||
| } | |||
| ctx.MapTo(s, (*Store)(nil)) | |||
| ctx.Next() | |||
| if err = sess.Release(); err != nil { | |||
| panic("session(release): " + err.Error()) | |||
| } | |||
| } | |||
| } | |||
| // Provider is the interface that provides session manipulations. | |||
| type Provider interface { | |||
| // Init initializes session provider. | |||
| Init(gclifetime int64, config string) error | |||
| // Read returns raw session store by session ID. | |||
| Read(sid string) (RawStore, error) | |||
| // Exist returns true if session with given ID exists. | |||
| Exist(sid string) bool | |||
| // Destory deletes a session by session ID. | |||
| Destory(sid string) error | |||
| // Regenerate regenerates a session store from old session ID to new one. | |||
| Regenerate(oldsid, sid string) (RawStore, error) | |||
| // Count counts and returns number of sessions. | |||
| Count() int | |||
| // GC calls GC to clean expired sessions. | |||
| GC() | |||
| } | |||
| var providers = make(map[string]Provider) | |||
| // Register registers a provider. | |||
| func Register(name string, provider Provider) { | |||
| if provider == nil { | |||
| panic("session: cannot register provider with nil value") | |||
| } | |||
| if _, dup := providers[name]; dup { | |||
| panic(fmt.Errorf("session: cannot register provider '%s' twice", name)) | |||
| } | |||
| providers[name] = provider | |||
| } | |||
| // _____ | |||
| // / \ _____ ____ _____ ____ ___________ | |||
| // / \ / \\__ \ / \\__ \ / ___\_/ __ \_ __ \ | |||
| // / Y \/ __ \| | \/ __ \_/ /_/ > ___/| | \/ | |||
| // \____|__ (____ /___| (____ /\___ / \___ >__| | |||
| // \/ \/ \/ \//_____/ \/ | |||
| // Manager represents a struct that contains session provider and its configuration. | |||
| type Manager struct { | |||
| provider Provider | |||
| opt Options | |||
| } | |||
| // NewManager creates and returns a new session manager by given provider name and configuration. | |||
| // It panics when given provider isn't registered. | |||
| func NewManager(name string, opt Options) (*Manager, error) { | |||
| p, ok := providers[name] | |||
| if !ok { | |||
| return nil, fmt.Errorf("session: unknown provider '%s'(forgotten import?)", name) | |||
| } | |||
| return &Manager{p, opt}, p.Init(opt.Maxlifetime, opt.ProviderConfig) | |||
| } | |||
| // sessionId generates a new session ID with rand string, unix nano time, remote addr by hash function. | |||
| func (m *Manager) sessionId() string { | |||
| return hex.EncodeToString(generateRandomKey(m.opt.IDLength / 2)) | |||
| } | |||
| // Start starts a session by generating new one | |||
| // or retrieve existence one by reading session ID from HTTP request if it's valid. | |||
| func (m *Manager) Start(ctx *macaron.Context) (RawStore, error) { | |||
| sid := ctx.GetCookie(m.opt.CookieName) | |||
| if len(sid) > 0 && m.provider.Exist(sid) { | |||
| return m.provider.Read(sid) | |||
| } | |||
| sid = m.sessionId() | |||
| sess, err := m.provider.Read(sid) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| cookie := &http.Cookie{ | |||
| Name: m.opt.CookieName, | |||
| Value: sid, | |||
| Path: m.opt.CookiePath, | |||
| HttpOnly: true, | |||
| Secure: m.opt.Secure, | |||
| Domain: m.opt.Domain, | |||
| } | |||
| if m.opt.CookieLifeTime >= 0 { | |||
| cookie.MaxAge = m.opt.CookieLifeTime | |||
| } | |||
| http.SetCookie(ctx.Resp, cookie) | |||
| ctx.Req.AddCookie(cookie) | |||
| return sess, nil | |||
| } | |||
| // Read returns raw session store by session ID. | |||
| func (m *Manager) Read(sid string) (RawStore, error) { | |||
| return m.provider.Read(sid) | |||
| } | |||
| // Destory deletes a session by given ID. | |||
| func (m *Manager) Destory(ctx *macaron.Context) error { | |||
| sid := ctx.GetCookie(m.opt.CookieName) | |||
| if len(sid) == 0 { | |||
| return nil | |||
| } | |||
| if err := m.provider.Destory(sid); err != nil { | |||
| return err | |||
| } | |||
| cookie := &http.Cookie{ | |||
| Name: m.opt.CookieName, | |||
| Path: m.opt.CookiePath, | |||
| HttpOnly: true, | |||
| Expires: time.Now(), | |||
| MaxAge: -1, | |||
| } | |||
| http.SetCookie(ctx.Resp, cookie) | |||
| return nil | |||
| } | |||
| // RegenerateId regenerates a session store from old session ID to new one. | |||
| func (m *Manager) RegenerateId(ctx *macaron.Context) (sess RawStore, err error) { | |||
| sid := m.sessionId() | |||
| oldsid := ctx.GetCookie(m.opt.CookieName) | |||
| sess, err = m.provider.Regenerate(oldsid, sid) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| ck := &http.Cookie{ | |||
| Name: m.opt.CookieName, | |||
| Value: sid, | |||
| Path: m.opt.CookiePath, | |||
| HttpOnly: true, | |||
| Secure: m.opt.Secure, | |||
| Domain: m.opt.Domain, | |||
| } | |||
| if m.opt.CookieLifeTime >= 0 { | |||
| ck.MaxAge = m.opt.CookieLifeTime | |||
| } | |||
| http.SetCookie(ctx.Resp, ck) | |||
| ctx.Req.AddCookie(ck) | |||
| return sess, nil | |||
| } | |||
| // Count counts and returns number of sessions. | |||
| func (m *Manager) Count() int { | |||
| return m.provider.Count() | |||
| } | |||
| // GC starts GC job in a certain period. | |||
| func (m *Manager) GC() { | |||
| m.provider.GC() | |||
| } | |||
| // startGC starts GC job in a certain period. | |||
| func (m *Manager) startGC() { | |||
| m.GC() | |||
| time.AfterFunc(time.Duration(m.opt.Gclifetime)*time.Second, func() { m.startGC() }) | |||
| } | |||
| // SetSecure indicates whether to set cookie with HTTPS or not. | |||
| func (m *Manager) SetSecure(secure bool) { | |||
| m.opt.Secure = secure | |||
| } | |||
| // ___________.____ _____ _________ ___ ___ | |||
| // \_ _____/| | / _ \ / _____// | \ | |||
| // | __) | | / /_\ \ \_____ \/ ~ \ | |||
| // | \ | |___/ | \/ \ Y / | |||
| // \___ / |_______ \____|__ /_______ /\___|_ / | |||
| // \/ \/ \/ \/ \/ | |||
| type Flash struct { | |||
| ctx *macaron.Context | |||
| url.Values | |||
| ErrorMsg, WarningMsg, InfoMsg, SuccessMsg string | |||
| } | |||
| func (f *Flash) set(name, msg string, current ...bool) { | |||
| isShow := false | |||
| if (len(current) == 0 && macaron.FlashNow) || | |||
| (len(current) > 0 && current[0]) { | |||
| isShow = true | |||
| } | |||
| if isShow { | |||
| f.ctx.Data["Flash"] = f | |||
| } else { | |||
| f.Set(name, msg) | |||
| } | |||
| } | |||
| func (f *Flash) Error(msg string, current ...bool) { | |||
| f.ErrorMsg = msg | |||
| f.set("error", msg, current...) | |||
| } | |||
| func (f *Flash) Warning(msg string, current ...bool) { | |||
| f.WarningMsg = msg | |||
| f.set("warning", msg, current...) | |||
| } | |||
| func (f *Flash) Info(msg string, current ...bool) { | |||
| f.InfoMsg = msg | |||
| f.set("info", msg, current...) | |||
| } | |||
| func (f *Flash) Success(msg string, current ...bool) { | |||
| f.SuccessMsg = msg | |||
| f.set("success", msg, current...) | |||
| } | |||
| @@ -0,0 +1,60 @@ | |||
| // Copyright 2013 Beego Authors | |||
| // Copyright 2014 The Macaron Authors | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"): you may | |||
| // not use this file except in compliance with the License. You may obtain | |||
| // a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
| // License for the specific language governing permissions and limitations | |||
| // under the License. | |||
| package session | |||
| import ( | |||
| "bytes" | |||
| "crypto/rand" | |||
| "encoding/gob" | |||
| "io" | |||
| "github.com/Unknwon/com" | |||
| ) | |||
| func init() { | |||
| gob.Register([]interface{}{}) | |||
| gob.Register(map[int]interface{}{}) | |||
| gob.Register(map[string]interface{}{}) | |||
| gob.Register(map[interface{}]interface{}{}) | |||
| gob.Register(map[string]string{}) | |||
| gob.Register(map[int]string{}) | |||
| gob.Register(map[int]int{}) | |||
| gob.Register(map[int]int64{}) | |||
| } | |||
| func EncodeGob(obj map[interface{}]interface{}) ([]byte, error) { | |||
| for _, v := range obj { | |||
| gob.Register(v) | |||
| } | |||
| buf := bytes.NewBuffer(nil) | |||
| err := gob.NewEncoder(buf).Encode(obj) | |||
| return buf.Bytes(), err | |||
| } | |||
| func DecodeGob(encoded []byte) (out map[interface{}]interface{}, err error) { | |||
| buf := bytes.NewBuffer(encoded) | |||
| err = gob.NewDecoder(buf).Decode(&out) | |||
| return out, err | |||
| } | |||
| // generateRandomKey creates a random key with the given strength. | |||
| func generateRandomKey(strength int) []byte { | |||
| k := make([]byte, strength) | |||
| if n, err := io.ReadFull(rand.Reader, k); n != strength || err != nil { | |||
| return com.RandomCreateBytes(strength) | |||
| } | |||
| return k | |||
| } | |||
| @@ -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,110 @@ | |||
| toolbox | |||
| ======= | |||
| Middleware toolbox provides health chcek, pprof, profile and statistic services for [Macaron](https://github.com/go-macaron/macaron). | |||
| [API Reference](https://gowalker.org/github.com/go-macaron/toolbox) | |||
| ### Installation | |||
| go get github.com/go-macaron/toolbox | |||
| ## Usage | |||
| ```go | |||
| // main.go | |||
| import ( | |||
| "gopkg.in/macaron.v1" | |||
| "github.com/go-macaron/toolbox" | |||
| ) | |||
| func main() { | |||
| m := macaron.Classic() | |||
| m.Use(toolbox.Toolboxer(m)) | |||
| m.Run() | |||
| } | |||
| ``` | |||
| Open your browser and visit `http://localhost:4000/debug` to see the effects. | |||
| ## Options | |||
| `toolbox.Toolboxer` comes with a variety of configuration options: | |||
| ```go | |||
| type dummyChecker struct { | |||
| } | |||
| func (dc *dummyChecker) Desc() string { | |||
| return "Dummy checker" | |||
| } | |||
| func (dc *dummyChecker) Check() error { | |||
| return nil | |||
| } | |||
| // ... | |||
| m.Use(toolbox.Toolboxer(m, toolbox.Options{ | |||
| URLPrefix: "/debug", // URL prefix for toolbox dashboard. | |||
| HealthCheckURL: "/healthcheck", // URL for health check request. | |||
| HealthCheckers: []HealthChecker{ | |||
| new(dummyChecker), | |||
| }, // Health checkers. | |||
| HealthCheckFuncs: []*toolbox.HealthCheckFuncDesc{ | |||
| &toolbox.HealthCheckFuncDesc{ | |||
| Desc: "Database connection", | |||
| Func: func() error { return "OK" }, | |||
| }, | |||
| }, // Health check functions. | |||
| PprofURLPrefix: "/debug/pprof/", // URL prefix of pprof. | |||
| ProfileURLPrefix: "/debug/profile/", // URL prefix of profile. | |||
| ProfilePath: "profile", // Path store profile files. | |||
| })) | |||
| // ... | |||
| ``` | |||
| ## Route Statistic | |||
| Toolbox also comes with a route call statistic functionality: | |||
| ```go | |||
| import ( | |||
| "os" | |||
| "time" | |||
| //... | |||
| "github.com/go-macaron/toolbox" | |||
| ) | |||
| func main() { | |||
| //... | |||
| m.Get("/", func(t toolbox.Toolbox) { | |||
| start := time.Now() | |||
| // Other operations. | |||
| t.AddStatistics("GET", "/", time.Since(start)) | |||
| }) | |||
| m.Get("/dump", func(t toolbox.Toolbox) { | |||
| t.GetMap(os.Stdout) | |||
| }) | |||
| } | |||
| ``` | |||
| Output take from test: | |||
| ``` | |||
| +---------------------------------------------------+------------+------------------+------------------+------------------+------------------+------------------+ | |||
| | Request URL | Method | Times | Total Used(s) | Max Used(μs) | Min Used(μs) | Avg Used(μs) | | |||
| +---------------------------------------------------+------------+------------------+------------------+------------------+------------------+------------------+ | |||
| | /api/user | POST | 2 | 0.000122 | 120.000000 | 2.000000 | 61.000000 | | |||
| | /api/user | GET | 1 | 0.000013 | 13.000000 | 13.000000 | 13.000000 | | |||
| | /api/user | DELETE | 1 | 0.000001 | 1.400000 | 1.400000 | 1.400000 | | |||
| | /api/admin | POST | 1 | 0.000014 | 14.000000 | 14.000000 | 14.000000 | | |||
| | /api/user/unknwon | POST | 1 | 0.000012 | 12.000000 | 12.000000 | 12.000000 | | |||
| +---------------------------------------------------+------------+------------------+------------------+------------------+------------------+------------------+ | |||
| ``` | |||
| ## License | |||
| This project is under Apache v2 License. See the [LICENSE](LICENSE) file for the full license text. | |||
| @@ -0,0 +1,83 @@ | |||
| // Copyright 2013 Beego Authors | |||
| // Copyright 2014 The Macaron Authors | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"): you may | |||
| // not use this file except in compliance with the License. You may obtain | |||
| // a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
| // License for the specific language governing permissions and limitations | |||
| // under the License. | |||
| package toolbox | |||
| import ( | |||
| "bytes" | |||
| ) | |||
| // HealthChecker represents a health check instance. | |||
| type HealthChecker interface { | |||
| Desc() string | |||
| Check() error | |||
| } | |||
| // HealthCheckFunc represents a callable function for health check. | |||
| type HealthCheckFunc func() error | |||
| // HealthCheckFunc represents a callable function for health check with description. | |||
| type HealthCheckFuncDesc struct { | |||
| Desc string | |||
| Func HealthCheckFunc | |||
| } | |||
| type healthCheck struct { | |||
| desc string | |||
| HealthChecker | |||
| check HealthCheckFunc // Not nil if add job as a function. | |||
| } | |||
| // AddHealthCheck adds new health check job. | |||
| func (t *toolbox) AddHealthCheck(hc HealthChecker) { | |||
| t.healthCheckJobs = append(t.healthCheckJobs, &healthCheck{ | |||
| HealthChecker: hc, | |||
| }) | |||
| } | |||
| // AddHealthCheckFunc adds a function as a new health check job. | |||
| func (t *toolbox) AddHealthCheckFunc(desc string, fn HealthCheckFunc) { | |||
| t.healthCheckJobs = append(t.healthCheckJobs, &healthCheck{ | |||
| desc: desc, | |||
| check: fn, | |||
| }) | |||
| } | |||
| func (t *toolbox) handleHealthCheck() string { | |||
| if len(t.healthCheckJobs) == 0 { | |||
| return "no health check jobs" | |||
| } | |||
| var buf bytes.Buffer | |||
| var err error | |||
| for _, job := range t.healthCheckJobs { | |||
| buf.WriteString("* ") | |||
| if job.check != nil { | |||
| buf.WriteString(job.desc) | |||
| err = job.check() | |||
| } else { | |||
| buf.WriteString(job.Desc()) | |||
| err = job.Check() | |||
| } | |||
| buf.WriteString(": ") | |||
| if err == nil { | |||
| buf.WriteString("OK") | |||
| } else { | |||
| buf.WriteString(err.Error()) | |||
| } | |||
| buf.WriteString("\n") | |||
| } | |||
| return buf.String() | |||
| } | |||
| @@ -0,0 +1,163 @@ | |||
| // Copyright 2013 Beego Authors | |||
| // Copyright 2014 The Macaron Authors | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"): you may | |||
| // not use this file except in compliance with the License. You may obtain | |||
| // a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
| // License for the specific language governing permissions and limitations | |||
| // under the License. | |||
| package toolbox | |||
| import ( | |||
| "bytes" | |||
| "errors" | |||
| "fmt" | |||
| "io" | |||
| "os" | |||
| "path" | |||
| "runtime" | |||
| "runtime/debug" | |||
| "runtime/pprof" | |||
| "time" | |||
| "github.com/Unknwon/com" | |||
| "gopkg.in/macaron.v1" | |||
| ) | |||
| var ( | |||
| profilePath string | |||
| pid int | |||
| startTime = time.Now() | |||
| inCPUProfile bool | |||
| ) | |||
| // StartCPUProfile starts CPU profile monitor. | |||
| func StartCPUProfile() error { | |||
| if inCPUProfile { | |||
| return errors.New("CPU profile has alreday been started!") | |||
| } | |||
| inCPUProfile = true | |||
| os.MkdirAll(profilePath, os.ModePerm) | |||
| f, err := os.Create(path.Join(profilePath, "cpu-"+com.ToStr(pid)+".pprof")) | |||
| if err != nil { | |||
| panic("fail to record CPU profile: " + err.Error()) | |||
| } | |||
| pprof.StartCPUProfile(f) | |||
| return nil | |||
| } | |||
| // StopCPUProfile stops CPU profile monitor. | |||
| func StopCPUProfile() error { | |||
| if !inCPUProfile { | |||
| return errors.New("CPU profile hasn't been started!") | |||
| } | |||
| pprof.StopCPUProfile() | |||
| inCPUProfile = false | |||
| return nil | |||
| } | |||
| func init() { | |||
| pid = os.Getpid() | |||
| } | |||
| // DumpMemProf dumps memory profile in pprof. | |||
| func DumpMemProf(w io.Writer) { | |||
| pprof.WriteHeapProfile(w) | |||
| } | |||
| func dumpMemProf() { | |||
| os.MkdirAll(profilePath, os.ModePerm) | |||
| f, err := os.Create(path.Join(profilePath, "mem-"+com.ToStr(pid)+".memprof")) | |||
| if err != nil { | |||
| panic("fail to record memory profile: " + err.Error()) | |||
| } | |||
| runtime.GC() | |||
| DumpMemProf(f) | |||
| f.Close() | |||
| } | |||
| func avg(items []time.Duration) time.Duration { | |||
| var sum time.Duration | |||
| for _, item := range items { | |||
| sum += item | |||
| } | |||
| return time.Duration(int64(sum) / int64(len(items))) | |||
| } | |||
| func dumpGC(memStats *runtime.MemStats, gcstats *debug.GCStats, w io.Writer) { | |||
| if gcstats.NumGC > 0 { | |||
| lastPause := gcstats.Pause[0] | |||
| elapsed := time.Now().Sub(startTime) | |||
| overhead := float64(gcstats.PauseTotal) / float64(elapsed) * 100 | |||
| allocatedRate := float64(memStats.TotalAlloc) / elapsed.Seconds() | |||
| fmt.Fprintf(w, "NumGC:%d Pause:%s Pause(Avg):%s Overhead:%3.2f%% Alloc:%s Sys:%s Alloc(Rate):%s/s Histogram:%s %s %s \n", | |||
| gcstats.NumGC, | |||
| com.ToStr(lastPause), | |||
| com.ToStr(avg(gcstats.Pause)), | |||
| overhead, | |||
| com.HumaneFileSize(memStats.Alloc), | |||
| com.HumaneFileSize(memStats.Sys), | |||
| com.HumaneFileSize(uint64(allocatedRate)), | |||
| com.ToStr(gcstats.PauseQuantiles[94]), | |||
| com.ToStr(gcstats.PauseQuantiles[98]), | |||
| com.ToStr(gcstats.PauseQuantiles[99])) | |||
| } else { | |||
| // while GC has disabled | |||
| elapsed := time.Now().Sub(startTime) | |||
| allocatedRate := float64(memStats.TotalAlloc) / elapsed.Seconds() | |||
| fmt.Fprintf(w, "Alloc:%s Sys:%s Alloc(Rate):%s/s\n", | |||
| com.HumaneFileSize(memStats.Alloc), | |||
| com.HumaneFileSize(memStats.Sys), | |||
| com.HumaneFileSize(uint64(allocatedRate))) | |||
| } | |||
| } | |||
| // DumpGCSummary dumps GC information to io.Writer | |||
| func DumpGCSummary(w io.Writer) { | |||
| memStats := &runtime.MemStats{} | |||
| runtime.ReadMemStats(memStats) | |||
| gcstats := &debug.GCStats{PauseQuantiles: make([]time.Duration, 100)} | |||
| debug.ReadGCStats(gcstats) | |||
| dumpGC(memStats, gcstats, w) | |||
| } | |||
| func handleProfile(ctx *macaron.Context) string { | |||
| switch ctx.Query("op") { | |||
| case "startcpu": | |||
| if err := StartCPUProfile(); err != nil { | |||
| return err.Error() | |||
| } | |||
| case "stopcpu": | |||
| if err := StopCPUProfile(); err != nil { | |||
| return err.Error() | |||
| } | |||
| case "mem": | |||
| dumpMemProf() | |||
| case "gc": | |||
| var buf bytes.Buffer | |||
| DumpGCSummary(&buf) | |||
| return string(buf.Bytes()) | |||
| default: | |||
| return fmt.Sprintf(`<p>Available operations:</p> | |||
| <ol> | |||
| <li><a href="%[1]s?op=startcpu">Start CPU profile</a></li> | |||
| <li><a href="%[1]s?op=stopcpu">Stop CPU profile</a></li> | |||
| <li><a href="%[1]s?op=mem">Dump memory profile</a></li> | |||
| <li><a href="%[1]s?op=gc">Dump GC summary</a></li> | |||
| </ol>`, opt.ProfileURLPrefix) | |||
| } | |||
| ctx.Redirect(opt.ProfileURLPrefix) | |||
| return "" | |||
| } | |||
| @@ -0,0 +1,138 @@ | |||
| // Copyright 2013 Beego Authors | |||
| // Copyright 2014 The Macaron Authors | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"): you may | |||
| // not use this file except in compliance with the License. You may obtain | |||
| // a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
| // License for the specific language governing permissions and limitations | |||
| // under the License. | |||
| package toolbox | |||
| import ( | |||
| "encoding/json" | |||
| "fmt" | |||
| "io" | |||
| "strings" | |||
| "sync" | |||
| "time" | |||
| ) | |||
| // Statistics struct | |||
| type Statistics struct { | |||
| RequestUrl string | |||
| RequestNum int64 | |||
| MinTime time.Duration | |||
| MaxTime time.Duration | |||
| TotalTime time.Duration | |||
| } | |||
| // UrlMap contains several statistics struct to log different data | |||
| type UrlMap struct { | |||
| lock sync.RWMutex | |||
| LengthLimit int // limit the urlmap's length if it's equal to 0 there's no limit | |||
| urlmap map[string]map[string]*Statistics | |||
| } | |||
| // add statistics task. | |||
| // it needs request method, request url and statistics time duration | |||
| func (m *UrlMap) AddStatistics(requestMethod, requestUrl string, requesttime time.Duration) { | |||
| m.lock.Lock() | |||
| defer m.lock.Unlock() | |||
| if method, ok := m.urlmap[requestUrl]; ok { | |||
| if s, ok := method[requestMethod]; ok { | |||
| s.RequestNum += 1 | |||
| if s.MaxTime < requesttime { | |||
| s.MaxTime = requesttime | |||
| } | |||
| if s.MinTime > requesttime { | |||
| s.MinTime = requesttime | |||
| } | |||
| s.TotalTime += requesttime | |||
| } else { | |||
| nb := &Statistics{ | |||
| RequestUrl: requestUrl, | |||
| RequestNum: 1, | |||
| MinTime: requesttime, | |||
| MaxTime: requesttime, | |||
| TotalTime: requesttime, | |||
| } | |||
| m.urlmap[requestUrl][requestMethod] = nb | |||
| } | |||
| } else { | |||
| if m.LengthLimit > 0 && m.LengthLimit <= len(m.urlmap) { | |||
| return | |||
| } | |||
| methodmap := make(map[string]*Statistics) | |||
| nb := &Statistics{ | |||
| RequestUrl: requestUrl, | |||
| RequestNum: 1, | |||
| MinTime: requesttime, | |||
| MaxTime: requesttime, | |||
| TotalTime: requesttime, | |||
| } | |||
| methodmap[requestMethod] = nb | |||
| m.urlmap[requestUrl] = methodmap | |||
| } | |||
| } | |||
| // put url statistics result in io.Writer | |||
| func (m *UrlMap) GetMap(w io.Writer) { | |||
| m.lock.RLock() | |||
| defer m.lock.RUnlock() | |||
| sep := fmt.Sprintf("+%s+%s+%s+%s+%s+%s+%s+\n", strings.Repeat("-", 51), strings.Repeat("-", 12), | |||
| strings.Repeat("-", 18), strings.Repeat("-", 18), strings.Repeat("-", 18), strings.Repeat("-", 18), strings.Repeat("-", 18)) | |||
| fmt.Fprintf(w, sep) | |||
| fmt.Fprintf(w, "| % -50s| % -10s | % -16s | % -16s | % -16s | % -16s | % -16s |\n", "Request URL", "Method", "Times", "Total Used(s)", "Max Used(μs)", "Min Used(μs)", "Avg Used(μs)") | |||
| fmt.Fprintf(w, sep) | |||
| for k, v := range m.urlmap { | |||
| for kk, vv := range v { | |||
| fmt.Fprintf(w, "| % -50s| % -10s | % 16d | % 16f | % 16.6f | % 16.6f | % 16.6f |\n", k, | |||
| kk, vv.RequestNum, vv.TotalTime.Seconds(), float64(vv.MaxTime.Nanoseconds())/1000, | |||
| float64(vv.MinTime.Nanoseconds())/1000, float64(time.Duration(int64(vv.TotalTime)/vv.RequestNum).Nanoseconds())/1000, | |||
| ) | |||
| } | |||
| } | |||
| fmt.Fprintf(w, sep) | |||
| } | |||
| type URLMapInfo struct { | |||
| URL string `json:"url"` | |||
| Method string `json:"method"` | |||
| Times int64 `json:"times"` | |||
| TotalUsed float64 `json:"total_used"` | |||
| MaxUsed float64 `json:"max_used"` | |||
| MinUsed float64 `json:"min_used"` | |||
| AvgUsed float64 `json:"avg_used"` | |||
| } | |||
| func (m *UrlMap) JSON(w io.Writer) { | |||
| infos := make([]*URLMapInfo, 0, len(m.urlmap)) | |||
| for k, v := range m.urlmap { | |||
| for kk, vv := range v { | |||
| infos = append(infos, &URLMapInfo{ | |||
| URL: k, | |||
| Method: kk, | |||
| Times: vv.RequestNum, | |||
| TotalUsed: vv.TotalTime.Seconds(), | |||
| MaxUsed: float64(vv.MaxTime.Nanoseconds()) / 1000, | |||
| MinUsed: float64(vv.MinTime.Nanoseconds()) / 1000, | |||
| AvgUsed: float64(time.Duration(int64(vv.TotalTime)/vv.RequestNum).Nanoseconds()) / 1000, | |||
| }) | |||
| } | |||
| } | |||
| if err := json.NewEncoder(w).Encode(infos); err != nil { | |||
| panic("URLMap.JSON: " + err.Error()) | |||
| } | |||
| } | |||
| @@ -0,0 +1,154 @@ | |||
| // Copyright 2014 The Macaron Authors | |||
| // | |||
| // Licensed under the Apache License, Version 2.0 (the "License"): you may | |||
| // not use this file except in compliance with the License. You may obtain | |||
| // a copy of the License at | |||
| // | |||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||
| // | |||
| // Unless required by applicable law or agreed to in writing, software | |||
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
| // License for the specific language governing permissions and limitations | |||
| // under the License. | |||
| // Package toolbox is a middleware that provides health check, pprof, profile and statistic services for Macaron. | |||
| package toolbox | |||
| import ( | |||
| "fmt" | |||
| "io" | |||
| "net/http" | |||
| "net/http/pprof" | |||
| "path" | |||
| "time" | |||
| "gopkg.in/macaron.v1" | |||
| ) | |||
| const _VERSION = "0.1.2" | |||
| func Version() string { | |||
| return _VERSION | |||
| } | |||
| // Toolbox represents a tool box service for Macaron instance. | |||
| type Toolbox interface { | |||
| AddHealthCheck(HealthChecker) | |||
| AddHealthCheckFunc(string, HealthCheckFunc) | |||
| AddStatistics(string, string, time.Duration) | |||
| GetMap(io.Writer) | |||
| JSON(io.Writer) | |||
| } | |||
| type toolbox struct { | |||
| *UrlMap | |||
| healthCheckJobs []*healthCheck | |||
| } | |||
| // Options represents a struct for specifying configuration options for the Toolbox middleware. | |||
| type Options struct { | |||
| // URL prefix for toolbox dashboard. Default is "/debug". | |||
| URLPrefix string | |||
| // URL for health check request. Default is "/healthcheck". | |||
| HealthCheckURL string | |||
| // Health checkers. | |||
| HealthCheckers []HealthChecker | |||
| // Health check functions. | |||
| HealthCheckFuncs []*HealthCheckFuncDesc | |||
| // URL for URL map json. Default is "/urlmap.json". | |||
| URLMapPrefix string | |||
| // URL prefix of pprof. Default is "/debug/pprof/". | |||
| PprofURLPrefix string | |||
| // URL prefix of profile. Default is "/debug/profile/". | |||
| ProfileURLPrefix string | |||
| // Path store profile files. Default is "profile". | |||
| ProfilePath string | |||
| } | |||
| var opt Options | |||
| func prepareOptions(options []Options) { | |||
| if len(options) > 0 { | |||
| opt = options[0] | |||
| } | |||
| // Defaults. | |||
| if len(opt.URLPrefix) == 0 { | |||
| opt.URLPrefix = "/debug" | |||
| } | |||
| if len(opt.HealthCheckURL) == 0 { | |||
| opt.HealthCheckURL = "/healthcheck" | |||
| } | |||
| if len(opt.URLMapPrefix) == 0 { | |||
| opt.URLMapPrefix = "/urlmap.json" | |||
| } | |||
| if len(opt.PprofURLPrefix) == 0 { | |||
| opt.PprofURLPrefix = "/debug/pprof/" | |||
| } else if opt.PprofURLPrefix[len(opt.PprofURLPrefix)-1] != '/' { | |||
| opt.PprofURLPrefix += "/" | |||
| } | |||
| if len(opt.ProfileURLPrefix) == 0 { | |||
| opt.ProfileURLPrefix = "/debug/profile/" | |||
| } else if opt.ProfileURLPrefix[len(opt.ProfileURLPrefix)-1] != '/' { | |||
| opt.ProfileURLPrefix += "/" | |||
| } | |||
| if len(opt.ProfilePath) == 0 { | |||
| opt.ProfilePath = path.Join(macaron.Root, "profile") | |||
| } | |||
| } | |||
| func dashboard(ctx *macaron.Context) string { | |||
| return fmt.Sprintf(`<p>Toolbox Index:</p> | |||
| <ol> | |||
| <li><a href="%s">Pprof Information</a></li> | |||
| <li><a href="%s">Profile Operations</a></li> | |||
| </ol>`, opt.PprofURLPrefix, opt.ProfileURLPrefix) | |||
| } | |||
| var _ Toolbox = &toolbox{} | |||
| // Toolboxer is a middleware provides health check, pprof, profile and statistic services for your application. | |||
| func Toolboxer(m *macaron.Macaron, options ...Options) macaron.Handler { | |||
| prepareOptions(options) | |||
| t := &toolbox{ | |||
| healthCheckJobs: make([]*healthCheck, 0, len(opt.HealthCheckers)+len(opt.HealthCheckFuncs)), | |||
| } | |||
| // Dashboard. | |||
| m.Get(opt.URLPrefix, dashboard) | |||
| // Health check. | |||
| for _, hc := range opt.HealthCheckers { | |||
| t.AddHealthCheck(hc) | |||
| } | |||
| for _, fd := range opt.HealthCheckFuncs { | |||
| t.AddHealthCheckFunc(fd.Desc, fd.Func) | |||
| } | |||
| m.Get(opt.HealthCheckURL, t.handleHealthCheck) | |||
| // URL map. | |||
| m.Get(opt.URLMapPrefix, func(rw http.ResponseWriter) { | |||
| t.JSON(rw) | |||
| }) | |||
| // Pprof. | |||
| m.Any(path.Join(opt.PprofURLPrefix, "cmdline"), pprof.Cmdline) | |||
| m.Any(path.Join(opt.PprofURLPrefix, "profile"), pprof.Profile) | |||
| m.Any(path.Join(opt.PprofURLPrefix, "symbol"), pprof.Symbol) | |||
| m.Any(opt.PprofURLPrefix, pprof.Index) | |||
| m.Any(path.Join(opt.PprofURLPrefix, "*"), pprof.Index) | |||
| // Profile. | |||
| profilePath = opt.ProfilePath | |||
| m.Get(opt.ProfileURLPrefix, handleProfile) | |||
| // Routes statistic. | |||
| t.UrlMap = &UrlMap{ | |||
| urlmap: make(map[string]map[string]*Statistics), | |||
| } | |||
| return func(ctx *macaron.Context) { | |||
| ctx.MapTo(t, (*Toolbox)(nil)) | |||
| } | |||
| } | |||
| @@ -0,0 +1,55 @@ | |||
| # This is the official list of Go-MySQL-Driver authors for copyright purposes. | |||
| # If you are submitting a patch, please add your name or the name of the | |||
| # organization which holds the copyright to this list in alphabetical order. | |||
| # Names should be added to this file as | |||
| # Name <email address> | |||
| # The email address is not required for organizations. | |||
| # Please keep the list sorted. | |||
| # Individual Persons | |||
| Aaron Hopkins <go-sql-driver at die.net> | |||
| Arne Hormann <arnehormann at gmail.com> | |||
| Carlos Nieto <jose.carlos at menteslibres.net> | |||
| Chris Moos <chris at tech9computers.com> | |||
| Daniel Nichter <nil at codenode.com> | |||
| Daniël van Eeden <git at myname.nl> | |||
| DisposaBoy <disposaboy at dby.me> | |||
| Frederick Mayle <frederickmayle at gmail.com> | |||
| Gustavo Kristic <gkristic at gmail.com> | |||
| Hanno Braun <mail at hannobraun.com> | |||
| Henri Yandell <flamefew at gmail.com> | |||
| Hirotaka Yamamoto <ymmt2005 at gmail.com> | |||
| INADA Naoki <songofacandy at gmail.com> | |||
| James Harr <james.harr at gmail.com> | |||
| Jian Zhen <zhenjl at gmail.com> | |||
| Joshua Prunier <joshua.prunier at gmail.com> | |||
| Julien Lefevre <julien.lefevr at gmail.com> | |||
| Julien Schmidt <go-sql-driver at julienschmidt.com> | |||
| Kamil Dziedzic <kamil at klecza.pl> | |||
| Kevin Malachowski <kevin at chowski.com> | |||
| Lennart Rudolph <lrudolph at hmc.edu> | |||
| Leonardo YongUk Kim <dalinaum at gmail.com> | |||
| Luca Looz <luca.looz92 at gmail.com> | |||
| Lucas Liu <extrafliu at gmail.com> | |||
| Luke Scott <luke at webconnex.com> | |||
| Michael Woolnough <michael.woolnough at gmail.com> | |||
| Nicola Peduzzi <thenikso at gmail.com> | |||
| Paul Bonser <misterpib at gmail.com> | |||
| Runrioter Wung <runrioter at gmail.com> | |||
| Soroush Pour <me at soroushjp.com> | |||
| Stan Putrya <root.vagner at gmail.com> | |||
| Stanley Gunawan <gunawan.stanley at gmail.com> | |||
| Xiangyu Hu <xiangyu.hu at outlook.com> | |||
| Xiaobing Jiang <s7v7nislands at gmail.com> | |||
| Xiuming Chen <cc at cxm.cc> | |||
| Zhenye Xie <xiezhenye at gmail.com> | |||
| # Organizations | |||
| Barracuda Networks, Inc. | |||
| Google Inc. | |||
| Stripe Inc. | |||
| @@ -0,0 +1,114 @@ | |||
| ## HEAD | |||
| Changes: | |||
| - Go 1.1 is no longer supported | |||
| - Use decimals fields in MySQL to format time types (#249) | |||
| - Buffer optimizations (#269) | |||
| - TLS ServerName defaults to the host (#283) | |||
| - Refactoring (#400, #410, #437) | |||
| - Adjusted documentation for second generation CloudSQL (#485) | |||
| New Features: | |||
| - Enable microsecond resolution on TIME, DATETIME and TIMESTAMP (#249) | |||
| - Support for returning table alias on Columns() (#289, #359, #382) | |||
| - Placeholder interpolation, can be actived with the DSN parameter `interpolateParams=true` (#309, #318, #490) | |||
| - Support for uint64 parameters with high bit set (#332, #345) | |||
| - Cleartext authentication plugin support (#327) | |||
| - Exported ParseDSN function and the Config struct (#403, #419, #429) | |||
| - Read / Write timeouts (#401) | |||
| - Support for JSON field type (#414) | |||
| - Support for multi-statements and multi-results (#411, #431) | |||
| - DSN parameter to set the driver-side max_allowed_packet value manually (#489) | |||
| Bugfixes: | |||
| - Fixed handling of queries without columns and rows (#255) | |||
| - Fixed a panic when SetKeepAlive() failed (#298) | |||
| - Handle ERR packets while reading rows (#321) | |||
| - Fixed reading NULL length-encoded integers in MySQL 5.6+ (#349) | |||
| - Fixed absolute paths support in LOAD LOCAL DATA INFILE (#356) | |||
| - Actually zero out bytes in handshake response (#378) | |||
| - Fixed race condition in registering LOAD DATA INFILE handler (#383) | |||
| - Fixed tests with MySQL 5.7.9+ (#380) | |||
| - QueryUnescape TLS config names (#397) | |||
| - Fixed "broken pipe" error by writing to closed socket (#390) | |||
| - Fixed LOAD LOCAL DATA INFILE buffering (#424) | |||
| - Fixed parsing of floats into float64 when placeholders are used (#434) | |||
| - Fixed DSN tests with Go 1.7+ (#459) | |||
| - Handle ERR packets while waiting for EOF (#473) | |||
| ## Version 1.2 (2014-06-03) | |||
| Changes: | |||
| - We switched back to a "rolling release". `go get` installs the current master branch again | |||
| - Version v1 of the driver will not be maintained anymore. Go 1.0 is no longer supported by this driver | |||
| - Exported errors to allow easy checking from application code | |||
| - Enabled TCP Keepalives on TCP connections | |||
| - Optimized INFILE handling (better buffer size calculation, lazy init, ...) | |||
| - The DSN parser also checks for a missing separating slash | |||
| - Faster binary date / datetime to string formatting | |||
| - Also exported the MySQLWarning type | |||
| - mysqlConn.Close returns the first error encountered instead of ignoring all errors | |||
| - writePacket() automatically writes the packet size to the header | |||
| - readPacket() uses an iterative approach instead of the recursive approach to merge splitted packets | |||
| New Features: | |||
| - `RegisterDial` allows the usage of a custom dial function to establish the network connection | |||
| - Setting the connection collation is possible with the `collation` DSN parameter. This parameter should be preferred over the `charset` parameter | |||
| - Logging of critical errors is configurable with `SetLogger` | |||
| - Google CloudSQL support | |||
| Bugfixes: | |||
| - Allow more than 32 parameters in prepared statements | |||
| - Various old_password fixes | |||
| - Fixed TestConcurrent test to pass Go's race detection | |||
| - Fixed appendLengthEncodedInteger for large numbers | |||
| - Renamed readLengthEnodedString to readLengthEncodedString and skipLengthEnodedString to skipLengthEncodedString (fixed typo) | |||
| ## Version 1.1 (2013-11-02) | |||
| Changes: | |||
| - Go-MySQL-Driver now requires Go 1.1 | |||
| - Connections now use the collation `utf8_general_ci` by default. Adding `&charset=UTF8` to the DSN should not be necessary anymore | |||
| - Made closing rows and connections error tolerant. This allows for example deferring rows.Close() without checking for errors | |||
| - `[]byte(nil)` is now treated as a NULL value. Before, it was treated like an empty string / `[]byte("")` | |||
| - DSN parameter values must now be url.QueryEscape'ed. This allows text values to contain special characters, such as '&'. | |||
| - Use the IO buffer also for writing. This results in zero allocations (by the driver) for most queries | |||
| - Optimized the buffer for reading | |||
| - stmt.Query now caches column metadata | |||
| - New Logo | |||
| - Changed the copyright header to include all contributors | |||
| - Improved the LOAD INFILE documentation | |||
| - The driver struct is now exported to make the driver directly accessible | |||
| - Refactored the driver tests | |||
| - Added more benchmarks and moved all to a separate file | |||
| - Other small refactoring | |||
| New Features: | |||
| - Added *old_passwords* support: Required in some cases, but must be enabled by adding `allowOldPasswords=true` to the DSN since it is insecure | |||
| - Added a `clientFoundRows` parameter: Return the number of matching rows instead of the number of rows changed on UPDATEs | |||
| - Added TLS/SSL support: Use a TLS/SSL encrypted connection to the server. Custom TLS configs can be registered and used | |||
| Bugfixes: | |||
| - Fixed MySQL 4.1 support: MySQL 4.1 sends packets with lengths which differ from the specification | |||
| - Convert to DB timezone when inserting `time.Time` | |||
| - Splitted packets (more than 16MB) are now merged correctly | |||
| - Fixed false positive `io.EOF` errors when the data was fully read | |||
| - Avoid panics on reuse of closed connections | |||
| - Fixed empty string producing false nil values | |||
| - Fixed sign byte for positive TIME fields | |||
| ## Version 1.0 (2013-05-14) | |||
| Initial Release | |||
| @@ -0,0 +1,23 @@ | |||
| # Contributing Guidelines | |||
| ## Reporting Issues | |||
| Before creating a new Issue, please check first if a similar Issue [already exists](https://github.com/go-sql-driver/mysql/issues?state=open) or was [recently closed](https://github.com/go-sql-driver/mysql/issues?direction=desc&page=1&sort=updated&state=closed). | |||
| ## Contributing Code | |||
| By contributing to this project, you share your code under the Mozilla Public License 2, as specified in the LICENSE file. | |||
| Don't forget to add yourself to the AUTHORS file. | |||
| ### Code Review | |||
| Everyone is invited to review and comment on pull requests. | |||
| If it looks fine to you, comment with "LGTM" (Looks good to me). | |||
| If changes are required, notice the reviewers with "PTAL" (Please take another look) after committing the fixes. | |||
| Before merging the Pull Request, at least one [team member](https://github.com/go-sql-driver?tab=members) must have commented with "LGTM". | |||
| ## Development Ideas | |||
| If you are looking for ideas for code contributions, please check our [Development Ideas](https://github.com/go-sql-driver/mysql/wiki/Development-Ideas) Wiki page. | |||
| @@ -0,0 +1,21 @@ | |||
| ### Issue description | |||
| Tell us what should happen and what happens instead | |||
| ### Example code | |||
| ```go | |||
| If possible, please enter some example code here to reproduce the issue. | |||
| ``` | |||
| ### Error log | |||
| ``` | |||
| If you have an error log, please paste it here. | |||
| ``` | |||
| ### Configuration | |||
| *Driver version (or git SHA):* | |||
| *Go version:* run `go version` in your console | |||
| *Server version:* E.g. MySQL 5.6, MariaDB 10.0.20 | |||
| *Server OS:* E.g. Debian 8.1 (Jessie), Windows 10 | |||