mirror of https://github.com/go-gitea/gitea
parent
78f86abba4
commit
1ebb35b988
@ -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 |
||||
================================== |
||||
|
||||
[![Go Walker](http://gowalker.org/api/v1/badge)](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 @@ |
||||
压缩与打包扩展 |
||||
============= |
||||
|
||||
[![Go Walker](http://gowalker.org/api/v1/badge)](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 |
||||
================ |
||||
|
||||
[![Build Status](https://travis-ci.org/Unknwon/com.svg)](https://travis-ci.org/Unknwon/com) [![Go Walker](http://gowalker.org/api/v1/badge)](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 [![Build Status](https://drone.io/github.com/Unknwon/paginater/status.png)](https://drone.io/github.com/Unknwon/paginater/latest) [![](http://gocover.io/_badge/github.com/Unknwon/paginater)](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 [![Build Status](https://travis-ci.org/go-macaron/binding.svg?branch=master)](https://travis-ci.org/go-macaron/binding) [![](http://gocover.io/_badge/github.com/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 [![Build Status](https://travis-ci.org/go-macaron/cache.svg?branch=master)](https://travis-ci.org/go-macaron/cache) [![](http://gocover.io/_badge/github.com/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 [![Build Status](https://travis-ci.org/go-macaron/captcha.svg?branch=master)](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 [![Build Status](https://travis-ci.org/go-macaron/csrf.svg?branch=master)](https://travis-ci.org/go-macaron/csrf) [![](http://gocover.io/_badge/github.com/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 [![Build Status](https://travis-ci.org/go-macaron/gzip.svg?branch=master)](https://travis-ci.org/go-macaron/gzip) [![](http://gocover.io/_badge/github.com/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 [![Build Status](https://travis-ci.org/go-macaron/i18n.svg?branch=master)](https://travis-ci.org/go-macaron/i18n) [![](http://gocover.io/_badge/github.com/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 [![Build Status](https://travis-ci.org/go-macaron/inject.svg?branch=master)](https://travis-ci.org/go-macaron/inject) [![](http://gocover.io/_badge/github.com/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 [![Build Status](https://travis-ci.org/go-macaron/session.svg?branch=master)](https://travis-ci.org/go-macaron/session) [![](http://gocover.io/_badge/github.com/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 |
@ -0,0 +1,373 @@ |
||||
Mozilla Public License Version 2.0 |
||||
================================== |
||||
|
||||
1. Definitions |
||||
-------------- |
||||
|
||||
1.1. "Contributor" |
||||
means each individual or legal entity that creates, contributes to |
||||
the creation of, or owns Covered Software. |
||||
|
||||
1.2. "Contributor Version" |
||||
means the combination of the Contributions of others (if any) used |
||||
by a Contributor and that particular Contributor's Contribution. |
||||
|
||||
1.3. "Contribution" |
||||
means Covered Software of a particular Contributor. |
||||
|
||||
1.4. "Covered Software" |
||||
means Source Code Form to which the initial Contributor has attached |
||||
the notice in Exhibit A, the Executable Form of such Source Code |
||||
Form, and Modifications of such Source Code Form, in each case |
||||
including portions thereof. |
||||
|
||||
1.5. "Incompatible With Secondary Licenses" |
||||
means |
||||
|
||||
(a) that the initial Contributor has attached the notice described |
||||
in Exhibit B to the Covered Software; or |
||||
|
||||
(b) that the Covered Software was made available under the terms of |
||||
version 1.1 or earlier of the License, but not also under the |
||||
terms of a Secondary License. |
||||
|
||||
1.6. "Executable Form" |
||||
means any form of the work other than Source Code Form. |
||||
|
||||
1.7. "Larger Work" |
||||
means a work that combines Covered Software with other material, in |
||||
a separate file or files, that is not Covered Software. |
||||
|
||||
1.8. "License" |
||||
means this document. |
||||
|
||||
1.9. "Licensable" |
||||
means having the right to grant, to the maximum extent possible, |
||||
whether at the time of the initial grant or subsequently, any and |
||||
all of the rights conveyed by this License. |
||||
|
||||
1.10. "Modifications" |
||||
means any of the following: |
||||
|
||||
(a) any file in Source Code Form that results from an addition to, |
||||
deletion from, or modification of the contents of Covered |
||||
Software; or |
||||
|
||||
(b) any new file in Source Code Form that contains any Covered |
||||
Software. |
||||
|
||||
1.11. "Patent Claims" of a Contributor |
||||
means any patent claim(s), including without limitation, method, |
||||
process, and apparatus claims, in any patent Licensable by such |
||||
Contributor that would be infringed, but for the grant of the |
||||
License, by the making, using, selling, offering for sale, having |
||||
made, import, or transfer of either its Contributions or its |
||||
Contributor Version. |
||||
|
||||
1.12. "Secondary License" |
||||
means either the GNU General Public License, Version 2.0, the GNU |
||||
Lesser General Public License, Version 2.1, the GNU Affero General |
||||
Public License, Version 3.0, or any later versions of those |
||||
licenses. |
||||
|
||||
1.13. "Source Code Form" |
||||
means the form of the work preferred for making modifications. |
||||
|
||||
1.14. "You" (or "Your") |
||||
means an individual or a legal entity exercising rights under this |
||||
License. For legal entities, "You" includes any entity that |
||||
controls, is controlled by, or is under common control with You. For |
||||
purposes of this definition, "control" means (a) the power, direct |
||||
or indirect, to cause the direction or management of such entity, |
||||
whether by contract or otherwise, or (b) ownership of more than |
||||
fifty percent (50%) of the outstanding shares or beneficial |
||||
ownership of such entity. |
||||
|
||||
2. License Grants and Conditions |
||||
-------------------------------- |
||||
|
||||
2.1. Grants |
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free, |
||||
non-exclusive license: |
||||
|
||||
(a) under intellectual property rights (other than patent or trademark) |
||||
Licensable by such Contributor to use, reproduce, make available, |
||||
modify, display, perform, distribute, and otherwise exploit its |
||||
Contributions, either on an unmodified basis, with Modifications, or |
||||
as part of a Larger Work; and |
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer |
||||
for sale, have made, import, and otherwise transfer either its |
||||
Contributions or its Contributor Version. |
||||
|
||||
2.2. Effective Date |
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution |
||||
become effective for each Contribution on the date the Contributor first |
||||
distributes such Contribution. |
||||
|
||||
2.3. Limitations on Grant Scope |
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under |
||||
this License. No additional rights or licenses will be implied from the |
||||
distribution or licensing of Covered Software under this License. |
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a |
||||
Contributor: |
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software; |
||||
or |
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's |
||||
modifications of Covered Software, or (ii) the combination of its |
||||
Contributions with other software (except as part of its Contributor |
||||
Version); or |
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of |
||||
its Contributions. |
||||
|
||||
This License does not grant any rights in the trademarks, service marks, |
||||
or logos of any Contributor (except as may be necessary to comply with |
||||
the notice requirements in Section 3.4). |
||||
|
||||
2.4. Subsequent Licenses |
||||
|
||||
No Contributor makes additional grants as a result of Your choice to |
||||
distribute the Covered Software under a subsequent version of this |
||||
License (see Section 10.2) or under the terms of a Secondary License (if |
||||
permitted under the terms of Section 3.3). |
||||
|
||||
2.5. Representation |
||||
|
||||
Each Contributor represents that the Contributor believes its |
||||
Contributions are its original creation(s) or it has sufficient rights |
||||
to grant the rights to its Contributions conveyed by this License. |
||||
|
||||
2.6. Fair Use |
||||
|
||||
This License is not intended to limit any rights You have under |
||||
applicable copyright doctrines of fair use, fair dealing, or other |
||||
equivalents. |
||||
|
||||
2.7. Conditions |
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted |
||||
in Section 2.1. |
||||
|
||||
3. Responsibilities |
||||
------------------- |
||||
|
||||
3.1. Distribution of Source Form |
||||
|
||||
All distribution of Covered Software in Source Code Form, including any |
||||
Modifications that You create or to which You contribute, must be under |
||||
the terms of this License. You must inform recipients that the Source |
||||
Code Form of the Covered Software is governed by the terms of this |
||||
License, and how they can obtain a copy of this License. You may not |
||||
attempt to alter or restrict the recipients' rights in the Source Code |
||||
Form. |
||||
|
||||
3.2. Distribution of Executable Form |
||||
|
||||
If You distribute Covered Software in Executable Form then: |
||||
|
||||
(a) such Covered Software must also be made available in Source Code |
||||
Form, as described in Section 3.1, and You must inform recipients of |
||||
the Executable Form how they can obtain a copy of such Source Code |
||||
Form by reasonable means in a timely manner, at a charge no more |
||||
than the cost of distribution to the recipient; and |
||||
|
||||
(b) You may distribute such Executable Form under the terms of this |
||||
License, or sublicense it under different terms, provided that the |
||||
license for the Executable Form does not attempt to limit or alter |
||||
the recipients' rights in the Source Code Form under this License. |
||||
|
||||
3.3. Distribution of a Larger Work |
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice, |
||||
provided that You also comply with the requirements of this License for |
||||
the Covered Software. If the Larger Work is a combination of Covered |
||||
Software with a work governed by one or more Secondary Licenses, and the |
||||
Covered Software is not Incompatible With Secondary Licenses, this |
||||
License permits You to additionally distribute such Covered Software |
||||
under the terms of such Secondary License(s), so that the recipient of |
||||
the Larger Work may, at their option, further distribute the Covered |
||||
Software under the terms of either this License or such Secondary |
||||
License(s). |
||||
|
||||
3.4. Notices |
||||
|
||||
You may not remove or alter the substance of any license notices |
||||
(including copyright notices, patent notices, disclaimers of warranty, |
||||
or limitations of liability) contained within the Source Code Form of |
||||
the Covered Software, except that You may alter any license notices to |
||||
the extent required to remedy known factual inaccuracies. |
||||
|
||||
3.5. Application of Additional Terms |
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support, |
||||
indemnity or liability obligations to one or more recipients of Covered |
||||
Software. However, You may do so only on Your own behalf, and not on |
||||
behalf of any Contributor. You must make it absolutely clear that any |
||||
such warranty, support, indemnity, or liability obligation is offered by |
||||
You alone, and You hereby agree to indemnify every Contributor for any |
||||
liability incurred by such Contributor as a result of warranty, support, |
||||
indemnity or liability terms You offer. You may include additional |
||||
disclaimers of warranty and limitations of liability specific to any |
||||
jurisdiction. |
||||
|
||||
4. Inability to Comply Due to Statute or Regulation |
||||
--------------------------------------------------- |
||||
|
||||
If it is impossible for You to comply with any of the terms of this |
||||
License with respect to some or all of the Covered Software due to |
||||
statute, judicial order, or regulation then You must: (a) comply with |
||||
the terms of this License to the maximum extent possible; and (b) |
||||
describe the limitations and the code they affect. Such description must |
||||
be placed in a text file included with all distributions of the Covered |
||||
Software under this License. Except to the extent prohibited by statute |
||||
or regulation, such description must be sufficiently detailed for a |
||||
recipient of ordinary skill to be able to understand it. |
||||
|
||||
5. Termination |
||||
-------------- |
||||
|
||||
5.1. The rights granted under this License will terminate automatically |
||||
if You fail to comply with any of its terms. However, if You become |
||||
compliant, then the rights granted under this License from a particular |
||||
Contributor are reinstated (a) provisionally, unless and until such |
||||
Contributor explicitly and finally terminates Your grants, and (b) on an |
||||
ongoing basis, if such Contributor fails to notify You of the |
||||
non-compliance by some reasonable means prior to 60 days after You have |
||||
come back into compliance. Moreover, Your grants from a particular |
||||
Contributor are reinstated on an ongoing basis if such Contributor |
||||
notifies You of the non-compliance by some reasonable means, this is the |
||||
first time You have received notice of non-compliance with this License |
||||
from such Contributor, and You become compliant prior to 30 days after |
||||
Your receipt of the notice. |
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent |
||||
infringement claim (excluding declaratory judgment actions, |
||||
counter-claims, and cross-claims) alleging that a Contributor Version |
||||
directly or indirectly infringes any patent, then the rights granted to |
||||
You by any and all Contributors for the Covered Software under Section |
||||
2.1 of this License shall terminate. |
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all |
||||
end user license agreements (excluding distributors and resellers) which |
||||
have been validly granted by You or Your distributors under this License |
||||
prior to termination shall survive termination. |
||||
|
||||
************************************************************************ |
||||
* * |
||||
* 6. Disclaimer of Warranty * |
||||
* ------------------------- * |
||||
* * |
||||
* Covered Software is provided under this License on an "as is" * |
||||
* basis, without warranty of any kind, either expressed, implied, or * |
||||
* statutory, including, without limitation, warranties that the * |
||||
* Covered Software is free of defects, merchantable, fit for a * |
||||
* particular purpose or non-infringing. The entire risk as to the * |
||||
* quality and performance of the Covered Software is with You. * |
||||
* Should any Covered Software prove defective in any respect, You * |
||||
* (not any Contributor) assume the cost of any necessary servicing, * |
||||
* repair, or correction. This disclaimer of warranty constitutes an * |
||||
* essential part of this License. No use of any Covered Software is * |
||||
* authorized under this License except under this disclaimer. * |
||||
* * |
||||
************************************************************************ |
||||
|
||||
************************************************************************ |
||||
* * |
||||
* 7. Limitation of Liability * |
||||
* -------------------------- * |
||||
* * |
||||
* Under no circumstances and under no legal theory, whether tort * |
||||
* (including negligence), contract, or otherwise, shall any * |
||||
* Contributor, or anyone who distributes Covered Software as * |
||||
* permitted above, be liable to You for any direct, indirect, * |
||||
* special, incidental, or consequential damages of any character * |
||||
* including, without limitation, damages for lost profits, loss of * |
||||
* goodwill, work stoppage, computer failure or malfunction, or any * |
||||
* and all other commercial damages or losses, even if such party * |
||||
* shall have been informed of the possibility of such damages. This * |
||||
* limitation of liability shall not apply to liability for death or * |
||||
* personal injury resulting from such party's negligence to the * |
||||
* extent applicable law prohibits such limitation. Some * |
||||
* jurisdictions do not allow the exclusion or limitation of * |
||||
* incidental or consequential damages, so this exclusion and * |
||||
* limitation may not apply to You. * |
||||
* * |
||||
************************************************************************ |
||||
|
||||
8. Litigation |
||||
------------- |
||||
|
||||
Any litigation relating to this License may be brought only in the |
||||
courts of a jurisdiction where the defendant maintains its principal |
||||
place of business and such litigation shall be governed by laws of that |
||||
jurisdiction, without reference to its conflict-of-law provisions. |
||||
Nothing in this Section shall prevent a party's ability to bring |
||||
cross-claims or counter-claims. |
||||
|
||||
9. Miscellaneous |
||||
---------------- |
||||
|
||||
This License represents the complete agreement concerning the subject |
||||
matter hereof. If any provision of this License is held to be |
||||
unenforceable, such provision shall be reformed only to the extent |
||||
necessary to make it enforceable. Any law or regulation which provides |
||||
that the language of a contract shall be construed against the drafter |
||||
shall not be used to construe this License against a Contributor. |
||||
|
||||
10. Versions of the License |
||||
--------------------------- |
||||
|
||||
10.1. New Versions |
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section |
||||
10.3, no one other than the license steward has the right to modify or |
||||
publish new versions of this License. Each version will be given a |
||||
distinguishing version number. |
||||
|
||||
10.2. Effect of New Versions |
||||
|
||||
You may distribute the Covered Software under the terms of the version |
||||
of the License under which You originally received the Covered Software, |
||||
or under the terms of any subsequent version published by the license |
||||
steward. |
||||
|
||||
10.3. Modified Versions |
||||
|
||||
If you create software not governed by this License, and you want to |
||||
create a new license for such software, you may create and use a |
||||
modified version of this License if you rename the license and remove |
||||
any references to the name of the license steward (except to note that |
||||
such modified license differs from this License). |
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary |
||||
Licenses |
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With |
||||
Secondary Licenses under the terms of this version of the License, the |
||||
notice described in Exhibit B of this License must be attached. |
||||
|
||||
Exhibit A - Source Code Form License Notice |
||||
------------------------------------------- |
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public |
||||
License, v. 2.0. If a copy of the MPL was not distributed with this |
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/. |
||||
|
||||
If it is not possible or desirable to put the notice in a particular |
||||
file, then You may include the notice in a location (such as a LICENSE |
||||
file in a relevant directory) where a recipient would be likely to look |
||||
for such a notice. |
||||
|
||||
You may add additional accurate notices of copyright ownership. |
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice |
||||
--------------------------------------------------------- |
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as |
||||
defined by the Mozilla Public License, v. 2.0. |
@ -0,0 +1,9 @@ |
||||
### Description |
||||
Please explain the changes you made here. |
||||
|
||||
### Checklist |
||||
- [ ] Code compiles correctly |
||||
- [ ] Created tests which fail without the change (if possible) |
||||
- [ ] All tests passing |
||||
- [ ] Extended the README / documentation, if necessary |
||||
- [ ] Added myself / the copyright holder to the AUTHORS file |
@ -0,0 +1,439 @@ |
||||
# Go-MySQL-Driver |
||||
|
||||
A MySQL-Driver for Go's [database/sql](http://golang.org/pkg/database/sql) package |
||||
|
||||
![Go-MySQL-Driver logo](https://raw.github.com/wiki/go-sql-driver/mysql/gomysql_m.png "Golang Gopher holding the MySQL Dolphin") |
||||
|
||||
**Latest stable Release:** [Version 1.2 (June 03, 2014)](https://github.com/go-sql-driver/mysql/releases) |
||||
|
||||
[![Build Status](https://travis-ci.org/go-sql-driver/mysql.png?branch=master)](https://travis-ci.org/go-sql-driver/mysql) |
||||
|
||||
--------------------------------------- |
||||
* [Features](#features) |
||||
* [Requirements](#requirements) |
||||
* [Installation](#installation) |
||||
* [Usage](#usage) |
||||
* [DSN (Data Source Name)](#dsn-data-source-name) |
||||
* [Password](#password) |
||||
* [Protocol](#protocol) |
||||
* [Address](#address) |
||||
* [Parameters](#parameters) |
||||
* [Examples](#examples) |
||||
* [LOAD DATA LOCAL INFILE support](#load-data-local-infile-support) |
||||
* [time.Time support](#timetime-support) |
||||
* [Unicode support](#unicode-support) |
||||
* [Testing / Development](#testing--development) |
||||
* [License](#license) |
||||
|
||||
--------------------------------------- |
||||
|
||||
## Features |
||||
* Lightweight and [fast](https://github.com/go-sql-driver/sql-benchmark "golang MySQL-Driver performance") |
||||
* Native Go implementation. No C-bindings, just pure Go |
||||
* Connections over TCP/IPv4, TCP/IPv6, Unix domain sockets or [custom protocols](http://godoc.org/github.com/go-sql-driver/mysql#DialFunc) |
||||
* Automatic handling of broken connections |
||||
* Automatic Connection Pooling *(by database/sql package)* |
||||
* Supports queries larger than 16MB |
||||
* Full [`sql.RawBytes`](http://golang.org/pkg/database/sql/#RawBytes) support. |
||||
* Intelligent `LONG DATA` handling in prepared statements |
||||
* Secure `LOAD DATA LOCAL INFILE` support with file Whitelisting and `io.Reader` support |
||||
* Optional `time.Time` parsing |
||||
* Optional placeholder interpolation |
||||
|
||||
## Requirements |
||||
* Go 1.2 or higher |
||||
* MySQL (4.1+), MariaDB, Percona Server, Google CloudSQL or Sphinx (2.2.3+) |
||||
|
||||
--------------------------------------- |
||||
|
||||
## Installation |
||||
Simple install the package to your [$GOPATH](http://code.google.com/p/go-wiki/wiki/GOPATH "GOPATH") with the [go tool](http://golang.org/cmd/go/ "go command") from shell: |
||||
```bash |
||||
$ go get github.com/go-sql-driver/mysql |
||||
``` |
||||
Make sure [Git is installed](http://git-scm.com/downloads) on your machine and in your system's `PATH`. |
||||
|
||||
## Usage |
||||
_Go MySQL Driver_ is an implementation of Go's `database/sql/driver` interface. You only need to import the driver and can use the full [`database/sql`](http://golang.org/pkg/database/sql) API then. |
||||
|
||||
Use `mysql` as `driverName` and a valid [DSN](#dsn-data-source-name) as `dataSourceName`: |
||||
```go |
||||
import "database/sql" |
||||
import _ "github.com/go-sql-driver/mysql" |
||||
|
||||
db, err := sql.Open("mysql", "user:password@/dbname") |
||||
``` |
||||
|
||||
[Examples are available in our Wiki](https://github.com/go-sql-driver/mysql/wiki/Examples "Go-MySQL-Driver Examples"). |
||||
|
||||
|
||||
### DSN (Data Source Name) |
||||
|
||||
The Data Source Name has a common format, like e.g. [PEAR DB](http://pear.php.net/manual/en/package.database.db.intro-dsn.php) uses it, but without type-prefix (optional parts marked by squared brackets): |
||||
``` |
||||
[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...¶mN=valueN] |
||||
``` |
||||
|
||||
A DSN in its fullest form: |
||||
``` |
||||
username:password@protocol(address)/dbname?param=value |
||||
``` |
||||
|
||||
Except for the databasename, all values are optional. So the minimal DSN is: |
||||
``` |
||||
/dbname |
||||
``` |
||||
|
||||
If you do not want to preselect a database, leave `dbname` empty: |
||||
``` |
||||
/ |
||||
``` |
||||
This has the same effect as an empty DSN string: |
||||
``` |
||||
|
||||
``` |
||||
|
||||
Alternatively, [Config.FormatDSN](https://godoc.org/github.com/go-sql-driver/mysql#Config.FormatDSN) can be used to create a DSN string by filling a struct. |
||||
|
||||
#### Password |
||||
Passwords can consist of any character. Escaping is **not** necessary. |
||||
|
||||
#### Protocol |
||||
See [net.Dial](http://golang.org/pkg/net/#Dial) for more information which networks are available. |
||||
In general you should use an Unix domain socket if available and TCP otherwise for best performance. |
||||
|
||||
#### Address |
||||
For TCP and UDP networks, addresses have the form `host:port`. |
||||
If `host` is a literal IPv6 address, it must be enclosed in square brackets. |
||||
The functions [net.JoinHostPort](http://golang.org/pkg/net/#JoinHostPort) and [net.SplitHostPort](http://golang.org/pkg/net/#SplitHostPort) manipulate addresses in this form. |
||||
|
||||
For Unix domain sockets the address is the absolute path to the MySQL-Server-socket, e.g. `/var/run/mysqld/mysqld.sock` or `/tmp/mysql.sock`. |
||||
|
||||
#### Parameters |
||||
*Parameters are case-sensitive!* |
||||
|
||||
Notice that any of `true`, `TRUE`, `True` or `1` is accepted to stand for a true boolean value. Not surprisingly, false can be specified as any of: `false`, `FALSE`, `False` or `0`. |
||||
|
||||
##### `allowAllFiles` |
||||
|
||||
``` |
||||
Type: bool |
||||
Valid Values: true, false |
||||
Default: false |
||||
``` |
||||
|
||||
`allowAllFiles=true` disables the file Whitelist for `LOAD DATA LOCAL INFILE` and allows *all* files. |
||||
[*Might be insecure!*](http://dev.mysql.com/doc/refman/5.7/en/load-data-local.html) |
||||
|
||||
##### `allowCleartextPasswords` |
||||
|
||||
``` |
||||
Type: bool |
||||
Valid Values: true, false |
||||
Default: false |
||||
``` |
||||
|
||||
`allowCleartextPasswords=true` allows using the [cleartext client side plugin](http://dev.mysql.com/doc/en/cleartext-authentication-plugin.html) if required by an account, such as one defined with the [PAM authentication plugin](http://dev.mysql.com/doc/en/pam-authentication-plugin.html). Sending passwords in clear text may be a security problem in some configurations. To avoid problems if there is any possibility that the password would be intercepted, clients should connect to MySQL Server using a method that protects the password. Possibilities include [TLS / SSL](#tls), IPsec, or a private network. |
||||
|
||||
##### `allowNativePasswords` |
||||
|
||||
``` |
||||
Type: bool |
||||
Valid Values: true, false |
||||
Default: false |
||||
``` |
||||
`allowNativePasswords=true` allows the usage of the mysql native password method. |
||||
|
||||
##### `allowOldPasswords` |
||||
|
||||
``` |
||||
Type: bool |
||||
Valid Values: true, false |
||||
Default: false |
||||
``` |
||||
`allowOldPasswords=true` allows the usage of the insecure old password method. This should be avoided, but is necessary in some cases. See also [the old_passwords wiki page](https://github.com/go-sql-driver/mysql/wiki/old_passwords). |
||||
|
||||
##### `charset` |
||||
|
||||
``` |
||||
Type: string |
||||
Valid Values: <name> |
||||
Default: none |
||||
``` |
||||
|
||||
Sets the charset used for client-server interaction (`"SET NAMES <value>"`). If multiple charsets are set (separated by a comma), the following charset is used if setting the charset failes. This enables for example support for `utf8mb4` ([introduced in MySQL 5.5.3](http://dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html)) with fallback to `utf8` for older servers (`charset=utf8mb4,utf8`). |
||||
|
||||
Usage of the `charset` parameter is discouraged because it issues additional queries to the server. |
||||
Unless you need the fallback behavior, please use `collation` instead. |
||||
|
||||
##### `collation` |
||||
|
||||
``` |
||||
Type: string |
||||
Valid Values: <name> |
||||
Default: utf8_general_ci |
||||
``` |
||||
|
||||
Sets the collation used for client-server interaction on connection. In contrast to `charset`, `collation` does not issue additional queries. If the specified collation is unavailable on the target server, the connection will fail. |
||||
|
||||
A list of valid charsets for a server is retrievable with `SHOW COLLATION`. |
||||
|
||||
##### `clientFoundRows` |
||||
|
||||
``` |
||||
Type: bool |
||||
Valid Values: true, false |
||||
Default: false |
||||
``` |
||||
|
||||
`clientFoundRows=true` causes an UPDATE to return the number of matching rows instead of the number of rows changed. |
||||
|
||||
##### `columnsWithAlias` |
||||
|
||||
``` |
||||
Type: bool |
||||
Valid Values: true, false |
||||
Default: false |
||||
``` |
||||
|
||||
When `columnsWithAlias` is true, calls to `sql.Rows.Columns()` will return the table alias and the column name separated by a dot. For example: |
||||
|
||||
``` |
||||
SELECT u.id FROM users as u |
||||
``` |
||||
|
||||
will return `u.id` instead of just `id` if `columnsWithAlias=true`. |
||||
|
||||
##### `interpolateParams` |
||||
|
||||
``` |
||||
Type: bool |
||||
Valid Values: true, false |
||||
Default: false |
||||
``` |
||||
|
||||
If `interpolateParams` is true, placeholders (`?`) in calls to `db.Query()` and `db.Exec()` are interpolated into a single query string with given parameters. This reduces the number of roundtrips, since the driver has to prepare a statement, execute it with given parameters and close the statement again with `interpolateParams=false`. |
||||
|
||||
*This can not be used together with the multibyte encodings BIG5, CP932, GB2312, GBK or SJIS. These are blacklisted as they may [introduce a SQL injection vulnerability](http://stackoverflow.com/a/12118602/3430118)!* |
||||
|
||||
##### `loc` |
||||
|
||||
``` |
||||
Type: string |
||||
Valid Values: <escaped name> |
||||
Default: UTC |
||||
``` |
||||
|
||||
Sets the location for time.Time values (when using `parseTime=true`). *"Local"* sets the system's location. See [time.LoadLocation](http://golang.org/pkg/time/#LoadLocation) for details. |
||||
|
||||
Note that this sets the location for time.Time values but does not change MySQL's [time_zone setting](https://dev.mysql.com/doc/refman/5.5/en/time-zone-support.html). For that see the [time_zone system variable](#system-variables), which can also be set as a DSN parameter. |
||||
|
||||
Please keep in mind, that param values must be [url.QueryEscape](http://golang.org/pkg/net/url/#QueryEscape)'ed. Alternatively you can manually replace the `/` with `%2F`. For example `US/Pacific` would be `loc=US%2FPacific`. |
||||
|
||||
##### `maxAllowedPacket` |
||||
``` |
||||
Type: decimal number |
||||
Default: 0 |
||||
``` |
||||
|
||||
Max packet size allowed in bytes. Use `maxAllowedPacket=0` to automatically fetch the `max_allowed_packet` variable from server. |
||||
|
||||
##### `multiStatements` |
||||
|
||||
``` |
||||
Type: bool |
||||
Valid Values: true, false |
||||
Default: false |
||||
``` |
||||
|
||||
Allow multiple statements in one query. While this allows batch queries, it also greatly increases the risk of SQL injections. Only the result of the first query is returned, all other results are silently discarded. |
||||
|
||||
When `multiStatements` is used, `?` parameters must only be used in the first statement. |
||||
|
||||
##### `parseTime` |
||||
|
||||
``` |
||||
Type: bool |
||||
Valid Values: true, false |
||||
Default: false |
||||
``` |
||||
|
||||
`parseTime=true` changes the output type of `DATE` and `DATETIME` values to `time.Time` instead of `[]byte` / `string` |
||||
|
||||
|
||||
##### `readTimeout` |
||||
|
||||
``` |
||||
Type: decimal number |
||||
Default: 0 |
||||
``` |
||||
|
||||
I/O read timeout. The value must be a decimal number with an unit suffix ( *"ms"*, *"s"*, *"m"*, *"h"* ), such as *"30s"*, *"0.5m"* or *"1m30s"*. |
||||
|
||||
##### `strict` |
||||
|
||||
``` |
||||
Type: bool |
||||
Valid Values: true, false |
||||
Default: false |
||||
``` |
||||
|
||||
`strict=true` enables a driver-side strict mode in which MySQL warnings are treated as errors. This mode should not be used in production as it may lead to data corruption in certain situations. |
||||
|
||||
A server-side strict mode, which is safe for production use, can be set via the [`sql_mode`](https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html) system variable. |
||||
|
||||
By default MySQL also treats notes as warnings. Use [`sql_notes=false`](http://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_sql_notes) to ignore notes. |
||||
|
||||
##### `timeout` |
||||
|
||||
``` |
||||
Type: decimal number |
||||
Default: OS default |
||||
``` |
||||
|
||||
*Driver* side connection timeout. The value must be a decimal number with an unit suffix ( *"ms"*, *"s"*, *"m"*, *"h"* ), such as *"30s"*, *"0.5m"* or *"1m30s"*. To set a server side timeout, use the parameter [`wait_timeout`](http://dev.mysql.com/doc/refman/5.6/en/server-system-variables.html#sysvar_wait_timeout). |
||||
|
||||
##### `tls` |
||||
|
||||
``` |
||||
Type: bool / string |
||||
Valid Values: true, false, skip-verify, <name> |
||||
Default: false |
||||
``` |
||||
|
||||
`tls=true` enables TLS / SSL encrypted connection to the server. Use `skip-verify` if you want to use a self-signed or invalid certificate (server side). Use a custom value registered with [`mysql.RegisterTLSConfig`](http://godoc.org/github.com/go-sql-driver/mysql#RegisterTLSConfig). |
||||
|
||||
##### `writeTimeout` |
||||
|
||||
``` |
||||
Type: decimal number |
||||
Default: 0 |
||||
``` |
||||
|
||||
I/O write timeout. The value must be a decimal number with an unit suffix ( *"ms"*, *"s"*, *"m"*, *"h"* ), such as *"30s"*, *"0.5m"* or *"1m30s"*. |
||||
|
||||
|
||||
##### System Variables |
||||
|
||||
All other parameters are interpreted as system variables: |
||||
* `autocommit`: `"SET autocommit=<value>"` |
||||
* [`time_zone`](https://dev.mysql.com/doc/refman/5.5/en/time-zone-support.html): `"SET time_zone=<value>"` |
||||
* [`tx_isolation`](https://dev.mysql.com/doc/refman/5.5/en/server-system-variables.html#sysvar_tx_isolation): `"SET tx_isolation=<value>"` |
||||
* `param`: `"SET <param>=<value>"` |
||||
|
||||
*The values must be [url.QueryEscape](http://golang.org/pkg/net/url/#QueryEscape)'ed!* |
||||
|
||||
#### Examples |
||||
``` |
||||
user@unix(/path/to/socket)/dbname |
||||
``` |
||||
|
||||
``` |
||||
root:pw@unix(/tmp/mysql.sock)/myDatabase?loc=Local |
||||
``` |
||||
|
||||
``` |
||||
user:password@tcp(localhost:5555)/dbname?tls=skip-verify&autocommit=true |
||||
``` |
||||
|
||||
Treat warnings as errors by setting the system variable [`sql_mode`](https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html): |
||||
``` |
||||
user:password@/dbname?sql_mode=TRADITIONAL |
||||
``` |
||||
|
||||
TCP via IPv6: |
||||
``` |
||||
user:password@tcp([de:ad:be:ef::ca:fe]:80)/dbname?timeout=90s&collation=utf8mb4_unicode_ci |
||||
``` |
||||
|
||||
TCP on a remote host, e.g. Amazon RDS: |
||||
``` |
||||
id:password@tcp(your-amazonaws-uri.com:3306)/dbname |
||||
``` |
||||
|
||||
Google Cloud SQL on App Engine (First Generation MySQL Server): |
||||
``` |
||||
user@cloudsql(project-id:instance-name)/dbname |
||||
``` |
||||
|
||||
Google Cloud SQL on App Engine (Second Generation MySQL Server): |
||||
``` |
||||
user@cloudsql(project-id:regionname:instance-name)/dbname |
||||
``` |
||||
|
||||
TCP using default port (3306) on localhost: |
||||
``` |
||||
user:password@tcp/dbname?charset=utf8mb4,utf8&sys_var=esc%40ped |
||||
``` |
||||
|
||||
Use the default protocol (tcp) and host (localhost:3306): |
||||
``` |
||||
user:password@/dbname |
||||
``` |
||||
|
||||
No Database preselected: |
||||
``` |
||||
user:password@/ |
||||
``` |
||||
|
||||
### `LOAD DATA LOCAL INFILE` support |
||||
For this feature you need direct access to the package. Therefore you must change the import path (no `_`): |
||||
```go |
||||
import "github.com/go-sql-driver/mysql" |
||||
``` |
||||
|
||||
Files must be whitelisted by registering them with `mysql.RegisterLocalFile(filepath)` (recommended) or the Whitelist check must be deactivated by using the DSN parameter `allowAllFiles=true` ([*Might be insecure!*](http://dev.mysql.com/doc/refman/5.7/en/load-data-local.html)). |
||||
|
||||
To use a `io.Reader` a handler function must be registered with `mysql.RegisterReaderHandler(name, handler)` which returns a `io.Reader` or `io.ReadCloser`. The Reader is available with the filepath `Reader::<name>` then. Choose different names for different handlers and `DeregisterReaderHandler` when you don't need it anymore. |
||||
|
||||
See the [godoc of Go-MySQL-Driver](http://godoc.org/github.com/go-sql-driver/mysql "golang mysql driver documentation") for details. |
||||
|
||||
|
||||
### `time.Time` support |
||||
The default internal output type of MySQL `DATE` and `DATETIME` values is `[]byte` which allows you to scan the value into a `[]byte`, `string` or `sql.RawBytes` variable in your programm. |
||||
|
||||
However, many want to scan MySQL `DATE` and `DATETIME` values into `time.Time` variables, which is the logical opposite in Go to `DATE` and `DATETIME` in MySQL. You can do that by changing the internal output type from `[]byte` to `time.Time` with the DSN parameter `parseTime=true`. You can set the default [`time.Time` location](http://golang.org/pkg/time/#Location) with the `loc` DSN parameter. |
||||
|
||||
**Caution:** As of Go 1.1, this makes `time.Time` the only variable type you can scan `DATE` and `DATETIME` values into. This breaks for example [`sql.RawBytes` support](https://github.com/go-sql-driver/mysql/wiki/Examples#rawbytes). |
||||
|
||||
Alternatively you can use the [`NullTime`](http://godoc.org/github.com/go-sql-driver/mysql#NullTime) type as the scan destination, which works with both `time.Time` and `string` / `[]byte`. |
||||
|
||||
|
||||
### Unicode support |
||||
Since version 1.1 Go-MySQL-Driver automatically uses the collation `utf8_general_ci` by default. |
||||
|
||||
Other collations / charsets can be set using the [`collation`](#collation) DSN parameter. |
||||
|
||||
Version 1.0 of the driver recommended adding `&charset=utf8` (alias for `SET NAMES utf8`) to the DSN to enable proper UTF-8 support. This is not necessary anymore. The [`collation`](#collation) parameter should be preferred to set another collation / charset than the default. |
||||
|
||||
See http://dev.mysql.com/doc/refman/5.7/en/charset-unicode.html for more details on MySQL's Unicode support. |
||||
|
||||
|
||||
## Testing / Development |
||||
To run the driver tests you may need to adjust the configuration. See the [Testing Wiki-Page](https://github.com/go-sql-driver/mysql/wiki/Testing "Testing") for details. |
||||
|
||||
Go-MySQL-Driver is not feature-complete yet. Your help is very appreciated. |
||||
If you want to contribute, you can work on an [open issue](https://github.com/go-sql-driver/mysql/issues?state=open) or review a [pull request](https://github.com/go-sql-driver/mysql/pulls). |
||||
|
||||
See the [Contribution Guidelines](https://github.com/go-sql-driver/mysql/blob/master/CONTRIBUTING.md) for details. |
||||
|
||||
--------------------------------------- |
||||
|
||||
## License |
||||
Go-MySQL-Driver is licensed under the [Mozilla Public License Version 2.0](https://raw.github.com/go-sql-driver/mysql/master/LICENSE) |
||||
|
||||
Mozilla summarizes the license scope as follows: |
||||
> MPL: The copyleft applies to any files containing MPLed code. |
||||
|
||||
|
||||
That means: |
||||
* You can **use** the **unchanged** source code both in private and commercially |
||||
* When distributing, you **must publish** the source code of any **changed files** licensed under the MPL 2.0 under a) the MPL 2.0 itself or b) a compatible license (e.g. GPL 3.0 or Apache License 2.0) |
||||
* You **needn't publish** the source code of your library as long as the files licensed under the MPL 2.0 are **unchanged** |
||||
|
||||
Please read the [MPL 2.0 FAQ](http://www.mozilla.org/MPL/2.0/FAQ.html) if you have further questions regarding the license. |
||||
|
||||
You can read the full terms here: [LICENSE](https://raw.github.com/go-sql-driver/mysql/master/LICENSE) |
||||
|
||||
![Go Gopher and MySQL Dolphin](https://raw.github.com/wiki/go-sql-driver/mysql/go-mysql-driver_m.jpg "Golang Gopher transporting the MySQL Dolphin in a wheelbarrow") |
||||
|
@ -0,0 +1,19 @@ |
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// +build appengine
|
||||
|
||||
package mysql |
||||
|
||||
import ( |
||||
"appengine/cloudsql" |
||||
) |
||||
|
||||
func init() { |
||||
RegisterDial("cloudsql", cloudsql.Dial) |
||||
} |
@ -0,0 +1,147 @@ |
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql |
||||
|
||||
import ( |
||||
"io" |
||||
"net" |
||||
"time" |
||||
) |
||||
|
||||
const defaultBufSize = 4096 |
||||
|
||||
// A buffer which is used for both reading and writing.
|
||||
// This is possible since communication on each connection is synchronous.
|
||||
// In other words, we can't write and read simultaneously on the same connection.
|
||||
// The buffer is similar to bufio.Reader / Writer but zero-copy-ish
|
||||
// Also highly optimized for this particular use case.
|
||||
type buffer struct { |
||||
buf []byte |
||||
nc net.Conn |
||||
idx int |
||||
length int |
||||
timeout time.Duration |
||||
} |
||||
|
||||
func newBuffer(nc net.Conn) buffer { |
||||
var b [defaultBufSize]byte |
||||
return buffer{ |
||||
buf: b[:], |
||||
nc: nc, |
||||
} |
||||
} |
||||
|
||||
// fill reads into the buffer until at least _need_ bytes are in it
|
||||
func (b *buffer) fill(need int) error { |
||||
n := b.length |
||||
|
||||
// move existing data to the beginning
|
||||
if n > 0 && b.idx > 0 { |
||||
copy(b.buf[0:n], b.buf[b.idx:]) |
||||
} |
||||
|
||||
// grow buffer if necessary
|
||||
// TODO: let the buffer shrink again at some point
|
||||
// Maybe keep the org buf slice and swap back?
|
||||
if need > len(b.buf) { |
||||
// Round up to the next multiple of the default size
|
||||
newBuf := make([]byte, ((need/defaultBufSize)+1)*defaultBufSize) |
||||
copy(newBuf, b.buf) |
||||
b.buf = newBuf |
||||
} |
||||
|
||||
b.idx = 0 |
||||
|
||||
for { |
||||
if b.timeout > 0 { |
||||
if err := b.nc.SetReadDeadline(time.Now().Add(b.timeout)); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
|
||||
nn, err := b.nc.Read(b.buf[n:]) |
||||
n += nn |
||||
|
||||
switch err { |
||||
case nil: |
||||
if n < need { |
||||
continue |
||||
} |
||||
b.length = n |
||||
return nil |
||||
|
||||
case io.EOF: |
||||
if n >= need { |
||||
b.length = n |
||||
return nil |
||||
} |
||||
return io.ErrUnexpectedEOF |
||||
|
||||
default: |
||||
return err |
||||
} |
||||
} |
||||
} |
||||
|
||||
// returns next N bytes from buffer.
|
||||
// The returned slice is only guaranteed to be valid until the next read
|
||||
func (b *buffer) readNext(need int) ([]byte, error) { |
||||
if b.length < need { |
||||
// refill
|
||||
if err := b.fill(need); err != nil { |
||||
return nil, err |
||||
} |
||||
} |
||||
|
||||
offset := b.idx |
||||
b.idx += need |
||||
b.length -= need |
||||
return b.buf[offset:b.idx], nil |
||||
} |
||||
|
||||
// returns a buffer with the requested size.
|
||||
// If possible, a slice from the existing buffer is returned.
|
||||
// Otherwise a bigger buffer is made.
|
||||
// Only one buffer (total) can be used at a time.
|
||||
func (b *buffer) takeBuffer(length int) []byte { |
||||
if b.length > 0 { |
||||
return nil |
||||
} |
||||
|
||||
// test (cheap) general case first
|
||||
if length <= defaultBufSize || length <= cap(b.buf) { |
||||
return b.buf[:length] |
||||
} |
||||
|
||||
if length < maxPacketSize { |
||||
b.buf = make([]byte, length) |
||||
return b.buf |
||||
} |
||||
return make([]byte, length) |
||||
} |
||||
|
||||
// shortcut which can be used if the requested buffer is guaranteed to be
|
||||
// smaller than defaultBufSize
|
||||
// Only one buffer (total) can be used at a time.
|
||||
func (b *buffer) takeSmallBuffer(length int) []byte { |
||||
if b.length == 0 { |
||||
return b.buf[:length] |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// takeCompleteBuffer returns the complete existing buffer.
|
||||
// This can be used if the necessary buffer size is unknown.
|
||||
// Only one buffer (total) can be used at a time.
|
||||
func (b *buffer) takeCompleteBuffer() []byte { |
||||
if b.length == 0 { |
||||
return b.buf |
||||
} |
||||
return nil |
||||
} |
@ -0,0 +1,250 @@ |
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2014 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql |
||||
|
||||
const defaultCollation = "utf8_general_ci" |
||||
|
||||
// A list of available collations mapped to the internal ID.
|
||||
// To update this map use the following MySQL query:
|
||||
// SELECT COLLATION_NAME, ID FROM information_schema.COLLATIONS
|
||||
var collations = map[string]byte{ |
||||
"big5_chinese_ci": 1, |
||||
"latin2_czech_cs": 2, |
||||
"dec8_swedish_ci": 3, |
||||
"cp850_general_ci": 4, |
||||
"latin1_german1_ci": 5, |
||||
"hp8_english_ci": 6, |
||||
"koi8r_general_ci": 7, |
||||
"latin1_swedish_ci": 8, |
||||
"latin2_general_ci": 9, |
||||
"swe7_swedish_ci": 10, |
||||
"ascii_general_ci": 11, |
||||
"ujis_japanese_ci": 12, |
||||
"sjis_japanese_ci": 13, |
||||
"cp1251_bulgarian_ci": 14, |
||||
"latin1_danish_ci": 15, |
||||
"hebrew_general_ci": 16, |
||||
"tis620_thai_ci": 18, |
||||
"euckr_korean_ci": 19, |
||||
"latin7_estonian_cs": 20, |
||||
"latin2_hungarian_ci": 21, |
||||
"koi8u_general_ci": 22, |
||||
"cp1251_ukrainian_ci": 23, |
||||
"gb2312_chinese_ci": 24, |
||||
"greek_general_ci": 25, |
||||
"cp1250_general_ci": 26, |
||||
"latin2_croatian_ci": 27, |
||||
"gbk_chinese_ci": 28, |
||||
"cp1257_lithuanian_ci": 29, |
||||
"latin5_turkish_ci": 30, |
||||
"latin1_german2_ci": 31, |
||||
"armscii8_general_ci": 32, |
||||
"utf8_general_ci": 33, |
||||
"cp1250_czech_cs": 34, |
||||
"ucs2_general_ci": 35, |
||||
"cp866_general_ci": 36, |
||||
"keybcs2_general_ci": 37, |
||||
"macce_general_ci": 38, |
||||
"macroman_general_ci": 39, |
||||
"cp852_general_ci": 40, |
||||
"latin7_general_ci": 41, |
||||
"latin7_general_cs": 42, |
||||
"macce_bin": 43, |
||||
"cp1250_croatian_ci": 44, |
||||
"utf8mb4_general_ci": 45, |
||||
"utf8mb4_bin": 46, |
||||
"latin1_bin": 47, |
||||
"latin1_general_ci": 48, |
||||
"latin1_general_cs": 49, |
||||
"cp1251_bin": 50, |
||||
"cp1251_general_ci": 51, |
||||
"cp1251_general_cs": 52, |
||||
"macroman_bin": 53, |
||||
"utf16_general_ci": 54, |
||||
"utf16_bin": 55, |
||||
"utf16le_general_ci": 56, |
||||
"cp1256_general_ci": 57, |
||||
"cp1257_bin": 58, |
||||
"cp1257_general_ci": 59, |
||||
"utf32_general_ci": 60, |
||||
"utf32_bin": 61, |
||||
"utf16le_bin": 62, |
||||
"binary": 63, |
||||
"armscii8_bin": 64, |
||||
"ascii_bin": 65, |
||||
"cp1250_bin": 66, |
||||
"cp1256_bin": 67, |
||||
"cp866_bin": 68, |
||||
"dec8_bin": 69, |
||||
"greek_bin": 70, |
||||
"hebrew_bin": 71, |
||||
"hp8_bin": 72, |
||||
"keybcs2_bin": 73, |
||||
"koi8r_bin": 74, |
||||
"koi8u_bin": 75, |
||||
"latin2_bin": 77, |
||||
"latin5_bin": 78, |
||||
"latin7_bin": 79, |
||||
"cp850_bin": 80, |
||||
"cp852_bin": 81, |
||||
"swe7_bin": 82, |
||||
"utf8_bin": 83, |
||||
"big5_bin": 84, |
||||
"euckr_bin": 85, |
||||
"gb2312_bin": 86, |
||||
"gbk_bin": 87, |
||||
"sjis_bin": 88, |
||||
"tis620_bin": 89, |
||||
"ucs2_bin": 90, |
||||
"ujis_bin": 91, |
||||
"geostd8_general_ci": 92, |
||||
"geostd8_bin": 93, |
||||
"latin1_spanish_ci": 94, |
||||
"cp932_japanese_ci": 95, |
||||
"cp932_bin": 96, |
||||
"eucjpms_japanese_ci": 97, |
||||
"eucjpms_bin": 98, |
||||
"cp1250_polish_ci": 99, |
||||
"utf16_unicode_ci": 101, |
||||
"utf16_icelandic_ci": 102, |
||||
"utf16_latvian_ci": 103, |
||||
"utf16_romanian_ci": 104, |
||||
"utf16_slovenian_ci": 105, |
||||
"utf16_polish_ci": 106, |
||||
"utf16_estonian_ci": 107, |
||||
"utf16_spanish_ci": 108, |
||||
"utf16_swedish_ci": 109, |
||||
"utf16_turkish_ci": 110, |
||||
"utf16_czech_ci": 111, |
||||
"utf16_danish_ci": 112, |
||||
"utf16_lithuanian_ci": 113, |
||||
"utf16_slovak_ci": 114, |
||||
"utf16_spanish2_ci": 115, |
||||
"utf16_roman_ci": 116, |
||||
"utf16_persian_ci": 117, |
||||
"utf16_esperanto_ci": 118, |
||||
"utf16_hungarian_ci": 119, |
||||
"utf16_sinhala_ci": 120, |
||||
"utf16_german2_ci": 121, |
||||
"utf16_croatian_ci": 122, |
||||
"utf16_unicode_520_ci": 123, |
||||
"utf16_vietnamese_ci": 124, |
||||
"ucs2_unicode_ci": 128, |
||||
"ucs2_icelandic_ci": 129, |
||||
"ucs2_latvian_ci": 130, |
||||
"ucs2_romanian_ci": 131, |
||||
"ucs2_slovenian_ci": 132, |
||||
"ucs2_polish_ci": 133, |
||||
"ucs2_estonian_ci": 134, |
||||
"ucs2_spanish_ci": 135, |
||||
"ucs2_swedish_ci": 136, |
||||
"ucs2_turkish_ci": 137, |
||||
"ucs2_czech_ci": 138, |
||||
"ucs2_danish_ci": 139, |
||||
"ucs2_lithuanian_ci": 140, |
||||
"ucs2_slovak_ci": 141, |
||||
"ucs2_spanish2_ci": 142, |
||||
"ucs2_roman_ci": 143, |
||||
"ucs2_persian_ci": 144, |
||||
"ucs2_esperanto_ci": 145, |
||||
"ucs2_hungarian_ci": 146, |
||||
"ucs2_sinhala_ci": 147, |
||||
"ucs2_german2_ci": 148, |
||||
"ucs2_croatian_ci": 149, |
||||
"ucs2_unicode_520_ci": 150, |
||||
"ucs2_vietnamese_ci": 151, |
||||
"ucs2_general_mysql500_ci": 159, |
||||
"utf32_unicode_ci": 160, |
||||
"utf32_icelandic_ci": 161, |
||||
"utf32_latvian_ci": 162, |
||||
"utf32_romanian_ci": 163, |
||||
"utf32_slovenian_ci": 164, |
||||
"utf32_polish_ci": 165, |
||||
"utf32_estonian_ci": 166, |
||||
"utf32_spanish_ci": 167, |
||||
"utf32_swedish_ci": 168, |
||||
"utf32_turkish_ci": 169, |
||||
"utf32_czech_ci": 170, |
||||
"utf32_danish_ci": 171, |
||||
"utf32_lithuanian_ci": 172, |
||||
"utf32_slovak_ci": 173, |
||||
"utf32_spanish2_ci": 174, |
||||
"utf32_roman_ci": 175, |
||||
"utf32_persian_ci": 176, |
||||
"utf32_esperanto_ci": 177, |
||||
"utf32_hungarian_ci": 178, |
||||
"utf32_sinhala_ci": 179, |
||||
"utf32_german2_ci": 180, |
||||
"utf32_croatian_ci": 181, |
||||
"utf32_unicode_520_ci": 182, |
||||
"utf32_vietnamese_ci": 183, |
||||
"utf8_unicode_ci": 192, |
||||
"utf8_icelandic_ci": 193, |
||||
"utf8_latvian_ci": 194, |
||||
"utf8_romanian_ci": 195, |
||||
"utf8_slovenian_ci": 196, |
||||
"utf8_polish_ci": 197, |
||||
"utf8_estonian_ci": 198, |
||||
"utf8_spanish_ci": 199, |
||||
"utf8_swedish_ci": 200, |
||||
"utf8_turkish_ci": 201, |
||||
"utf8_czech_ci": 202, |
||||
"utf8_danish_ci": 203, |
||||
"utf8_lithuanian_ci": 204, |
||||
"utf8_slovak_ci": 205, |
||||
"utf8_spanish2_ci": 206, |
||||
"utf8_roman_ci": 207, |
||||
"utf8_persian_ci": 208, |
||||
"utf8_esperanto_ci": 209, |
||||
"utf8_hungarian_ci": 210, |
||||
"utf8_sinhala_ci": 211, |
||||
"utf8_german2_ci": 212, |
||||
"utf8_croatian_ci": 213, |
||||
"utf8_unicode_520_ci": 214, |
||||
"utf8_vietnamese_ci": 215, |
||||
"utf8_general_mysql500_ci": 223, |
||||
"utf8mb4_unicode_ci": 224, |
||||
"utf8mb4_icelandic_ci": 225, |
||||
"utf8mb4_latvian_ci": 226, |
||||
"utf8mb4_romanian_ci": 227, |
||||
"utf8mb4_slovenian_ci": 228, |
||||
"utf8mb4_polish_ci": 229, |
||||
"utf8mb4_estonian_ci": 230, |
||||
"utf8mb4_spanish_ci": 231, |
||||
"utf8mb4_swedish_ci": 232, |
||||
"utf8mb4_turkish_ci": 233, |
||||
"utf8mb4_czech_ci": 234, |
||||
"utf8mb4_danish_ci": 235, |
||||
"utf8mb4_lithuanian_ci": 236, |
||||
"utf8mb4_slovak_ci": 237, |
||||
"utf8mb4_spanish2_ci": 238, |
||||
"utf8mb4_roman_ci": 239, |
||||
"utf8mb4_persian_ci": 240, |
||||
"utf8mb4_esperanto_ci": 241, |
||||
"utf8mb4_hungarian_ci": 242, |
||||
"utf8mb4_sinhala_ci": 243, |
||||
"utf8mb4_german2_ci": 244, |
||||
"utf8mb4_croatian_ci": 245, |
||||
"utf8mb4_unicode_520_ci": 246, |
||||
"utf8mb4_vietnamese_ci": 247, |
||||
} |
||||
|
||||
// A blacklist of collations which is unsafe to interpolate parameters.
|
||||
// These multibyte encodings may contains 0x5c (`\`) in their trailing bytes.
|
||||
var unsafeCollations = map[string]bool{ |
||||
"big5_chinese_ci": true, |
||||
"sjis_japanese_ci": true, |
||||
"gbk_chinese_ci": true, |
||||
"big5_bin": true, |
||||
"gb2312_bin": true, |
||||
"gbk_bin": true, |
||||
"sjis_bin": true, |
||||
"cp932_japanese_ci": true, |
||||
"cp932_bin": true, |
||||
} |
@ -0,0 +1,377 @@ |
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql |
||||
|
||||
import ( |
||||
"database/sql/driver" |
||||
"net" |
||||
"strconv" |
||||
"strings" |
||||
"time" |
||||
) |
||||
|
||||
type mysqlConn struct { |
||||
buf buffer |
||||
netConn net.Conn |
||||
affectedRows uint64 |
||||
insertId uint64 |
||||
cfg *Config |
||||
maxAllowedPacket int |
||||
maxWriteSize int |
||||
writeTimeout time.Duration |
||||
flags clientFlag |
||||
status statusFlag |
||||
sequence uint8 |
||||
parseTime bool |
||||
strict bool |
||||
} |
||||
|
||||
// Handles parameters set in DSN after the connection is established
|
||||
func (mc *mysqlConn) handleParams() (err error) { |
||||
for param, val := range mc.cfg.Params { |
||||
switch param { |
||||
// Charset
|
||||
case "charset": |
||||
charsets := strings.Split(val, ",") |
||||
for i := range charsets { |
||||
// ignore errors here - a charset may not exist
|
||||
err = mc.exec("SET NAMES " + charsets[i]) |
||||
if err == nil { |
||||
break |
||||
} |
||||
} |
||||
if err != nil { |
||||
return |
||||
} |
||||
|
||||
// System Vars
|
||||
default: |
||||
err = mc.exec("SET " + param + "=" + val + "") |
||||
if err != nil { |
||||
return |
||||
} |
||||
} |
||||
} |
||||
|
||||
return |
||||
} |
||||
|
||||
func (mc *mysqlConn) Begin() (driver.Tx, error) { |
||||
if mc.netConn == nil { |
||||
errLog.Print(ErrInvalidConn) |
||||
return nil, driver.ErrBadConn |
||||
} |
||||
err := mc.exec("START TRANSACTION") |
||||
if err == nil { |
||||
return &mysqlTx{mc}, err |
||||
} |
||||
|
||||
return nil, err |
||||
} |
||||
|
||||
func (mc *mysqlConn) Close() (err error) { |
||||
// Makes Close idempotent
|
||||
if mc.netConn != nil { |
||||
err = mc.writeCommandPacket(comQuit) |
||||
} |
||||
|
||||
mc.cleanup() |
||||
|
||||
return |
||||
} |
||||
|
||||
// Closes the network connection and unsets internal variables. Do not call this
|
||||
// function after successfully authentication, call Close instead. This function
|
||||
// is called before auth or on auth failure because MySQL will have already
|
||||
// closed the network connection.
|
||||
func (mc *mysqlConn) cleanup() { |
||||
// Makes cleanup idempotent
|
||||
if mc.netConn != nil { |
||||
if err := mc.netConn.Close(); err != nil { |
||||
errLog.Print(err) |
||||
} |
||||
mc.netConn = nil |
||||
} |
||||
mc.cfg = nil |
||||
mc.buf.nc = nil |
||||
} |
||||
|
||||
func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) { |
||||
if mc.netConn == nil { |
||||
errLog.Print(ErrInvalidConn) |
||||
return nil, driver.ErrBadConn |
||||
} |
||||
// Send command
|
||||
err := mc.writeCommandPacketStr(comStmtPrepare, query) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
stmt := &mysqlStmt{ |
||||
mc: mc, |
||||
} |
||||
|
||||
// Read Result
|
||||
columnCount, err := stmt.readPrepareResultPacket() |
||||
if err == nil { |
||||
if stmt.paramCount > 0 { |
||||
if err = mc.readUntilEOF(); err != nil { |
||||
return nil, err |
||||
} |
||||
} |
||||
|
||||
if columnCount > 0 { |
||||
err = mc.readUntilEOF() |
||||
} |
||||
} |
||||
|
||||
return stmt, err |
||||
} |
||||
|
||||
func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (string, error) { |
||||
// Number of ? should be same to len(args)
|
||||
if strings.Count(query, "?") != len(args) { |
||||
return "", driver.ErrSkip |
||||
} |
||||
|
||||
buf := mc.buf.takeCompleteBuffer() |
||||
if buf == nil { |
||||
// can not take the buffer. Something must be wrong with the connection
|
||||
errLog.Print(ErrBusyBuffer) |
||||
return "", driver.ErrBadConn |
||||
} |
||||
buf = buf[:0] |
||||
argPos := 0 |
||||
|
||||
for i := 0; i < len(query); i++ { |
||||
q := strings.IndexByte(query[i:], '?') |
||||
if q == -1 { |
||||
buf = append(buf, query[i:]...) |
||||
break |
||||
} |
||||
buf = append(buf, query[i:i+q]...) |
||||
i += q |
||||
|
||||
arg := args[argPos] |
||||
argPos++ |
||||
|
||||
if arg == nil { |
||||
buf = append(buf, "NULL"...) |
||||
continue |
||||
} |
||||
|
||||
switch v := arg.(type) { |
||||
case int64: |
||||
buf = strconv.AppendInt(buf, v, 10) |
||||
case float64: |
||||
buf = strconv.AppendFloat(buf, v, 'g', -1, 64) |
||||
case bool: |
||||
if v { |
||||
buf = append(buf, '1') |
||||
} else { |
||||
buf = append(buf, '0') |
||||
} |
||||
case time.Time: |
||||
if v.IsZero() { |
||||
buf = append(buf, "'0000-00-00'"...) |
||||
} else { |
||||
v := v.In(mc.cfg.Loc) |
||||
v = v.Add(time.Nanosecond * 500) // To round under microsecond
|
||||
year := v.Year() |
||||
year100 := year / 100 |
||||
year1 := year % 100 |
||||
month := v.Month() |
||||
day := v.Day() |
||||
hour := v.Hour() |
||||
minute := v.Minute() |
||||
second := v.Second() |
||||
micro := v.Nanosecond() / 1000 |
||||
|
||||
buf = append(buf, []byte{ |
||||
'\'', |
||||
digits10[year100], digits01[year100], |
||||
digits10[year1], digits01[year1], |
||||
'-', |
||||
digits10[month], digits01[month], |
||||
'-', |
||||
digits10[day], digits01[day], |
||||
' ', |
||||
digits10[hour], digits01[hour], |
||||
':', |
||||
digits10[minute], digits01[minute], |
||||
':', |
||||
digits10[second], digits01[second], |
||||
}...) |
||||
|
||||
if micro != 0 { |
||||
micro10000 := micro / 10000 |
||||
micro100 := micro / 100 % 100 |
||||
micro1 := micro % 100 |
||||
buf = append(buf, []byte{ |
||||
'.', |
||||
digits10[micro10000], digits01[micro10000], |
||||
digits10[micro100], digits01[micro100], |
||||
digits10[micro1], digits01[micro1], |
||||
}...) |
||||
} |
||||
buf = append(buf, '\'') |
||||
} |
||||
case []byte: |
||||
if v == nil { |
||||
buf = append(buf, "NULL"...) |
||||
} else { |
||||
buf = append(buf, "_binary'"...) |
||||
if mc.status&statusNoBackslashEscapes == 0 { |
||||
buf = escapeBytesBackslash(buf, v) |
||||
} else { |
||||
buf = escapeBytesQuotes(buf, v) |
||||
} |
||||
buf = append(buf, '\'') |
||||
} |
||||
case string: |
||||
buf = append(buf, '\'') |
||||
if mc.status&statusNoBackslashEscapes == 0 { |
||||
buf = escapeStringBackslash(buf, v) |
||||
} else { |
||||
buf = escapeStringQuotes(buf, v) |
||||
} |
||||
buf = append(buf, '\'') |
||||
default: |
||||
return "", driver.ErrSkip |
||||
} |
||||
|
||||
if len(buf)+4 > mc.maxAllowedPacket { |
||||
return "", driver.ErrSkip |
||||
} |
||||
} |
||||
if argPos != len(args) { |
||||
return "", driver.ErrSkip |
||||
} |
||||
return string(buf), nil |
||||
} |
||||
|
||||
func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, error) { |
||||
if mc.netConn == nil { |
||||
errLog.Print(ErrInvalidConn) |
||||
return nil, driver.ErrBadConn |
||||
} |
||||
if len(args) != 0 { |
||||
if !mc.cfg.InterpolateParams { |
||||
return nil, driver.ErrSkip |
||||
} |
||||
// try to interpolate the parameters to save extra roundtrips for preparing and closing a statement
|
||||
prepared, err := mc.interpolateParams(query, args) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
query = prepared |
||||
args = nil |
||||
} |
||||
mc.affectedRows = 0 |
||||
mc.insertId = 0 |
||||
|
||||
err := mc.exec(query) |
||||
if err == nil { |
||||
return &mysqlResult{ |
||||
affectedRows: int64(mc.affectedRows), |
||||
insertId: int64(mc.insertId), |
||||
}, err |
||||
} |
||||
return nil, err |
||||
} |
||||
|
||||
// Internal function to execute commands
|
||||
func (mc *mysqlConn) exec(query string) error { |
||||
// Send command
|
||||
err := mc.writeCommandPacketStr(comQuery, query) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
// Read Result
|
||||
resLen, err := mc.readResultSetHeaderPacket() |
||||
if err == nil && resLen > 0 { |
||||
if err = mc.readUntilEOF(); err != nil { |
||||
return err |
||||
} |
||||
|
||||
err = mc.readUntilEOF() |
||||
} |
||||
|
||||
return err |
||||
} |
||||
|
||||
func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, error) { |
||||
if mc.netConn == nil { |
||||
errLog.Print(ErrInvalidConn) |
||||
return nil, driver.ErrBadConn |
||||
} |
||||
if len(args) != 0 { |
||||
if !mc.cfg.InterpolateParams { |
||||
return nil, driver.ErrSkip |
||||
} |
||||
// try client-side prepare to reduce roundtrip
|
||||
prepared, err := mc.interpolateParams(query, args) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
query = prepared |
||||
args = nil |
||||
} |
||||
// Send command
|
||||
err := mc.writeCommandPacketStr(comQuery, query) |
||||
if err == nil { |
||||
// Read Result
|
||||
var resLen int |
||||
resLen, err = mc.readResultSetHeaderPacket() |
||||
if err == nil { |
||||
rows := new(textRows) |
||||
rows.mc = mc |
||||
|
||||
if resLen == 0 { |
||||
// no columns, no more data
|
||||
return emptyRows{}, nil |
||||
} |
||||
// Columns
|
||||
rows.columns, err = mc.readColumns(resLen) |
||||
return rows, err |
||||
} |
||||
} |
||||
return nil, err |
||||
} |
||||
|
||||
// Gets the value of the given MySQL System Variable
|
||||
// The returned byte slice is only valid until the next read
|
||||
func (mc *mysqlConn) getSystemVar(name string) ([]byte, error) { |
||||
// Send command
|
||||
if err := mc.writeCommandPacketStr(comQuery, "SELECT @@"+name); err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
// Read Result
|
||||
resLen, err := mc.readResultSetHeaderPacket() |
||||
if err == nil { |
||||
rows := new(textRows) |
||||
rows.mc = mc |
||||
rows.columns = []mysqlField{{fieldType: fieldTypeVarChar}} |
||||
|
||||
if resLen > 0 { |
||||
// Columns
|
||||
if err := mc.readUntilEOF(); err != nil { |
||||
return nil, err |
||||
} |
||||
} |
||||
|
||||
dest := make([]driver.Value, resLen) |
||||
if err = rows.readRow(dest); err == nil { |
||||
return dest[0].([]byte), mc.readUntilEOF() |
||||
} |
||||
} |
||||
return nil, err |
||||
} |
@ -0,0 +1,163 @@ |
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql |
||||
|
||||
const ( |
||||
minProtocolVersion byte = 10 |
||||
maxPacketSize = 1<<24 - 1 |
||||
timeFormat = "2006-01-02 15:04:05.999999" |
||||
) |
||||
|
||||
// MySQL constants documentation:
|
||||
// http://dev.mysql.com/doc/internals/en/client-server-protocol.html
|
||||
|
||||
const ( |
||||
iOK byte = 0x00 |
||||
iLocalInFile byte = 0xfb |
||||
iEOF byte = 0xfe |
||||
iERR byte = 0xff |
||||
) |
||||
|
||||
// https://dev.mysql.com/doc/internals/en/capability-flags.html#packet-Protocol::CapabilityFlags
|
||||
type clientFlag uint32 |
||||
|
||||
const ( |
||||
clientLongPassword clientFlag = 1 << iota |
||||
clientFoundRows |
||||
clientLongFlag |
||||
clientConnectWithDB |
||||
clientNoSchema |
||||
clientCompress |
||||
clientODBC |
||||
clientLocalFiles |
||||
clientIgnoreSpace |
||||
clientProtocol41 |
||||
clientInteractive |
||||
clientSSL |
||||
clientIgnoreSIGPIPE |
||||
clientTransactions |
||||
clientReserved |
||||
clientSecureConn |
||||
clientMultiStatements |
||||
clientMultiResults |
||||
clientPSMultiResults |
||||
clientPluginAuth |
||||
clientConnectAttrs |
||||
clientPluginAuthLenEncClientData |
||||
clientCanHandleExpiredPasswords |
||||
clientSessionTrack |
||||
clientDeprecateEOF |
||||
) |
||||
|
||||
const ( |
||||
comQuit byte = iota + 1 |
||||
comInitDB |
||||
comQuery |
||||
comFieldList |
||||
comCreateDB |
||||
comDropDB |
||||
comRefresh |
||||
comShutdown |
||||
comStatistics |
||||
comProcessInfo |
||||
comConnect |
||||
comProcessKill |
||||
comDebug |
||||
comPing |
||||
comTime |
||||
comDelayedInsert |
||||
comChangeUser |
||||
comBinlogDump |
||||
comTableDump |
||||
comConnectOut |
||||
comRegisterSlave |
||||
comStmtPrepare |
||||
comStmtExecute |
||||
comStmtSendLongData |
||||
comStmtClose |
||||
comStmtReset |
||||
comSetOption |
||||
comStmtFetch |
||||
) |
||||
|
||||
// https://dev.mysql.com/doc/internals/en/com-query-response.html#packet-Protocol::ColumnType
|
||||
const ( |
||||
fieldTypeDecimal byte = iota |
||||
fieldTypeTiny |
||||
fieldTypeShort |
||||
fieldTypeLong |
||||
fieldTypeFloat |
||||
fieldTypeDouble |
||||
fieldTypeNULL |
||||
fieldTypeTimestamp |
||||
fieldTypeLongLong |
||||
fieldTypeInt24 |
||||
fieldTypeDate |
||||
fieldTypeTime |
||||
fieldTypeDateTime |
||||
fieldTypeYear |
||||
fieldTypeNewDate |
||||
fieldTypeVarChar |
||||
fieldTypeBit |
||||
) |
||||
const ( |
||||
fieldTypeJSON byte = iota + 0xf5 |
||||
fieldTypeNewDecimal |
||||
fieldTypeEnum |
||||
fieldTypeSet |
||||
fieldTypeTinyBLOB |
||||
fieldTypeMediumBLOB |
||||
fieldTypeLongBLOB |
||||
fieldTypeBLOB |
||||
fieldTypeVarString |
||||
fieldTypeString |
||||
fieldTypeGeometry |
||||
) |
||||
|
||||
type fieldFlag uint16 |
||||
|
||||
const ( |
||||
flagNotNULL fieldFlag = 1 << iota |
||||
flagPriKey |
||||
flagUniqueKey |
||||
flagMultipleKey |
||||
flagBLOB |
||||
flagUnsigned |
||||
flagZeroFill |
||||
flagBinary |
||||
flagEnum |
||||
flagAutoIncrement |
||||
flagTimestamp |
||||
flagSet |
||||
flagUnknown1 |
||||
flagUnknown2 |
||||
flagUnknown3 |
||||
flagUnknown4 |
||||
) |
||||
|
||||
// http://dev.mysql.com/doc/internals/en/status-flags.html
|
||||
type statusFlag uint16 |
||||
|
||||
const ( |
||||
statusInTrans statusFlag = 1 << iota |
||||
statusInAutocommit |
||||
statusReserved // Not in documentation
|
||||
statusMoreResultsExists |
||||
statusNoGoodIndexUsed |
||||
statusNoIndexUsed |
||||
statusCursorExists |
||||
statusLastRowSent |
||||
statusDbDropped |
||||
statusNoBackslashEscapes |
||||
statusMetadataChanged |
||||
statusQueryWasSlow |
||||
statusPsOutParams |
||||
statusInTransReadonly |
||||
statusSessionStateChanged |
||||
) |
@ -0,0 +1,176 @@ |
||||
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// Package mysql provides a MySQL driver for Go's database/sql package
|
||||
//
|
||||
// The driver should be used via the database/sql package:
|
||||
//
|
||||
// import "database/sql"
|
||||
// import _ "github.com/go-sql-driver/mysql"
|
||||
//
|
||||
// db, err := sql.Open("mysql", "user:password@/dbname")
|
||||
//
|
||||
// See https://github.com/go-sql-driver/mysql#usage for details
|
||||
package mysql |
||||
|
||||
import ( |
||||
"database/sql" |
||||
"database/sql/driver" |
||||
"net" |
||||
) |
||||
|
||||
// MySQLDriver is exported to make the driver directly accessible.
|
||||
// In general the driver is used via the database/sql package.
|
||||
type MySQLDriver struct{} |
||||
|
||||
// DialFunc is a function which can be used to establish the network connection.
|
||||
// Custom dial functions must be registered with RegisterDial
|
||||
type DialFunc func(addr string) (net.Conn, error) |
||||
|
||||
var dials map[string]DialFunc |
||||
|
||||
// RegisterDial registers a custom dial function. It can then be used by the
|
||||
// network address mynet(addr), where mynet is the registered new network.
|
||||
// addr is passed as a parameter to the dial function.
|
||||
func RegisterDial(net string, dial DialFunc) { |
||||
if dials == nil { |
||||
dials = make(map[string]DialFunc) |
||||
} |
||||
dials[net] = dial |
||||
} |
||||
|
||||
// Open new Connection.
|
||||
// See https://github.com/go-sql-driver/mysql#dsn-data-source-name for how
|
||||
// the DSN string is formated
|
||||
func (d MySQLDriver) Open(dsn string) (driver.Conn, error) { |
||||
var err error |
||||
|
||||
// New mysqlConn
|
||||
mc := &mysqlConn{ |
||||
maxAllowedPacket: maxPacketSize, |
||||
maxWriteSize: maxPacketSize - 1, |
||||
} |
||||
mc.cfg, err = ParseDSN(dsn) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
mc.parseTime = mc.cfg.ParseTime |
||||
mc.strict = mc.cfg.Strict |
||||
|
||||
// Connect to Server
|
||||
if dial, ok := dials[mc.cfg.Net]; ok { |
||||
mc.netConn, err = dial(mc.cfg.Addr) |
||||
} else { |
||||
nd := net.Dialer{Timeout: mc.cfg.Timeout} |
||||
mc.netConn, err = nd.Dial(mc.cfg.Net, mc.cfg.Addr) |
||||
} |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
// Enable TCP Keepalives on TCP connections
|
||||
if tc, ok := mc.netConn.(*net.TCPConn); ok { |
||||
if err := tc.SetKeepAlive(true); err != nil { |
||||
// Don't send COM_QUIT before handshake.
|
||||
mc.netConn.Close() |
||||
mc.netConn = nil |
||||
return nil, err |
||||
} |
||||
} |
||||
|
||||
mc.buf = newBuffer(mc.netConn) |
||||
|
||||
// Set I/O timeouts
|
||||
mc.buf.timeout = mc.cfg.ReadTimeout |
||||
mc.writeTimeout = mc.cfg.WriteTimeout |
||||
|
||||
// Reading Handshake Initialization Packet
|
||||
cipher, err := mc.readInitPacket() |
||||
if err != nil { |
||||
mc.cleanup() |
||||
return nil, err |
||||
} |
||||
|
||||
// Send Client Authentication Packet
|
||||
if err = mc.writeAuthPacket(cipher); err != nil { |
||||
mc.cleanup() |
||||
return nil, err |
||||
} |
||||
|
||||
// Handle response to auth packet, switch methods if possible
|
||||
if err = handleAuthResult(mc); err != nil { |
||||
// Authentication failed and MySQL has already closed the connection
|
||||
// (https://dev.mysql.com/doc/internals/en/authentication-fails.html).
|
||||
// Do not send COM_QUIT, just cleanup and return the error.
|
||||
mc.cleanup() |
||||
return nil, err |
||||
} |
||||
|
||||
if mc.cfg.MaxAllowedPacket > 0 { |
||||
mc.maxAllowedPacket = mc.cfg.MaxAllowedPacket |
||||
} else { |
||||
// Get max allowed packet size
|
||||
maxap, err := mc.getSystemVar("max_allowed_packet") |
||||
if err != nil { |
||||
mc.Close() |
||||
return nil, err |
||||
} |
||||
mc.maxAllowedPacket = stringToInt(maxap) - 1 |
||||
} |
||||
if mc.maxAllowedPacket < maxPacketSize { |
||||
mc.maxWriteSize = mc.maxAllowedPacket |
||||
} |
||||
|
||||
// Handle DSN Params
|
||||
err = mc.handleParams() |
||||
if err != nil { |
||||
mc.Close() |
||||
return nil, err |
||||
} |
||||
|
||||
return mc, nil |
||||
} |
||||
|
||||
func handleAuthResult(mc *mysqlConn) error { |
||||
// Read Result Packet
|
||||
cipher, err := mc.readResultOK() |
||||
if err == nil { |
||||
return nil // auth successful
|
||||
} |
||||
|
||||
if mc.cfg == nil { |
||||
return err // auth failed and retry not possible
|
||||
} |
||||
|
||||
// Retry auth if configured to do so.
|
||||
if mc.cfg.AllowOldPasswords && err == ErrOldPassword { |
||||
// Retry with old authentication method. Note: there are edge cases
|
||||
// where this should work but doesn't; this is currently "wontfix":
|
||||
// https://github.com/go-sql-driver/mysql/issues/184
|
||||
if err = mc.writeOldAuthPacket(cipher); err != nil { |
||||
return err |
||||
} |
||||
_, err = mc.readResultOK() |
||||
} else if mc.cfg.AllowCleartextPasswords && err == ErrCleartextPassword { |
||||
// Retry with clear text password for
|
||||
// http://dev.mysql.com/doc/refman/5.7/en/cleartext-authentication-plugin.html
|
||||
// http://dev.mysql.com/doc/refman/5.7/en/pam-authentication-plugin.html
|
||||
if err = mc.writeClearAuthPacket(); err != nil { |
||||
return err |
||||
} |
||||
_, err = mc.readResultOK() |
||||
} else if mc.cfg.AllowNativePasswords && err == ErrNativePassword { |
||||
if err = mc.writeNativeAuthPacket(cipher); err != nil { |
||||
return err |
||||
} |
||||
_, err = mc.readResultOK() |
||||
} |
||||
return err |
||||
} |
||||
|
||||
func init() { |
||||
sql.Register("mysql", &MySQLDriver{}) |
||||
} |
@ -0,0 +1,548 @@ |
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2016 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql |
||||
|
||||
import ( |
||||
"bytes" |
||||
"crypto/tls" |
||||
"errors" |
||||
"fmt" |
||||
"net" |
||||
"net/url" |
||||
"strconv" |
||||
"strings" |
||||
"time" |
||||
) |
||||
|
||||
var ( |
||||
errInvalidDSNUnescaped = errors.New("invalid DSN: did you forget to escape a param value?") |
||||
errInvalidDSNAddr = errors.New("invalid DSN: network address not terminated (missing closing brace)") |
||||
errInvalidDSNNoSlash = errors.New("invalid DSN: missing the slash separating the database name") |
||||
errInvalidDSNUnsafeCollation = errors.New("invalid DSN: interpolateParams can not be used with unsafe collations") |
||||
) |
||||
|
||||
// Config is a configuration parsed from a DSN string
|
||||
type Config struct { |
||||
User string // Username
|
||||
Passwd string // Password (requires User)
|
||||
Net string // Network type
|
||||
Addr string // Network address (requires Net)
|
||||
DBName string // Database name
|
||||
Params map[string]string // Connection parameters
|
||||
Collation string // Connection collation
|
||||
Loc *time.Location // Location for time.Time values
|
||||
MaxAllowedPacket int // Max packet size allowed
|
||||
TLSConfig string // TLS configuration name
|
||||
tls *tls.Config // TLS configuration
|
||||
Timeout time.Duration // Dial timeout
|
||||
ReadTimeout time.Duration // I/O read timeout
|
||||
WriteTimeout time.Duration // I/O write timeout
|
||||
|
||||
AllowAllFiles bool // Allow all files to be used with LOAD DATA LOCAL INFILE
|
||||
AllowCleartextPasswords bool // Allows the cleartext client side plugin
|
||||
AllowNativePasswords bool // Allows the native password authentication method
|
||||
AllowOldPasswords bool // Allows the old insecure password method
|
||||
ClientFoundRows bool // Return number of matching rows instead of rows changed
|
||||
ColumnsWithAlias bool // Prepend table alias to column names
|
||||
InterpolateParams bool // Interpolate placeholders into query string
|
||||
MultiStatements bool // Allow multiple statements in one query
|
||||
ParseTime bool // Parse time values to time.Time
|
||||
Strict bool // Return warnings as errors
|
||||
} |
||||
|
||||
// FormatDSN formats the given Config into a DSN string which can be passed to
|
||||
// the driver.
|
||||
func (cfg *Config) FormatDSN() string { |
||||
var buf bytes.Buffer |
||||
|
||||
// [username[:password]@]
|
||||
if len(cfg.User) > 0 { |
||||
buf.WriteString(cfg.User) |
||||
if len(cfg.Passwd) > 0 { |
||||
buf.WriteByte(':') |
||||
buf.WriteString(cfg.Passwd) |
||||
} |
||||
buf.WriteByte('@') |
||||
} |
||||
|
||||
// [protocol[(address)]]
|
||||
if len(cfg.Net) > 0 { |
||||
buf.WriteString(cfg.Net) |
||||
if len(cfg.Addr) > 0 { |
||||
buf.WriteByte('(') |
||||
buf.WriteString(cfg.Addr) |
||||
buf.WriteByte(')') |
||||
} |
||||
} |
||||
|
||||
// /dbname
|
||||
buf.WriteByte('/') |
||||
buf.WriteString(cfg.DBName) |
||||
|
||||
// [?param1=value1&...¶mN=valueN]
|
||||
hasParam := false |
||||
|
||||
if cfg.AllowAllFiles { |
||||
hasParam = true |
||||
buf.WriteString("?allowAllFiles=true") |
||||
} |
||||
|
||||
if cfg.AllowCleartextPasswords { |
||||
if hasParam { |
||||
buf.WriteString("&allowCleartextPasswords=true") |
||||
} else { |
||||
hasParam = true |
||||
buf.WriteString("?allowCleartextPasswords=true") |
||||
} |
||||
} |
||||
|
||||
if cfg.AllowNativePasswords { |
||||
if hasParam { |
||||
buf.WriteString("&allowNativePasswords=true") |
||||
} else { |
||||
hasParam = true |
||||
buf.WriteString("?allowNativePasswords=true") |
||||
} |
||||
} |
||||
|
||||
if cfg.AllowOldPasswords { |
||||
if hasParam { |
||||
buf.WriteString("&allowOldPasswords=true") |
||||
} else { |
||||
hasParam = true |
||||
buf.WriteString("?allowOldPasswords=true") |
||||
} |
||||
} |
||||
|
||||
if cfg.ClientFoundRows { |
||||
if hasParam { |
||||
buf.WriteString("&clientFoundRows=true") |
||||
} else { |
||||
hasParam = true |
||||
buf.WriteString("?clientFoundRows=true") |
||||
} |
||||
} |
||||
|
||||
if col := cfg.Collation; col != defaultCollation && len(col) > 0 { |
||||
if hasParam { |
||||
buf.WriteString("&collation=") |
||||
} else { |
||||
hasParam = true |
||||
buf.WriteString("?collation=") |
||||
} |
||||
buf.WriteString(col) |
||||
} |
||||
|
||||
if cfg.ColumnsWithAlias { |
||||
if hasParam { |
||||
buf.WriteString("&columnsWithAlias=true") |
||||
} else { |
||||
hasParam = true |
||||
buf.WriteString("?columnsWithAlias=true") |
||||
} |
||||
} |
||||
|
||||
if cfg.InterpolateParams { |
||||
if hasParam { |
||||
buf.WriteString("&interpolateParams=true") |
||||
} else { |
||||
hasParam = true |
||||
buf.WriteString("?interpolateParams=true") |
||||
} |
||||
} |
||||
|
||||
if cfg.Loc != time.UTC && cfg.Loc != nil { |
||||
if hasParam { |
||||
buf.WriteString("&loc=") |
||||
} else { |
||||
hasParam = true |
||||
buf.WriteString("?loc=") |
||||
} |
||||
buf.WriteString(url.QueryEscape(cfg.Loc.String())) |
||||
} |
||||
|
||||
if cfg.MultiStatements { |
||||
if hasParam { |
||||
buf.WriteString("&multiStatements=true") |
||||
} else { |
||||
hasParam = true |
||||
buf.WriteString("?multiStatements=true") |
||||
} |
||||
} |
||||
|
||||
if cfg.ParseTime { |
||||
if hasParam { |
||||
buf.WriteString("&parseTime=true") |
||||
} else { |
||||
hasParam = true |
||||
buf.WriteString("?parseTime=true") |
||||
} |
||||
} |
||||
|
||||
if cfg.ReadTimeout > 0 { |
||||
if hasParam { |
||||
buf.WriteString("&readTimeout=") |
||||
} else { |
||||
hasParam = true |
||||
buf.WriteString("?readTimeout=") |
||||
} |
||||
buf.WriteString(cfg.ReadTimeout.String()) |
||||
} |
||||
|
||||
if cfg.Strict { |
||||
if hasParam { |
||||
buf.WriteString("&strict=true") |
||||
} else { |
||||
hasParam = true |
||||
buf.WriteString("?strict=true") |
||||
} |
||||
} |
||||
|
||||
if cfg.Timeout > 0 { |
||||
if hasParam { |
||||
buf.WriteString("&timeout=") |
||||
} else { |
||||
hasParam = true |
||||
buf.WriteString("?timeout=") |
||||
} |
||||
buf.WriteString(cfg.Timeout.String()) |
||||
} |
||||
|
||||
if len(cfg.TLSConfig) > 0 { |
||||
if hasParam { |
||||
buf.WriteString("&tls=") |
||||
} else { |
||||
hasParam = true |
||||
buf.WriteString("?tls=") |
||||
} |
||||
buf.WriteString(url.QueryEscape(cfg.TLSConfig)) |
||||
} |
||||
|
||||
if cfg.WriteTimeout > 0 { |
||||
if hasParam { |
||||
buf.WriteString("&writeTimeout=") |
||||
} else { |
||||
hasParam = true |
||||
buf.WriteString("?writeTimeout=") |
||||
} |
||||
buf.WriteString(cfg.WriteTimeout.String()) |
||||
} |
||||
|
||||
if cfg.MaxAllowedPacket > 0 { |
||||
if hasParam { |
||||
buf.WriteString("&maxAllowedPacket=") |
||||
} else { |
||||
hasParam = true |
||||
buf.WriteString("?maxAllowedPacket=") |
||||
} |
||||
buf.WriteString(strconv.Itoa(cfg.MaxAllowedPacket)) |
||||
|
||||
} |
||||
|
||||
// other params
|
||||
if cfg.Params != nil { |
||||
for param, value := range cfg.Params { |
||||
if hasParam { |
||||
buf.WriteByte('&') |
||||
} else { |
||||
hasParam = true |
||||
buf.WriteByte('?') |
||||
} |
||||
|
||||
buf.WriteString(param) |
||||
buf.WriteByte('=') |
||||
buf.WriteString(url.QueryEscape(value)) |
||||
} |
||||
} |
||||
|
||||
return buf.String() |
||||
} |
||||
|
||||
// ParseDSN parses the DSN string to a Config
|
||||
func ParseDSN(dsn string) (cfg *Config, err error) { |
||||
// New config with some default values
|
||||
cfg = &Config{ |
||||
Loc: time.UTC, |
||||
Collation: defaultCollation, |
||||
} |
||||
|
||||
// [user[:password]@][net[(addr)]]/dbname[?param1=value1¶mN=valueN]
|
||||
// Find the last '/' (since the password or the net addr might contain a '/')
|
||||
foundSlash := false |
||||
for i := len(dsn) - 1; i >= 0; i-- { |
||||
if dsn[i] == '/' { |
||||
foundSlash = true |
||||
var j, k int |
||||
|
||||
// left part is empty if i <= 0
|
||||
if i > 0 { |
||||
// [username[:password]@][protocol[(address)]]
|
||||
// Find the last '@' in dsn[:i]
|
||||
for j = i; j >= 0; j-- { |
||||
if dsn[j] == '@' { |
||||
// username[:password]
|
||||
// Find the first ':' in dsn[:j]
|
||||
for k = 0; k < j; k++ { |
||||
if dsn[k] == ':' { |
||||
cfg.Passwd = dsn[k+1 : j] |
||||
break |
||||
} |
||||
} |
||||
cfg.User = dsn[:k] |
||||
|
||||
break |
||||
} |
||||
} |
||||
|
||||
// [protocol[(address)]]
|
||||
// Find the first '(' in dsn[j+1:i]
|
||||
for k = j + 1; k < i; k++ { |
||||
if dsn[k] == '(' { |
||||
// dsn[i-1] must be == ')' if an address is specified
|
||||
if dsn[i-1] != ')' { |
||||
if strings.ContainsRune(dsn[k+1:i], ')') { |
||||
return nil, errInvalidDSNUnescaped |
||||
} |
||||
return nil, errInvalidDSNAddr |
||||
} |
||||
cfg.Addr = dsn[k+1 : i-1] |
||||
break |
||||
} |
||||
} |
||||
cfg.Net = dsn[j+1 : k] |
||||
} |
||||
|
||||
// dbname[?param1=value1&...¶mN=valueN]
|
||||
// Find the first '?' in dsn[i+1:]
|
||||
for j = i + 1; j < len(dsn); j++ { |
||||
if dsn[j] == '?' { |
||||
if err = parseDSNParams(cfg, dsn[j+1:]); err != nil { |
||||
return |
||||
} |
||||
break |
||||
} |
||||
} |
||||
cfg.DBName = dsn[i+1 : j] |
||||
|
||||
break |
||||
} |
||||
} |
||||
|
||||
if !foundSlash && len(dsn) > 0 { |
||||
return nil, errInvalidDSNNoSlash |
||||
} |
||||
|
||||
if cfg.InterpolateParams && unsafeCollations[cfg.Collation] { |
||||
return nil, errInvalidDSNUnsafeCollation |
||||
} |
||||
|
||||
// Set default network if empty
|
||||
if cfg.Net == "" { |
||||
cfg.Net = "tcp" |
||||
} |
||||
|
||||
// Set default address if empty
|
||||
if cfg.Addr == "" { |
||||
switch cfg.Net { |
||||
case "tcp": |
||||
cfg.Addr = "127.0.0.1:3306" |
||||
case "unix": |
||||
cfg.Addr = "/tmp/mysql.sock" |
||||
default: |
||||
return nil, errors.New("default addr for network '" + cfg.Net + "' unknown") |
||||
} |
||||
|
||||
} |
||||
|
||||
return |
||||
} |
||||
|
||||
// parseDSNParams parses the DSN "query string"
|
||||
// Values must be url.QueryEscape'ed
|
||||
func parseDSNParams(cfg *Config, params string) (err error) { |
||||
for _, v := range strings.Split(params, "&") { |
||||
param := strings.SplitN(v, "=", 2) |
||||
if len(param) != 2 { |
||||
continue |
||||
} |
||||
|
||||
// cfg params
|
||||
switch value := param[1]; param[0] { |
||||
|
||||
// Disable INFILE whitelist / enable all files
|
||||
case "allowAllFiles": |
||||
var isBool bool |
||||
cfg.AllowAllFiles, isBool = readBool(value) |
||||
if !isBool { |
||||
return errors.New("invalid bool value: " + value) |
||||
} |
||||
|
||||
// Use cleartext authentication mode (MySQL 5.5.10+)
|
||||
case "allowCleartextPasswords": |
||||
var isBool bool |
||||
cfg.AllowCleartextPasswords, isBool = readBool(value) |
||||
if !isBool { |
||||
return errors.New("invalid bool value: " + value) |
||||
} |
||||
|
||||
// Use native password authentication
|
||||
case "allowNativePasswords": |
||||
var isBool bool |
||||
cfg.AllowNativePasswords, isBool = readBool(value) |
||||
if !isBool { |
||||
return errors.New("invalid bool value: " + value) |
||||
} |
||||
|
||||
// Use old authentication mode (pre MySQL 4.1)
|
||||
case "allowOldPasswords": |
||||
var isBool bool |
||||
cfg.AllowOldPasswords, isBool = readBool(value) |
||||
if !isBool { |
||||
return errors.New("invalid bool value: " + value) |
||||
} |
||||
|
||||
// Switch "rowsAffected" mode
|
||||
case "clientFoundRows": |
||||
var isBool bool |
||||
cfg.ClientFoundRows, isBool = readBool(value) |
||||
if !isBool { |
||||
return errors.New("invalid bool value: " + value) |
||||
} |
||||
|
||||
// Collation
|
||||
case "collation": |
||||
cfg.Collation = value |
||||
break |
||||
|
||||
case "columnsWithAlias": |
||||
var isBool bool |
||||
cfg.ColumnsWithAlias, isBool = readBool(value) |
||||
if !isBool { |
||||
return errors.New("invalid bool value: " + value) |
||||
} |
||||
|
||||
// Compression
|
||||
case "compress": |
||||
return errors.New("compression not implemented yet") |
||||
|
||||
// Enable client side placeholder substitution
|
||||
case "interpolateParams": |
||||
var isBool bool |
||||
cfg.InterpolateParams, isBool = readBool(value) |
||||
if !isBool { |
||||
return errors.New("invalid bool value: " + value) |
||||
} |
||||
|
||||
// Time Location
|
||||
case "loc": |
||||
if value, err = url.QueryUnescape(value); err != nil { |
||||
return |
||||
} |
||||
cfg.Loc, err = time.LoadLocation(value) |
||||
if err != nil { |
||||
return |
||||
} |
||||
|
||||
// multiple statements in one query
|
||||
case "multiStatements": |
||||
var isBool bool |
||||
cfg.MultiStatements, isBool = readBool(value) |
||||
if !isBool { |
||||
return errors.New("invalid bool value: " + value) |
||||
} |
||||
|
||||
// time.Time parsing
|
||||
case "parseTime": |
||||
var isBool bool |
||||
cfg.ParseTime, isBool = readBool(value) |
||||
if !isBool { |
||||
return errors.New("invalid bool value: " + value) |
||||
} |
||||
|
||||
// I/O read Timeout
|
||||
case "readTimeout": |
||||
cfg.ReadTimeout, err = time.ParseDuration(value) |
||||
if err != nil { |
||||
return |
||||
} |
||||
|
||||
// Strict mode
|
||||
case "strict": |
||||
var isBool bool |
||||
cfg.Strict, isBool = readBool(value) |
||||
if !isBool { |
||||
return errors.New("invalid bool value: " + value) |
||||
} |
||||
|
||||
// Dial Timeout
|
||||
case "timeout": |
||||
cfg.Timeout, err = time.ParseDuration(value) |
||||
if err != nil { |
||||
return |
||||
} |
||||
|
||||
// TLS-Encryption
|
||||
case "tls": |
||||
boolValue, isBool := readBool(value) |
||||
if isBool { |
||||
if boolValue { |
||||
cfg.TLSConfig = "true" |
||||
cfg.tls = &tls.Config{} |
||||
} else { |
||||
cfg.TLSConfig = "false" |
||||
} |
||||
} else if vl := strings.ToLower(value); vl == "skip-verify" { |
||||
cfg.TLSConfig = vl |
||||
cfg.tls = &tls.Config{InsecureSkipVerify: true} |
||||
} else { |
||||
name, err := url.QueryUnescape(value) |
||||
if err != nil { |
||||
return fmt.Errorf("invalid value for TLS config name: %v", err) |
||||
} |
||||
|
||||
if tlsConfig, ok := tlsConfigRegister[name]; ok { |
||||
if len(tlsConfig.ServerName) == 0 && !tlsConfig.InsecureSkipVerify { |
||||
host, _, err := net.SplitHostPort(cfg.Addr) |
||||
if err == nil { |
||||
tlsConfig.ServerName = host |
||||
} |
||||
} |
||||
|
||||
cfg.TLSConfig = name |
||||
cfg.tls = tlsConfig |
||||
} else { |
||||
return errors.New("invalid value / unknown config name: " + name) |
||||
} |
||||
} |
||||
|
||||
// I/O write Timeout
|
||||
case "writeTimeout": |
||||
cfg.WriteTimeout, err = time.ParseDuration(value) |
||||
if err != nil { |
||||
return |
||||
} |
||||
case "maxAllowedPacket": |
||||
cfg.MaxAllowedPacket, err = strconv.Atoi(value) |
||||
if err != nil { |
||||
return |
||||
} |
||||
default: |
||||
// lazy init
|
||||
if cfg.Params == nil { |
||||
cfg.Params = make(map[string]string) |
||||
} |
||||
|
||||
if cfg.Params[param[0]], err = url.QueryUnescape(value); err != nil { |
||||
return |
||||
} |
||||
} |
||||
} |
||||
|
||||
return |
||||
} |
@ -0,0 +1,132 @@ |
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql |
||||
|
||||
import ( |
||||
"database/sql/driver" |
||||
"errors" |
||||
"fmt" |
||||
"io" |
||||
"log" |
||||
"os" |
||||
) |
||||
|
||||
// Various errors the driver might return. Can change between driver versions.
|
||||
var ( |
||||
ErrInvalidConn = errors.New("invalid connection") |
||||
ErrMalformPkt = errors.New("malformed packet") |
||||
ErrNoTLS = errors.New("TLS requested but server does not support TLS") |
||||
ErrCleartextPassword = errors.New("this user requires clear text authentication. If you still want to use it, please add 'allowCleartextPasswords=1' to your DSN") |
||||
ErrNativePassword = errors.New("this user requires mysql native password authentication.") |
||||
ErrOldPassword = errors.New("this user requires old password authentication. If you still want to use it, please add 'allowOldPasswords=1' to your DSN. See also https://github.com/go-sql-driver/mysql/wiki/old_passwords") |
||||
ErrUnknownPlugin = errors.New("this authentication plugin is not supported") |
||||
ErrOldProtocol = errors.New("MySQL server does not support required protocol 41+") |
||||
ErrPktSync = errors.New("commands out of sync. You can't run this command now") |
||||
ErrPktSyncMul = errors.New("commands out of sync. Did you run multiple statements at once?") |
||||
ErrPktTooLarge = errors.New("packet for query is too large. Try adjusting the 'max_allowed_packet' variable on the server") |
||||
ErrBusyBuffer = errors.New("busy buffer") |
||||
) |
||||
|
||||
var errLog = Logger(log.New(os.Stderr, "[mysql] ", log.Ldate|log.Ltime|log.Lshortfile)) |
||||
|
||||
// Logger is used to log critical error messages.
|
||||
type Logger interface { |
||||
Print(v ...interface{}) |
||||
} |
||||
|
||||
// SetLogger is used to set the logger for critical errors.
|
||||
// The initial logger is os.Stderr.
|
||||
func SetLogger(logger Logger) error { |
||||
if logger == nil { |
||||
return errors.New("logger is nil") |
||||
} |
||||
errLog = logger |
||||
return nil |
||||
} |
||||
|
||||
// MySQLError is an error type which represents a single MySQL error
|
||||
type MySQLError struct { |
||||
Number uint16 |
||||
Message string |
||||
} |
||||
|
||||
func (me *MySQLError) Error() string { |
||||
return fmt.Sprintf("Error %d: %s", me.Number, me.Message) |
||||
} |
||||
|
||||
// MySQLWarnings is an error type which represents a group of one or more MySQL
|
||||
// warnings
|
||||
type MySQLWarnings []MySQLWarning |
||||
|
||||
func (mws MySQLWarnings) Error() string { |
||||
var msg string |
||||
for i, warning := range mws { |
||||
if i > 0 { |
||||
msg += "\r\n" |
||||
} |
||||
msg += fmt.Sprintf( |
||||
"%s %s: %s", |
||||
warning.Level, |
||||
warning.Code, |
||||
warning.Message, |
||||
) |
||||
} |
||||
return msg |
||||
} |
||||
|
||||
// MySQLWarning is an error type which represents a single MySQL warning.
|
||||
// Warnings are returned in groups only. See MySQLWarnings
|
||||
type MySQLWarning struct { |
||||
Level string |
||||
Code string |
||||
Message string |
||||
} |
||||
|
||||
func (mc *mysqlConn) getWarnings() (err error) { |
||||
rows, err := mc.Query("SHOW WARNINGS", nil) |
||||
if err != nil { |
||||
return |
||||
} |
||||
|
||||
var warnings = MySQLWarnings{} |
||||
var values = make([]driver.Value, 3) |
||||
|
||||
for { |
||||
err = rows.Next(values) |
||||
switch err { |
||||
case nil: |
||||
warning := MySQLWarning{} |
||||
|
||||
if raw, ok := values[0].([]byte); ok { |
||||
warning.Level = string(raw) |
||||
} else { |
||||
warning.Level = fmt.Sprintf("%s", values[0]) |
||||
} |
||||
if raw, ok := values[1].([]byte); ok { |
||||
warning.Code = string(raw) |
||||
} else { |
||||
warning.Code = fmt.Sprintf("%s", values[1]) |
||||
} |
||||
if raw, ok := values[2].([]byte); ok { |
||||
warning.Message = string(raw) |
||||
} else { |
||||
warning.Message = fmt.Sprintf("%s", values[0]) |
||||
} |
||||
|
||||
warnings = append(warnings, warning) |
||||
|
||||
case io.EOF: |
||||
return warnings |
||||
|
||||
default: |
||||
rows.Close() |
||||
return |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,182 @@ |
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql |
||||
|
||||
import ( |
||||
"fmt" |
||||
"io" |
||||
"os" |
||||
"strings" |
||||
"sync" |
||||
) |
||||
|
||||
var ( |
||||
fileRegister map[string]bool |
||||
fileRegisterLock sync.RWMutex |
||||
readerRegister map[string]func() io.Reader |
||||
readerRegisterLock sync.RWMutex |
||||
) |
||||
|
||||
// RegisterLocalFile adds the given file to the file whitelist,
|
||||
// so that it can be used by "LOAD DATA LOCAL INFILE <filepath>".
|
||||
// Alternatively you can allow the use of all local files with
|
||||
// the DSN parameter 'allowAllFiles=true'
|
||||
//
|
||||
// filePath := "/home/gopher/data.csv"
|
||||
// mysql.RegisterLocalFile(filePath)
|
||||
// err := db.Exec("LOAD DATA LOCAL INFILE '" + filePath + "' INTO TABLE foo")
|
||||
// if err != nil {
|
||||
// ...
|
||||
//
|
||||
func RegisterLocalFile(filePath string) { |
||||
fileRegisterLock.Lock() |
||||
// lazy map init
|
||||
if fileRegister == nil { |
||||
fileRegister = make(map[string]bool) |
||||
} |
||||
|
||||
fileRegister[strings.Trim(filePath, `"`)] = true |
||||
fileRegisterLock.Unlock() |
||||
} |
||||
|
||||
// DeregisterLocalFile removes the given filepath from the whitelist.
|
||||
func DeregisterLocalFile(filePath string) { |
||||
fileRegisterLock.Lock() |
||||
delete(fileRegister, strings.Trim(filePath, `"`)) |
||||
fileRegisterLock.Unlock() |
||||
} |
||||
|
||||
// RegisterReaderHandler registers a handler function which is used
|
||||
// to receive a io.Reader.
|
||||
// The Reader can be used by "LOAD DATA LOCAL INFILE Reader::<name>".
|
||||
// If the handler returns a io.ReadCloser Close() is called when the
|
||||
// request is finished.
|
||||
//
|
||||
// mysql.RegisterReaderHandler("data", func() io.Reader {
|
||||
// var csvReader io.Reader // Some Reader that returns CSV data
|
||||
// ... // Open Reader here
|
||||
// return csvReader
|
||||
// })
|
||||
// err := db.Exec("LOAD DATA LOCAL INFILE 'Reader::data' INTO TABLE foo")
|
||||
// if err != nil {
|
||||
// ...
|
||||
//
|
||||
func RegisterReaderHandler(name string, handler func() io.Reader) { |
||||
readerRegisterLock.Lock() |
||||
// lazy map init
|
||||
if readerRegister == nil { |
||||
readerRegister = make(map[string]func() io.Reader) |
||||
} |
||||
|
||||
readerRegister[name] = handler |
||||
readerRegisterLock.Unlock() |
||||
} |
||||
|
||||
// DeregisterReaderHandler removes the ReaderHandler function with
|
||||
// the given name from the registry.
|
||||
func DeregisterReaderHandler(name string) { |
||||
readerRegisterLock.Lock() |
||||
delete(readerRegister, name) |
||||
readerRegisterLock.Unlock() |
||||
} |
||||
|
||||
func deferredClose(err *error, closer io.Closer) { |
||||
closeErr := closer.Close() |
||||
if *err == nil { |
||||
*err = closeErr |
||||
} |
||||
} |
||||
|
||||
func (mc *mysqlConn) handleInFileRequest(name string) (err error) { |
||||
var rdr io.Reader |
||||
var data []byte |
||||
packetSize := 16 * 1024 // 16KB is small enough for disk readahead and large enough for TCP
|
||||
if mc.maxWriteSize < packetSize { |
||||
packetSize = mc.maxWriteSize |
||||
} |
||||
|
||||
if idx := strings.Index(name, "Reader::"); idx == 0 || (idx > 0 && name[idx-1] == '/') { // io.Reader
|
||||
// The server might return an an absolute path. See issue #355.
|
||||
name = name[idx+8:] |
||||
|
||||
readerRegisterLock.RLock() |
||||
handler, inMap := readerRegister[name] |
||||
readerRegisterLock.RUnlock() |
||||
|
||||
if inMap { |
||||
rdr = handler() |
||||
if rdr != nil { |
||||
if cl, ok := rdr.(io.Closer); ok { |
||||
defer deferredClose(&err, cl) |
||||
} |
||||
} else { |
||||
err = fmt.Errorf("Reader '%s' is <nil>", name) |
||||
} |
||||
} else { |
||||
err = fmt.Errorf("Reader '%s' is not registered", name) |
||||
} |
||||
} else { // File
|
||||
name = strings.Trim(name, `"`) |
||||
fileRegisterLock.RLock() |
||||
fr := fileRegister[name] |
||||
fileRegisterLock.RUnlock() |
||||
if mc.cfg.AllowAllFiles || fr { |
||||
var file *os.File |
||||
var fi os.FileInfo |
||||
|
||||
if file, err = os.Open(name); err == nil { |
||||
defer deferredClose(&err, file) |
||||
|
||||
// get file size
|
||||
if fi, err = file.Stat(); err == nil { |
||||
rdr = file |
||||
if fileSize := int(fi.Size()); fileSize < packetSize { |
||||
packetSize = fileSize |
||||
} |
||||
} |
||||
} |
||||
} else { |
||||
err = fmt.Errorf("local file '%s' is not registered", name) |
||||
} |
||||
} |
||||
|
||||
// send content packets
|
||||
if err == nil { |
||||
data := make([]byte, 4+packetSize) |
||||
var n int |
||||
for err == nil { |
||||
n, err = rdr.Read(data[4:]) |
||||
if n > 0 { |
||||
if ioErr := mc.writePacket(data[:4+n]); ioErr != nil { |
||||
return ioErr |
||||
} |
||||
} |
||||
} |
||||
if err == io.EOF { |
||||
err = nil |
||||
} |
||||
} |
||||
|
||||
// send empty packet (termination)
|
||||
if data == nil { |
||||
data = make([]byte, 4) |
||||
} |
||||
if ioErr := mc.writePacket(data[:4]); ioErr != nil { |
||||
return ioErr |
||||
} |
||||
|
||||
// read OK packet
|
||||
if err == nil { |
||||
_, err = mc.readResultOK() |
||||
return err |
||||
} |
||||
|
||||
mc.readPacket() |
||||
return err |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,22 @@ |
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql |
||||
|
||||
type mysqlResult struct { |
||||
affectedRows int64 |
||||
insertId int64 |
||||
} |
||||
|
||||
func (res *mysqlResult) LastInsertId() (int64, error) { |
||||
return res.insertId, nil |
||||
} |
||||
|
||||
func (res *mysqlResult) RowsAffected() (int64, error) { |
||||
return res.affectedRows, nil |
||||
} |
@ -0,0 +1,112 @@ |
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql |
||||
|
||||
import ( |
||||
"database/sql/driver" |
||||
"io" |
||||
) |
||||
|
||||
type mysqlField struct { |
||||
tableName string |
||||
name string |
||||
flags fieldFlag |
||||
fieldType byte |
||||
decimals byte |
||||
} |
||||
|
||||
type mysqlRows struct { |
||||
mc *mysqlConn |
||||
columns []mysqlField |
||||
} |
||||
|
||||
type binaryRows struct { |
||||
mysqlRows |
||||
} |
||||
|
||||
type textRows struct { |
||||
mysqlRows |
||||
} |
||||
|
||||
type emptyRows struct{} |
||||
|
||||
func (rows *mysqlRows) Columns() []string { |
||||
columns := make([]string, len(rows.columns)) |
||||
if rows.mc != nil && rows.mc.cfg.ColumnsWithAlias { |
||||
for i := range columns { |
||||
if tableName := rows.columns[i].tableName; len(tableName) > 0 { |
||||
columns[i] = tableName + "." + rows.columns[i].name |
||||
} else { |
||||
columns[i] = rows.columns[i].name |
||||
} |
||||
} |
||||
} else { |
||||
for i := range columns { |
||||
columns[i] = rows.columns[i].name |
||||
} |
||||
} |
||||
return columns |
||||
} |
||||
|
||||
func (rows *mysqlRows) Close() error { |
||||
mc := rows.mc |
||||
if mc == nil { |
||||
return nil |
||||
} |
||||
if mc.netConn == nil { |
||||
return ErrInvalidConn |
||||
} |
||||
|
||||
// Remove unread packets from stream
|
||||
err := mc.readUntilEOF() |
||||
if err == nil { |
||||
if err = mc.discardResults(); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
|
||||
rows.mc = nil |
||||
return err |
||||
} |
||||
|
||||
func (rows *binaryRows) Next(dest []driver.Value) error { |
||||
if mc := rows.mc; mc != nil { |
||||
if mc.netConn == nil { |
||||
return ErrInvalidConn |
||||
} |
||||
|
||||
// Fetch next row from stream
|
||||
return rows.readRow(dest) |
||||
} |
||||
return io.EOF |
||||
} |
||||
|
||||
func (rows *textRows) Next(dest []driver.Value) error { |
||||
if mc := rows.mc; mc != nil { |
||||
if mc.netConn == nil { |
||||
return ErrInvalidConn |
||||
} |
||||
|
||||
// Fetch next row from stream
|
||||
return rows.readRow(dest) |
||||
} |
||||
return io.EOF |
||||
} |
||||
|
||||
func (rows emptyRows) Columns() []string { |
||||
return nil |
||||
} |
||||
|
||||
func (rows emptyRows) Close() error { |
||||
return nil |
||||
} |
||||
|
||||
func (rows emptyRows) Next(dest []driver.Value) error { |
||||
return io.EOF |
||||
} |
@ -0,0 +1,150 @@ |
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql |
||||
|
||||
import ( |
||||
"database/sql/driver" |
||||
"fmt" |
||||
"reflect" |
||||
"strconv" |
||||
) |
||||
|
||||
type mysqlStmt struct { |
||||
mc *mysqlConn |
||||
id uint32 |
||||
paramCount int |
||||
columns []mysqlField // cached from the first query
|
||||
} |
||||
|
||||
func (stmt *mysqlStmt) Close() error { |
||||
if stmt.mc == nil || stmt.mc.netConn == nil { |
||||
errLog.Print(ErrInvalidConn) |
||||
return driver.ErrBadConn |
||||
} |
||||
|
||||
err := stmt.mc.writeCommandPacketUint32(comStmtClose, stmt.id) |
||||
stmt.mc = nil |
||||
return err |
||||
} |
||||
|
||||
func (stmt *mysqlStmt) NumInput() int { |
||||
return stmt.paramCount |
||||
} |
||||
|
||||
func (stmt *mysqlStmt) ColumnConverter(idx int) driver.ValueConverter { |
||||
return converter{} |
||||
} |
||||
|
||||
func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) { |
||||
if stmt.mc.netConn == nil { |
||||
errLog.Print(ErrInvalidConn) |
||||
return nil, driver.ErrBadConn |
||||
} |
||||
// Send command
|
||||
err := stmt.writeExecutePacket(args) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
mc := stmt.mc |
||||
|
||||
mc.affectedRows = 0 |
||||
mc.insertId = 0 |
||||
|
||||
// Read Result
|
||||
resLen, err := mc.readResultSetHeaderPacket() |
||||
if err == nil { |
||||
if resLen > 0 { |
||||
// Columns
|
||||
err = mc.readUntilEOF() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
// Rows
|
||||
err = mc.readUntilEOF() |
||||
} |
||||
if err == nil { |
||||
return &mysqlResult{ |
||||
affectedRows: int64(mc.affectedRows), |
||||
insertId: int64(mc.insertId), |
||||
}, nil |
||||
} |
||||
} |
||||
|
||||
return nil, err |
||||
} |
||||
|
||||
func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) { |
||||
if stmt.mc.netConn == nil { |
||||
errLog.Print(ErrInvalidConn) |
||||
return nil, driver.ErrBadConn |
||||
} |
||||
// Send command
|
||||
err := stmt.writeExecutePacket(args) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
mc := stmt.mc |
||||
|
||||
// Read Result
|
||||
resLen, err := mc.readResultSetHeaderPacket() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
rows := new(binaryRows) |
||||
|
||||
if resLen > 0 { |
||||
rows.mc = mc |
||||
// Columns
|
||||
// If not cached, read them and cache them
|
||||
if stmt.columns == nil { |
||||
rows.columns, err = mc.readColumns(resLen) |
||||
stmt.columns = rows.columns |
||||
} else { |
||||
rows.columns = stmt.columns |
||||
err = mc.readUntilEOF() |
||||
} |
||||
} |
||||
|
||||
return rows, err |
||||
} |
||||
|
||||
type converter struct{} |
||||
|
||||
func (c converter) ConvertValue(v interface{}) (driver.Value, error) { |
||||
if driver.IsValue(v) { |
||||
return v, nil |
||||
} |
||||
|
||||
rv := reflect.ValueOf(v) |
||||
switch rv.Kind() { |
||||
case reflect.Ptr: |
||||
// indirect pointers
|
||||
if rv.IsNil() { |
||||
return nil, nil |
||||
} |
||||
return c.ConvertValue(rv.Elem().Interface()) |
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
||||
return rv.Int(), nil |
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32: |
||||
return int64(rv.Uint()), nil |
||||
case reflect.Uint64: |
||||
u64 := rv.Uint() |
||||
if u64 >= 1<<63 { |
||||
return strconv.FormatUint(u64, 10), nil |
||||
} |
||||
return int64(u64), nil |
||||
case reflect.Float32, reflect.Float64: |
||||
return rv.Float(), nil |
||||
} |
||||
return nil, fmt.Errorf("unsupported type %T, a %s", v, rv.Kind()) |
||||
} |
@ -0,0 +1,31 @@ |
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql |
||||
|
||||
type mysqlTx struct { |
||||
mc *mysqlConn |
||||
} |
||||
|
||||
func (tx *mysqlTx) Commit() (err error) { |
||||
if tx.mc == nil || tx.mc.netConn == nil { |
||||
return ErrInvalidConn |
||||
} |
||||
err = tx.mc.exec("COMMIT") |
||||
tx.mc = nil |
||||
return |
||||
} |
||||
|
||||
func (tx *mysqlTx) Rollback() (err error) { |
||||
if tx.mc == nil || tx.mc.netConn == nil { |
||||
return ErrInvalidConn |
||||
} |
||||
err = tx.mc.exec("ROLLBACK") |
||||
tx.mc = nil |
||||
return |
||||
} |
@ -0,0 +1,740 @@ |
||||
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||
//
|
||||
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package mysql |
||||
|
||||
import ( |
||||
"crypto/sha1" |
||||
"crypto/tls" |
||||
"database/sql/driver" |
||||
"encoding/binary" |
||||
"fmt" |
||||
"io" |
||||
"strings" |
||||
"time" |
||||
) |
||||
|
||||
var ( |
||||
tlsConfigRegister map[string]*tls.Config // Register for custom tls.Configs
|
||||
) |
||||
|
||||
// RegisterTLSConfig registers a custom tls.Config to be used with sql.Open.
|
||||
// Use the key as a value in the DSN where tls=value.
|
||||
//
|
||||
// rootCertPool := x509.NewCertPool()
|
||||
// pem, err := ioutil.ReadFile("/path/ca-cert.pem")
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
// if ok := rootCertPool.AppendCertsFromPEM(pem); !ok {
|
||||
// log.Fatal("Failed to append PEM.")
|
||||
// }
|
||||
// clientCert := make([]tls.Certificate, 0, 1)
|
||||
// certs, err := tls.LoadX509KeyPair("/path/client-cert.pem", "/path/client-key.pem")
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
// clientCert = append(clientCert, certs)
|
||||
// mysql.RegisterTLSConfig("custom", &tls.Config{
|
||||
// RootCAs: rootCertPool,
|
||||
// Certificates: clientCert,
|
||||
// })
|
||||
// db, err := sql.Open("mysql", "user@tcp(localhost:3306)/test?tls=custom")
|
||||
//
|
||||
func RegisterTLSConfig(key string, config *tls.Config) error { |
||||
if _, isBool := readBool(key); isBool || strings.ToLower(key) == "skip-verify" { |
||||
return fmt.Errorf("key '%s' is reserved", key) |
||||
} |
||||
|
||||
if tlsConfigRegister == nil { |
||||
tlsConfigRegister = make(map[string]*tls.Config) |
||||
} |
||||
|
||||
tlsConfigRegister[key] = config |
||||
return nil |
||||
} |
||||
|
||||
// DeregisterTLSConfig removes the tls.Config associated with key.
|
||||
func DeregisterTLSConfig(key string) { |
||||
if tlsConfigRegister != nil { |
||||
delete(tlsConfigRegister, key) |
||||
} |
||||
} |
||||
|
||||
// Returns the bool value of the input.
|
||||
// The 2nd return value indicates if the input was a valid bool value
|
||||
func readBool(input string) (value bool, valid bool) { |
||||
switch input { |
||||
case "1", "true", "TRUE", "True": |
||||
return true, true |
||||
case "0", "false", "FALSE", "False": |
||||
return false, true |
||||
} |
||||
|
||||
// Not a valid bool value
|
||||
return |
||||
} |
||||
|
||||
/****************************************************************************** |
||||
* Authentication * |
||||
******************************************************************************/ |
||||
|
||||
// Encrypt password using 4.1+ method
|
||||
func scramblePassword(scramble, password []byte) []byte { |
||||
if len(password) == 0 { |
||||
return nil |
||||
} |
||||
|
||||
// stage1Hash = SHA1(password)
|
||||
crypt := sha1.New() |
||||
crypt.Write(password) |
||||
stage1 := crypt.Sum(nil) |
||||
|
||||
// scrambleHash = SHA1(scramble + SHA1(stage1Hash))
|
||||
// inner Hash
|
||||
crypt.Reset() |
||||
crypt.Write(stage1) |
||||
hash := crypt.Sum(nil) |
||||
|
||||
// outer Hash
|
||||
crypt.Reset() |
||||
crypt.Write(scramble) |
||||
crypt.Write(hash) |
||||
scramble = crypt.Sum(nil) |
||||
|
||||
// token = scrambleHash XOR stage1Hash
|
||||
for i := range scramble { |
||||
scramble[i] ^= stage1[i] |
||||
} |
||||
return scramble |
||||
} |
||||
|
||||
// Encrypt password using pre 4.1 (old password) method
|
||||
// https://github.com/atcurtis/mariadb/blob/master/mysys/my_rnd.c
|
||||
type myRnd struct { |
||||
seed1, seed2 uint32 |
||||
} |
||||
|
||||
const myRndMaxVal = 0x3FFFFFFF |
||||
|
||||
// Pseudo random number generator
|
||||
func newMyRnd(seed1, seed2 uint32) *myRnd { |
||||
return &myRnd{ |
||||
seed1: seed1 % myRndMaxVal, |
||||
seed2: seed2 % myRndMaxVal, |
||||
} |
||||
} |
||||
|
||||
// Tested to be equivalent to MariaDB's floating point variant
|
||||
// http://play.golang.org/p/QHvhd4qved
|
||||
// http://play.golang.org/p/RG0q4ElWDx
|
||||
func (r *myRnd) NextByte() byte { |
||||
r.seed1 = (r.seed1*3 + r.seed2) % myRndMaxVal |
||||
r.seed2 = (r.seed1 + r.seed2 + 33) % myRndMaxVal |
||||
|
||||
return byte(uint64(r.seed1) * 31 / myRndMaxVal) |
||||
} |
||||
|
||||
// Generate binary hash from byte string using insecure pre 4.1 method
|
||||
func pwHash(password []byte) (result [2]uint32) { |
||||
var add uint32 = 7 |
||||
var tmp uint32 |
||||
|
||||
result[0] = 1345345333 |
||||
result[1] = 0x12345671 |
||||
|
||||
for _, c := range password { |
||||
// skip spaces and tabs in password
|
||||
if c == ' ' || c == '\t' { |
||||
continue |
||||
} |
||||
|
||||
tmp = uint32(c) |
||||
result[0] ^= (((result[0] & 63) + add) * tmp) + (result[0] << 8) |
||||
result[1] += (result[1] << 8) ^ result[0] |
||||
add += tmp |
||||
} |
||||
|
||||
// Remove sign bit (1<<31)-1)
|
||||
result[0] &= 0x7FFFFFFF |
||||
result[1] &= 0x7FFFFFFF |
||||
|
||||
return |
||||
} |
||||
|
||||
// Encrypt password using insecure pre 4.1 method
|
||||
func scrambleOldPassword(scramble, password []byte) []byte { |
||||
if len(password) == 0 { |
||||
return nil |
||||
} |
||||
|
||||
scramble = scramble[:8] |
||||
|
||||
hashPw := pwHash(password) |
||||
hashSc := pwHash(scramble) |
||||
|
||||
r := newMyRnd(hashPw[0]^hashSc[0], hashPw[1]^hashSc[1]) |
||||
|
||||
var out [8]byte |
||||
for i := range out { |
||||
out[i] = r.NextByte() + 64 |
||||
} |
||||
|
||||
mask := r.NextByte() |
||||
for i := range out { |
||||
out[i] ^= mask |
||||
} |
||||
|
||||
return out[:] |
||||
} |
||||
|
||||
/****************************************************************************** |
||||
* Time related utils * |
||||
******************************************************************************/ |
||||
|
||||
// NullTime represents a time.Time that may be NULL.
|
||||
// NullTime implements the Scanner interface so
|
||||
// it can be used as a scan destination:
|
||||
//
|
||||
// var nt NullTime
|
||||
// err := db.QueryRow("SELECT time FROM foo WHERE id=?", id).Scan(&nt)
|
||||
// ...
|
||||
// if nt.Valid {
|
||||
// // use nt.Time
|
||||
// } else {
|
||||
// // NULL value
|
||||
// }
|
||||
//
|
||||
// This NullTime implementation is not driver-specific
|
||||
type NullTime struct { |
||||
Time time.Time |
||||
Valid bool // Valid is true if Time is not NULL
|
||||
} |
||||
|
||||
// Scan implements the Scanner interface.
|
||||
// The value type must be time.Time or string / []byte (formatted time-string),
|
||||
// otherwise Scan fails.
|
||||
func (nt *NullTime) Scan(value interface{}) (err error) { |
||||
if value == nil { |
||||
nt.Time, nt.Valid = time.Time{}, false |
||||
return |
||||
} |
||||
|
||||
switch v := value.(type) { |
||||
case time.Time: |
||||
nt.Time, nt.Valid = v, true |
||||
return |
||||
case []byte: |
||||
nt.Time, err = parseDateTime(string(v), time.UTC) |
||||
nt.Valid = (err == nil) |
||||
return |
||||
case string: |
||||
nt.Time, err = parseDateTime(v, time.UTC) |
||||
nt.Valid = (err == nil) |
||||
return |
||||
} |
||||
|
||||
nt.Valid = false |
||||
return fmt.Errorf("Can't convert %T to time.Time", value) |
||||
} |
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (nt NullTime) Value() (driver.Value, error) { |
||||
if !nt.Valid { |
||||
return nil, nil |
||||
} |
||||
return nt.Time, nil |
||||
} |
||||
|
||||
func parseDateTime(str string, loc *time.Location) (t time.Time, err error) { |
||||
base := "0000-00-00 00:00:00.0000000" |
||||
switch len(str) { |
||||
case 10, 19, 21, 22, 23, 24, 25, 26: // up to "YYYY-MM-DD HH:MM:SS.MMMMMM"
|
||||
if str == base[:len(str)] { |
||||
return |
||||
} |
||||
t, err = time.Parse(timeFormat[:len(str)], str) |
||||
default: |
||||
err = fmt.Errorf("invalid time string: %s", str) |
||||
return |
||||
} |
||||
|
||||
// Adjust location
|
||||
if err == nil && loc != time.UTC { |
||||
y, mo, d := t.Date() |
||||
h, mi, s := t.Clock() |
||||
t, err = time.Date(y, mo, d, h, mi, s, t.Nanosecond(), loc), nil |
||||
} |
||||
|
||||
return |
||||
} |
||||
|
||||
func parseBinaryDateTime(num uint64, data []byte, loc *time.Location) (driver.Value, error) { |
||||
switch num { |
||||
case 0: |
||||
return time.Time{}, nil |
||||
case 4: |
||||
return time.Date( |
||||
int(binary.LittleEndian.Uint16(data[:2])), // year
|
||||
time.Month(data[2]), // month
|
||||
int(data[3]), // day
|
||||
0, 0, 0, 0, |
||||
loc, |
||||
), nil |
||||
case 7: |
||||
return time.Date( |
||||
int(binary.LittleEndian.Uint16(data[:2])), // year
|
||||
time.Month(data[2]), // month
|
||||
int(data[3]), // day
|
||||
int(data[4]), // hour
|
||||
int(data[5]), // minutes
|
||||
int(data[6]), // seconds
|
||||
0, |
||||
loc, |
||||
), nil |
||||
case 11: |
||||
return time.Date( |
||||
int(binary.LittleEndian.Uint16(data[:2])), // year
|
||||
time.Month(data[2]), // month
|
||||
int(data[3]), // day
|
||||
int(data[4]), // hour
|
||||
int(data[5]), // minutes
|
||||
int(data[6]), // seconds
|
||||
int(binary.LittleEndian.Uint32(data[7:11]))*1000, // nanoseconds
|
||||
loc, |
||||
), nil |
||||
} |
||||
return nil, fmt.Errorf("invalid DATETIME packet length %d", num) |
||||
} |
||||
|
||||
// zeroDateTime is used in formatBinaryDateTime to avoid an allocation
|
||||
// if the DATE or DATETIME has the zero value.
|
||||
// It must never be changed.
|
||||
// The current behavior depends on database/sql copying the result.
|
||||
var zeroDateTime = []byte("0000-00-00 00:00:00.000000") |
||||
|
||||
const digits01 = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" |
||||
const digits10 = "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999" |
||||
|
||||
func formatBinaryDateTime(src []byte, length uint8, justTime bool) (driver.Value, error) { |
||||
// length expects the deterministic length of the zero value,
|
||||
// negative time and 100+ hours are automatically added if needed
|
||||
if len(src) == 0 { |
||||
if justTime { |
||||
return zeroDateTime[11 : 11+length], nil |
||||
} |
||||
return zeroDateTime[:length], nil |
||||
} |
||||
var dst []byte // return value
|
||||
var pt, p1, p2, p3 byte // current digit pair
|
||||
var zOffs byte // offset of value in zeroDateTime
|
||||
if justTime { |
||||
switch length { |
||||
case |
||||
8, // time (can be up to 10 when negative and 100+ hours)
|
||||
10, 11, 12, 13, 14, 15: // time with fractional seconds
|
||||
default: |
||||
return nil, fmt.Errorf("illegal TIME length %d", length) |
||||
} |
||||
switch len(src) { |
||||
case 8, 12: |
||||
default: |
||||
return nil, fmt.Errorf("invalid TIME packet length %d", len(src)) |
||||
} |
||||
// +2 to enable negative time and 100+ hours
|
||||
dst = make([]byte, 0, length+2) |
||||
if src[0] == 1 { |
||||
dst = append(dst, '-') |
||||
} |
||||
if src[1] != 0 { |
||||
hour := uint16(src[1])*24 + uint16(src[5]) |
||||
pt = byte(hour / 100) |
||||
p1 = byte(hour - 100*uint16(pt)) |
||||
dst = append(dst, digits01[pt]) |
||||
} else { |
||||
p1 = src[5] |
||||
} |
||||
zOffs = 11 |
||||
src = src[6:] |
||||
} else { |
||||
switch length { |
||||
case 10, 19, 21, 22, 23, 24, 25, 26: |
||||
default: |
||||
t := "DATE" |
||||
if length > 10 { |
||||
t += "TIME" |
||||
} |
||||
return nil, fmt.Errorf("illegal %s length %d", t, length) |
||||
} |
||||
switch len(src) { |
||||
case 4, 7, 11: |
||||
default: |
||||
t := "DATE" |
||||
if length > 10 { |
||||
t += "TIME" |
||||
} |
||||
return nil, fmt.Errorf("illegal %s packet length %d", t, len(src)) |
||||
} |
||||
dst = make([]byte, 0, length) |
||||
// start with the date
|
||||
year := binary.LittleEndian.Uint16(src[:2]) |
||||
pt = byte(year / 100) |
||||
p1 = byte(year - 100*uint16(pt)) |
||||
p2, p3 = src[2], src[3] |
||||
dst = append(dst, |
||||
digits10[pt], digits01[pt], |
||||
digits10[p1], digits01[p1], '-', |
||||
digits10[p2], digits01[p2], '-', |
||||
digits10[p3], digits01[p3], |
||||
) |
||||
if length == 10 { |
||||
return dst, nil |
||||
} |
||||
if len(src) == 4 { |
||||
return append(dst, zeroDateTime[10:length]...), nil |
||||
} |
||||
dst = append(dst, ' ') |
||||
p1 = src[4] // hour
|
||||
src = src[5:] |
||||
} |
||||
// p1 is 2-digit hour, src is after hour
|
||||
p2, p3 = src[0], src[1] |
||||
dst = append(dst, |
||||
digits10[p1], digits01[p1], ':', |
||||
digits10[p2], digits01[p2], ':', |
||||
digits10[p3], digits01[p3], |
||||
) |
||||
if length <= byte(len(dst)) { |
||||
return dst, nil |
||||
} |
||||
src = src[2:] |
||||
if len(src) == 0 { |
||||
return append(dst, zeroDateTime[19:zOffs+length]...), nil |
||||
} |
||||
microsecs := binary.LittleEndian.Uint32(src[:4]) |
||||
p1 = byte(microsecs / 10000) |
||||
microsecs -= 10000 * uint32(p1) |
||||
p2 = byte(microsecs / 100) |
||||
microsecs -= 100 * uint32(p2) |
||||
p3 = byte(microsecs) |
||||
switch decimals := zOffs + length - 20; decimals { |
||||
default: |
||||
return append(dst, '.', |
||||
digits10[p1], digits01[p1], |
||||
digits10[p2], digits01[p2], |
||||
digits10[p3], digits01[p3], |
||||
), nil |
||||
case 1: |
||||
return append(dst, '.', |
||||
digits10[p1], |
||||
), nil |
||||
case 2: |
||||
return append(dst, '.', |
||||
digits10[p1], digits01[p1], |
||||
), nil |
||||
case 3: |
||||
return append(dst, '.', |
||||
digits10[p1], digits01[p1], |
||||
digits10[p2], |
||||
), nil |
||||
case 4: |
||||
return append(dst, '.', |
||||
digits10[p1], digits01[p1], |
||||
digits10[p2], digits01[p2], |
||||
), nil |
||||
case 5: |
||||
return append(dst, '.', |
||||
digits10[p1], digits01[p1], |
||||
digits10[p2], digits01[p2], |
||||
digits10[p3], |
||||
), nil |
||||
} |
||||
} |
||||
|
||||
/****************************************************************************** |
||||
* Convert from and to bytes * |
||||
******************************************************************************/ |
||||
|
||||
func uint64ToBytes(n uint64) []byte { |
||||
return []byte{ |
||||
byte(n), |
||||
byte(n >> 8), |
||||
byte(n >> 16), |
||||
byte(n >> 24), |
||||
byte(n >> 32), |
||||
byte(n >> 40), |
||||
byte(n >> 48), |
||||
byte(n >> 56), |
||||
} |
||||
} |
||||
|
||||
func uint64ToString(n uint64) []byte { |
||||
var a [20]byte |
||||
i := 20 |
||||
|
||||
// U+0030 = 0
|
||||
// ...
|
||||
// U+0039 = 9
|
||||
|
||||
var q uint64 |
||||
for n >= 10 { |
||||
i-- |
||||
q = n / 10 |
||||
a[i] = uint8(n-q*10) + 0x30 |
||||
n = q |
||||
} |
||||
|
||||
i-- |
||||
a[i] = uint8(n) + 0x30 |
||||
|
||||
return a[i:] |
||||
} |
||||
|
||||
// treats string value as unsigned integer representation
|
||||
func stringToInt(b []byte) int { |
||||
val := 0 |
||||
for i := range b { |
||||
val *= 10 |
||||
val += int(b[i] - 0x30) |
||||
} |
||||
return val |
||||
} |
||||
|
||||
// returns the string read as a bytes slice, wheter the value is NULL,
|
||||
// the number of bytes read and an error, in case the string is longer than
|
||||
// the input slice
|
||||
func readLengthEncodedString(b []byte) ([]byte, bool, int, error) { |
||||
// Get length
|
||||
num, isNull, n := readLengthEncodedInteger(b) |
||||
if num < 1 { |
||||
return b[n:n], isNull, n, nil |
||||
} |
||||
|
||||
n += int(num) |
||||
|
||||
// Check data length
|
||||
if len(b) >= n { |
||||
return b[n-int(num) : n], false, n, nil |
||||
} |
||||
return nil, false, n, io.EOF |
||||
} |
||||
|
||||
// returns the number of bytes skipped and an error, in case the string is
|
||||
// longer than the input slice
|
||||
func skipLengthEncodedString(b []byte) (int, error) { |
||||
// Get length
|
||||
num, _, n := readLengthEncodedInteger(b) |
||||
if num < 1 { |
||||
return n, nil |
||||
} |
||||
|
||||
n += int(num) |
||||
|
||||
// Check data length
|
||||
if len(b) >= n { |
||||
return n, nil |
||||
} |
||||
return n, io.EOF |
||||
} |
||||
|
||||
// returns the number read, whether the value is NULL and the number of bytes read
|
||||
func readLengthEncodedInteger(b []byte) (uint64, bool, int) { |
||||
// See issue #349
|
||||
if len(b) == 0 { |
||||
return 0, true, 1 |
||||
} |
||||
switch b[0] { |
||||
|
||||
// 251: NULL
|
||||
case 0xfb: |
||||
return 0, true, 1 |
||||
|
||||
// 252: value of following 2
|
||||
case 0xfc: |
||||
return uint64(b[1]) | uint64(b[2])<<8, false, 3 |
||||
|
||||
// 253: value of following 3
|
||||
case 0xfd: |
||||
return uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16, false, 4 |
||||
|
||||
// 254: value of following 8
|
||||
case 0xfe: |
||||
return uint64(b[1]) | uint64(b[2])<<8 | uint64(b[3])<<16 | |
||||
uint64(b[4])<<24 | uint64(b[5])<<32 | uint64(b[6])<<40 | |
||||
uint64(b[7])<<48 | uint64(b[8])<<56, |
||||
false, 9 |
||||
} |
||||
|
||||
// 0-250: value of first byte
|
||||
return uint64(b[0]), false, 1 |
||||
} |
||||
|
||||
// encodes a uint64 value and appends it to the given bytes slice
|
||||
func appendLengthEncodedInteger(b []byte, n uint64) []byte { |
||||
switch { |
||||
case n <= 250: |
||||
return append(b, byte(n)) |
||||
|
||||
case n <= 0xffff: |
||||
return append(b, 0xfc, byte(n), byte(n>>8)) |
||||
|
||||
case n <= 0xffffff: |
||||
return append(b, 0xfd, byte(n), byte(n>>8), byte(n>>16)) |
||||
} |
||||
return append(b, 0xfe, byte(n), byte(n>>8), byte(n>>16), byte(n>>24), |
||||
byte(n>>32), byte(n>>40), byte(n>>48), byte(n>>56)) |
||||
} |
||||
|
||||
// reserveBuffer checks cap(buf) and expand buffer to len(buf) + appendSize.
|
||||
// If cap(buf) is not enough, reallocate new buffer.
|
||||
func reserveBuffer(buf []byte, appendSize int) []byte { |
||||
newSize := len(buf) + appendSize |
||||
if cap(buf) < newSize { |
||||
// Grow buffer exponentially
|
||||
newBuf := make([]byte, len(buf)*2+appendSize) |
||||
copy(newBuf, buf) |
||||
buf = newBuf |
||||
} |
||||
return buf[:newSize] |
||||
} |
||||
|
||||
// escapeBytesBackslash escapes []byte with backslashes (\)
|
||||
// This escapes the contents of a string (provided as []byte) by adding backslashes before special
|
||||
// characters, and turning others into specific escape sequences, such as
|
||||
// turning newlines into \n and null bytes into \0.
|
||||
// https://github.com/mysql/mysql-server/blob/mysql-5.7.5/mysys/charset.c#L823-L932
|
||||
func escapeBytesBackslash(buf, v []byte) []byte { |
||||
pos := len(buf) |
||||
buf = reserveBuffer(buf, len(v)*2) |
||||
|
||||
for _, c := range v { |
||||
switch c { |
||||
case '\x00': |
||||
buf[pos] = '\\' |
||||
buf[pos+1] = '0' |
||||
pos += 2 |
||||
case '\n': |
||||
buf[pos] = '\\' |
||||
buf[pos+1] = 'n' |
||||
pos += 2 |
||||
case '\r': |
||||
buf[pos] = '\\' |
||||
buf[pos+1] = 'r' |
||||
pos += 2 |
||||
case '\x1a': |
||||
buf[pos] = '\\' |
||||
buf[pos+1] = 'Z' |
||||
pos += 2 |
||||
case '\'': |
||||
buf[pos] = '\\' |
||||
buf[pos+1] = '\'' |
||||
pos += 2 |
||||
case '"': |
||||
buf[pos] = '\\' |
||||
buf[pos+1] = '"' |
||||
pos += 2 |
||||
case '\\': |
||||
buf[pos] = '\\' |
||||
buf[pos+1] = '\\' |
||||
pos += 2 |
||||
default: |
||||
buf[pos] = c |
||||
pos++ |
||||
} |
||||
} |
||||
|
||||
return buf[:pos] |
||||
} |
||||
|
||||
// escapeStringBackslash is similar to escapeBytesBackslash but for string.
|
||||
func escapeStringBackslash(buf []byte, v string) []byte { |
||||
pos := len(buf) |
||||
buf = reserveBuffer(buf, len(v)*2) |
||||
|
||||
for i := 0; i < len(v); i++ { |
||||
c := v[i] |
||||
switch c { |
||||
case '\x00': |
||||
buf[pos] = '\\' |
||||
buf[pos+1] = '0' |
||||
pos += 2 |
||||
case '\n': |
||||
buf[pos] = '\\' |
||||
buf[pos+1] = 'n' |
||||
pos += 2 |
||||
case '\r': |
||||
buf[pos] = '\\' |
||||
buf[pos+1] = 'r' |
||||
pos += 2 |
||||
case '\x1a': |
||||
buf[pos] = '\\' |
||||
buf[pos+1] = 'Z' |
||||
pos += 2 |
||||
case '\'': |
||||
buf[pos] = '\\' |
||||
buf[pos+1] = '\'' |
||||
pos += 2 |
||||
case '"': |
||||
buf[pos] = '\\' |
||||
buf[pos+1] = '"' |
||||
pos += 2 |
||||
case '\\': |
||||
buf[pos] = '\\' |
||||
buf[pos+1] = '\\' |
||||
pos += 2 |
||||
default: |
||||
buf[pos] = c |
||||
pos++ |
||||
} |
||||
} |
||||
|
||||
return buf[:pos] |
||||
} |
||||
|
||||
// escapeBytesQuotes escapes apostrophes in []byte by doubling them up.
|
||||
// This escapes the contents of a string by doubling up any apostrophes that
|
||||
// it contains. This is used when the NO_BACKSLASH_ESCAPES SQL_MODE is in
|
||||
// effect on the server.
|
||||
// https://github.com/mysql/mysql-server/blob/mysql-5.7.5/mysys/charset.c#L963-L1038
|
||||
func escapeBytesQuotes(buf, v []byte) []byte { |
||||
pos := len(buf) |
||||
buf = reserveBuffer(buf, len(v)*2) |
||||
|
||||
for _, c := range v { |
||||
if c == '\'' { |
||||
buf[pos] = '\'' |
||||
buf[pos+1] = '\'' |
||||
pos += 2 |
||||
} else { |
||||
buf[pos] = c |
||||
pos++ |
||||
} |
||||
} |
||||
|
||||
return buf[:pos] |
||||
} |
||||
|
||||
// escapeStringQuotes is similar to escapeBytesQuotes but for string.
|
||||
func escapeStringQuotes(buf []byte, v string) []byte { |
||||
pos := len(buf) |
||||
buf = reserveBuffer(buf, len(v)*2) |
||||
|
||||
for i := 0; i < len(v); i++ { |
||||
c := v[i] |
||||
if c == '\'' { |
||||
buf[pos] = '\'' |
||||
buf[pos+1] = '\'' |
||||
pos += 2 |
||||
} else { |
||||
buf[pos] = c |
||||
pos++ |
||||
} |
||||
} |
||||
|
||||
return buf[:pos] |
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue