@@ -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}} | |||
@@ -162,6 +162,11 @@ | |||
</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}"> | |||
<p class="help">{{.i18n.Tr "repo.settings.tracker_url_format_desc" | Str2html}}</p> | |||
@@ -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 |