@@ -17,4 +17,3 @@ output* | |||||
gogs.sublime-project | gogs.sublime-project | ||||
gogs.sublime-workspace | gogs.sublime-workspace | ||||
/release | /release | ||||
vendor |
@@ -7,18 +7,14 @@ go: | |||||
before_install: | before_install: | ||||
- sudo apt-get update -qq | - sudo apt-get update -qq | ||||
- sudo apt-get install -y libpam-dev | - 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: | after_success: | ||||
- bash <(curl -s https://codecov.io/bash) | - 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.issues_desc = Enable issue tracker | ||||
settings.use_internal_issue_tracker = Use builtin lightweight issue tracker | settings.use_internal_issue_tracker = Use builtin lightweight issue tracker | ||||
settings.use_external_issue_tracker = Use external 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_url_format = External Issue Tracker URL Format | ||||
settings.tracker_issue_style = External Issue Tracker Naming Style: | settings.tracker_issue_style = External Issue Tracker Naming Style: | ||||
settings.tracker_issue_style.numeric = Numeric | 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 | ExternalWikiURL string | ||||
EnableIssues bool `xorm:"NOT NULL DEFAULT true"` | EnableIssues bool `xorm:"NOT NULL DEFAULT true"` | ||||
EnableExternalTracker bool | EnableExternalTracker bool | ||||
ExternalTrackerURL string | |||||
ExternalTrackerFormat string | ExternalTrackerFormat string | ||||
ExternalTrackerStyle string | ExternalTrackerStyle string | ||||
ExternalMetas map[string]string `xorm:"-"` | ExternalMetas map[string]string `xorm:"-"` | ||||
@@ -1,11 +1,11 @@ | |||||
package models_test | package models_test | ||||
import ( | import ( | ||||
. "github.com/go-gitea/gitea/models" | |||||
. "github.com/smartystreets/goconvey/convey" | |||||
"testing" | "testing" | ||||
. "github.com/go-gitea/gitea/models" | |||||
"github.com/go-gitea/gitea/modules/markdown" | "github.com/go-gitea/gitea/modules/markdown" | ||||
. "github.com/smartystreets/goconvey/convey" | |||||
) | ) | ||||
func TestRepo(t *testing.T) { | func TestRepo(t *testing.T) { | ||||
@@ -96,6 +96,7 @@ type RepoSettingForm struct { | |||||
ExternalWikiURL string | ExternalWikiURL string | ||||
EnableIssues bool | EnableIssues bool | ||||
EnableExternalTracker bool | EnableExternalTracker bool | ||||
ExternalTrackerURL string | |||||
TrackerURLFormat string | TrackerURLFormat string | ||||
TrackerIssueStyle string | TrackerIssueStyle string | ||||
EnablePulls bool | EnablePulls bool | ||||
@@ -1,13 +1,13 @@ | |||||
package markdown_test | package markdown_test | ||||
import ( | import ( | ||||
. "github.com/go-gitea/gitea/modules/markdown" | |||||
. "github.com/smartystreets/goconvey/convey" | |||||
"bytes" | |||||
"testing" | "testing" | ||||
"bytes" | |||||
. "github.com/go-gitea/gitea/modules/markdown" | |||||
"github.com/go-gitea/gitea/modules/setting" | "github.com/go-gitea/gitea/modules/setting" | ||||
"github.com/russross/blackfriday" | "github.com/russross/blackfriday" | ||||
. "github.com/smartystreets/goconvey/convey" | |||||
) | ) | ||||
func TestMarkdown(t *testing.T) { | func TestMarkdown(t *testing.T) { | ||||
@@ -22,8 +22,8 @@ import ( | |||||
_ "github.com/go-macaron/cache/redis" | _ "github.com/go-macaron/cache/redis" | ||||
"github.com/go-macaron/session" | "github.com/go-macaron/session" | ||||
_ "github.com/go-macaron/session/redis" | _ "github.com/go-macaron/session/redis" | ||||
"strk.kbt.io/projects/go/libravatar" | |||||
"gopkg.in/ini.v1" | "gopkg.in/ini.v1" | ||||
"strk.kbt.io/projects/go/libravatar" | |||||
"github.com/go-gitea/gitea/modules/bindata" | "github.com/go-gitea/gitea/modules/bindata" | ||||
"github.com/go-gitea/gitea/modules/log" | "github.com/go-gitea/gitea/modules/log" | ||||
@@ -52,10 +52,15 @@ var ( | |||||
) | ) | ||||
func MustEnableIssues(ctx *context.Context) { | func MustEnableIssues(ctx *context.Context) { | ||||
if !ctx.Repo.Repository.EnableIssues || ctx.Repo.Repository.EnableExternalTracker { | |||||
if !ctx.Repo.Repository.EnableIssues { | |||||
ctx.Handle(404, "MustEnableIssues", nil) | ctx.Handle(404, "MustEnableIssues", nil) | ||||
return | return | ||||
} | } | ||||
if ctx.Repo.Repository.EnableExternalTracker { | |||||
ctx.Redirect(ctx.Repo.Repository.ExternalTrackerURL) | |||||
return | |||||
} | |||||
} | } | ||||
func MustAllowPulls(ctx *context.Context) { | func MustAllowPulls(ctx *context.Context) { | ||||
@@ -502,7 +507,7 @@ func ViewIssue(ctx *context.Context) { | |||||
} | } | ||||
return | return | ||||
} | } | ||||
ctx.Data["Title"] = issue.Title | |||||
ctx.Data["Title"] = fmt.Sprintf("#%d - %s", issue.Index, issue.Title) | |||||
// Make sure type and URL matches. | // Make sure type and URL matches. | ||||
if ctx.Params(":type") == "issues" && issue.IsPull { | if ctx.Params(":type") == "issues" && issue.IsPull { | ||||
@@ -6,6 +6,7 @@ package repo | |||||
import ( | import ( | ||||
"container/list" | "container/list" | ||||
"fmt" | |||||
"path" | "path" | ||||
"strings" | "strings" | ||||
@@ -148,7 +149,7 @@ func checkPullInfo(ctx *context.Context) *models.Issue { | |||||
} | } | ||||
return nil | return nil | ||||
} | } | ||||
ctx.Data["Title"] = issue.Title | |||||
ctx.Data["Title"] = fmt.Sprintf("#%d - %s", issue.Index, issue.Title) | |||||
ctx.Data["Issue"] = issue | ctx.Data["Issue"] = issue | ||||
if !issue.IsPull { | if !issue.IsPull { | ||||
@@ -146,6 +146,7 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) { | |||||
repo.ExternalWikiURL = form.ExternalWikiURL | repo.ExternalWikiURL = form.ExternalWikiURL | ||||
repo.EnableIssues = form.EnableIssues | repo.EnableIssues = form.EnableIssues | ||||
repo.EnableExternalTracker = form.EnableExternalTracker | repo.EnableExternalTracker = form.EnableExternalTracker | ||||
repo.ExternalTrackerURL = form.ExternalTrackerURL | |||||
repo.ExternalTrackerFormat = form.TrackerURLFormat | repo.ExternalTrackerFormat = form.TrackerURLFormat | ||||
repo.ExternalTrackerStyle = form.TrackerIssueStyle | repo.ExternalTrackerStyle = form.TrackerIssueStyle | ||||
repo.EnablePulls = form.EnablePulls | repo.EnablePulls = form.EnablePulls | ||||
@@ -30,7 +30,11 @@ | |||||
<tr> | <tr> | ||||
<td class="author"> | <td class="author"> | ||||
{{if .User}} | {{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}} | {{else}} | ||||
<img class="ui avatar image" src="{{AvatarLink .Author.Email}}" alt=""/> {{.Author.Name}} | <img class="ui avatar image" src="{{AvatarLink .Author.Email}}" alt=""/> {{.Author.Name}} | ||||
{{end}} | {{end}} | ||||
@@ -14,7 +14,11 @@ | |||||
<div class="ui attached info segment"> | <div class="ui attached info segment"> | ||||
{{if .Author}} | {{if .Author}} | ||||
<img class="ui avatar image" src="{{.Author.RelAvatarLink}}" /> | <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}} | {{else}} | ||||
<img class="ui avatar image" src="{{AvatarLink .Commit.Author.Email}}" /> | <img class="ui avatar image" src="{{AvatarLink .Commit.Author.Email}}" /> | ||||
<strong>{{.Commit.Author.Name}}</strong> | <strong>{{.Commit.Author.Name}}</strong> | ||||
@@ -52,9 +52,9 @@ | |||||
<a class="{{if .PageIsViewCode}}active{{end}} item" href="{{.RepoLink}}"> | <a class="{{if .PageIsViewCode}}active{{end}} item" href="{{.RepoLink}}"> | ||||
<i class="octicon octicon-code"></i> {{.i18n.Tr "repo.code"}} | <i class="octicon octicon-code"></i> {{.i18n.Tr "repo.code"}} | ||||
</a> | </a> | ||||
{{if and .Repository.EnableIssues (not .Repository.EnableExternalTracker)}} | |||||
{{if .Repository.EnableIssues}} | |||||
<a class="{{if .PageIsIssueList}}active{{end}} item" href="{{.RepoLink}}/issues"> | <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> | </a> | ||||
{{end}} | {{end}} | ||||
{{if .Repository.AllowsPulls}} | {{if .Repository.AllowsPulls}} | ||||
@@ -162,6 +162,11 @@ | |||||
</div> | </div> | ||||
<div class="field {{if not .Repository.EnableExternalTracker}}disabled{{end}}" id="external_issue_box"> | <div class="field {{if not .Repository.EnableExternalTracker}}disabled{{end}}" id="external_issue_box"> | ||||
<div class="field"> | <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> | <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}"> | <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> | <p class="help">{{.i18n.Tr "repo.settings.tracker_url_format_desc" | Str2html}}</p> | ||||
@@ -4,7 +4,11 @@ | |||||
<th class="four wide"> | <th class="four wide"> | ||||
{{if .LatestCommitUser}} | {{if .LatestCommitUser}} | ||||
<img class="ui avatar image img-12" src="{{.LatestCommitUser.RelAvatarLink}}" /> | <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}} | {{else}} | ||||
<img class="ui avatar image img-12" src="{{AvatarLink .LatestCommit.Author.Email}}" /> | <img class="ui avatar image img-12" src="{{AvatarLink .LatestCommit.Author.Email}}" /> | ||||
<strong>{{.LatestCommit.Author.Name}}</strong> | <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 |