mirror of
https://github.com/aptly-dev/aptly.git
synced 2026-10-22 11:08:20 +00:00
Imported Upstream version 1.0.1
This commit is contained in:
1 parent
1ee35b841d
commit
4f12259bc0
5531 files changed
+1834599
-742844
No files matched your search
+11
@@ -0,0 +1,11 @@
|
||||
# This is the official list of Go-Commander authors for copyright purposes.
|
||||
# This file is distinct from the CONTRIBUTORS files.
|
||||
# See the latter for an explanation.
|
||||
|
||||
# Names should be added to this file as
|
||||
# Name or Organization <email address>
|
||||
# The email address is not required for organizations.
|
||||
|
||||
# Please keep the list sorted.
|
||||
|
||||
Google Inc
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
# This is the official list of people who can contribute
|
||||
# (and typically have contributed) code to the Go-Commander repository.
|
||||
# The AUTHORS file lists the copyright holders; this file
|
||||
# lists people. For example, Google employees are listed here
|
||||
# but not in AUTHORS, because Google holds the copyright.
|
||||
#
|
||||
# The submission process automatically checks to make sure
|
||||
# that people submitting code are listed in this file (by email address).
|
||||
#
|
||||
# Names should be added to this file only after verifying that
|
||||
# the individual or the individual's organization has agreed to
|
||||
# the appropriate Contributor License Agreement, found here:
|
||||
#
|
||||
# http://code.google.com/legal/individual-cla-v1.0.html
|
||||
# http://code.google.com/legal/corporate-cla-v1.0.html
|
||||
#
|
||||
# The agreement for individuals can be filled out on the web.
|
||||
#
|
||||
# When adding J Random Contributor's name to this file,
|
||||
# either J's name or J's organization's name should be
|
||||
# added to the AUTHORS file, depending on whether the
|
||||
# individual or corporate CLA was used.
|
||||
|
||||
# Names should be added to this file like so:
|
||||
# Name <email address>
|
||||
|
||||
# Please keep the list sorted.
|
||||
|
||||
Juan Batiz-Benet <juan@benet.ai>
|
||||
Sebastien Binet <seb.binet@gmail.com>
|
||||
Yves Junqueira <yves.junqueira@gmail.com>
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2012 The Go-Commander Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
commander
|
||||
============
|
||||
|
||||
``commander`` is a fork of a spin off of [golang](http://golang.org) ``go tool`` infrastructure to provide commands and sub-commands.
|
||||
|
||||
Difference with [original](http://github.com/gonuts/commander/):
|
||||
|
||||
- flags could be placed anywhere on the command line
|
||||
- flags in whole subcommand tree should be non-conflicting (i.e. one name - one value type)
|
||||
- interface has changed: first flags should be parsed, then command dispatched
|
||||
|
||||
A ``commander.Command`` has a ``Subcommands`` field holding ``[]*commander.Command`` subcommands, referenced by name from the command line.
|
||||
|
||||
So a ``Command`` can have sub commands.
|
||||
|
||||
So you can have, _e.g._:
|
||||
```sh
|
||||
$ mycmd action1 [options...]
|
||||
$ mycmd subcmd1 action1 [options...]
|
||||
```
|
||||
|
||||
Example provided by:
|
||||
- [hwaf](https://github.com/hwaf/hwaf)
|
||||
- [examples/my-cmd](examples/my-cmd)
|
||||
|
||||
## Documentation
|
||||
Is available on [godoc](http://godoc.org/github.com/gonuts/commander)
|
||||
|
||||
## Installation
|
||||
Is performed with the usual:
|
||||
```sh
|
||||
$ go get github.com/gonuts/commander
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
See the simple ``my-cmd`` example command for how this all hangs
|
||||
together [there](http://github.com/gonuts/commander/blob/master/examples/my-cmd/main.go):
|
||||
|
||||
```sh
|
||||
$ my-cmd cmd1
|
||||
my-cmd-cmd1: hello from cmd1 (quiet=true)
|
||||
|
||||
$ my-cmd cmd1 -q
|
||||
my-cmd-cmd1: hello from cmd1 (quiet=true)
|
||||
|
||||
$ my-cmd cmd1 -q=0
|
||||
my-cmd-cmd1: hello from cmd1 (quiet=false)
|
||||
|
||||
$ my-cmd cmd2
|
||||
my-cmd-cmd2: hello from cmd2 (quiet=true)
|
||||
|
||||
$ my-cmd subcmd1 cmd1
|
||||
my-cmd-subcmd1-cmd1: hello from subcmd1-cmd1 (quiet=true)
|
||||
|
||||
$ my-cmd subcmd1 cmd2
|
||||
my-cmd-subcmd1-cmd2: hello from subcmd1-cmd2 (quiet=true)
|
||||
|
||||
$ my-cmd subcmd2 cmd1
|
||||
my-cmd-subcmd2-cmd1: hello from subcmd2-cmd1 (quiet=true)
|
||||
|
||||
$ my-cmd subcmd2 cmd2
|
||||
my-cmd-subcmd2-cmd2: hello from subcmd2-cmd2 (quiet=true)
|
||||
|
||||
$ my-cmd help
|
||||
Usage:
|
||||
|
||||
my-cmd command [arguments]
|
||||
|
||||
The commands are:
|
||||
|
||||
cmd1 runs cmd1 and exits
|
||||
cmd2 runs cmd2 and exits
|
||||
subcmd1 subcmd1 subcommand. does subcmd1 thingies
|
||||
subcmd2 subcmd2 subcommand. does subcmd2 thingies
|
||||
|
||||
Use "my-cmd help [command]" for more information about a command.
|
||||
|
||||
Additional help topics:
|
||||
|
||||
|
||||
Use "my-cmd help [topic]" for more information about that topic.
|
||||
|
||||
|
||||
$ my-cmd help subcmd1
|
||||
Usage:
|
||||
|
||||
subcmd1 command [arguments]
|
||||
|
||||
The commands are:
|
||||
|
||||
cmd1 runs cmd1 and exits
|
||||
cmd2 runs cmd2 and exits
|
||||
|
||||
|
||||
Use "subcmd1 help [command]" for more information about a command.
|
||||
|
||||
Additional help topics:
|
||||
|
||||
|
||||
Use "subcmd1 help [topic]" for more information about that topic.
|
||||
|
||||
```
|
||||
|
||||
|
||||
## TODO
|
||||
|
||||
- automatically generate the bash/zsh/csh autocompletion lists
|
||||
- automatically generate Readme examples text
|
||||
- test cases
|
||||
|
||||
+442
@@ -0,0 +1,442 @@
|
||||
// Copyright 2012 The Go-Commander Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
//
|
||||
// Based on the original work by The Go Authors:
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
|
||||
// commander helps creating command line programs whose arguments are flags,
|
||||
// commands and subcommands.
|
||||
package commander
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/smira/flag"
|
||||
)
|
||||
|
||||
// UsageSection differentiates between sections in the usage text.
|
||||
type Listing int
|
||||
|
||||
const (
|
||||
CommandsList = iota
|
||||
HelpTopicsList
|
||||
Unlisted
|
||||
)
|
||||
|
||||
var (
|
||||
ErrFlagError = errors.New("unable to parse flags")
|
||||
ErrCommandError = errors.New("unable to parse command")
|
||||
)
|
||||
|
||||
// A Command is an implementation of a subcommand.
|
||||
type Command struct {
|
||||
|
||||
// UsageLine is the short usage message.
|
||||
// The first word in the line is taken to be the command name.
|
||||
UsageLine string
|
||||
|
||||
// Short is the short description line shown in command lists.
|
||||
Short string
|
||||
|
||||
// Long is the long description shown in the 'help <this-command>' output.
|
||||
Long string
|
||||
|
||||
// List reports which list to show this command in Usage and Help.
|
||||
// Choose between {CommandsList (default), HelpTopicsList, Unlisted}
|
||||
List Listing
|
||||
|
||||
// Run runs the command.
|
||||
// The args are the arguments after the command name.
|
||||
Run func(cmd *Command, args []string) error
|
||||
|
||||
// Flag is a set of flags specific to this command.
|
||||
Flag flag.FlagSet
|
||||
|
||||
// CustomFlags indicates that the command will do its own
|
||||
// flag parsing.
|
||||
CustomFlags bool
|
||||
|
||||
// Subcommands are dispatched from this command
|
||||
Subcommands []*Command
|
||||
|
||||
// Parent command, nil for root.
|
||||
Parent *Command
|
||||
|
||||
// UsageTemplate formats the usage (short) information displayed to the user
|
||||
// (leave empty for default)
|
||||
UsageTemplate string
|
||||
|
||||
// HelpTemplate formats the help (long) information displayed to the user
|
||||
// (leave empty for default)
|
||||
HelpTemplate string
|
||||
|
||||
// Stdout and Stderr by default are os.Stdout and os.Stderr, but you can
|
||||
// point them at any io.Writer
|
||||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
|
||||
// mergedFlags is merged flagset from this command and all subcommands
|
||||
mergedFlags *flag.FlagSet
|
||||
}
|
||||
|
||||
// Name returns the command's name: the first word in the usage line.
|
||||
func (c *Command) Name() string {
|
||||
name := c.UsageLine
|
||||
i := strings.Index(name, " ")
|
||||
if i >= 0 {
|
||||
name = name[:i]
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
// Usage prints the usage details to the standard error output.
|
||||
func (c *Command) Usage() {
|
||||
c.usage()
|
||||
}
|
||||
|
||||
// FlagOptions returns the flag's options as a string
|
||||
func (c *Command) FlagOptions() string {
|
||||
var buf bytes.Buffer
|
||||
|
||||
flags := flag.NewFlagSet("help", 0)
|
||||
for cmd := c; cmd != nil; cmd = cmd.Parent {
|
||||
flags.Merge(&cmd.Flag)
|
||||
}
|
||||
flags.SetOutput(&buf)
|
||||
flags.PrintDefaults()
|
||||
|
||||
str := string(buf.Bytes())
|
||||
if len(str) > 0 {
|
||||
return fmt.Sprintf("\nOptions:\n%s", str)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Runnable reports whether the command can be run; otherwise
|
||||
// it is a documentation pseudo-command such as importpath.
|
||||
func (c *Command) Runnable() bool {
|
||||
return c.Run != nil
|
||||
}
|
||||
|
||||
// Type to allow us to use sort.Sort on a slice of Commands
|
||||
type CommandSlice []*Command
|
||||
|
||||
func (c CommandSlice) Len() int {
|
||||
return len(c)
|
||||
}
|
||||
|
||||
func (c CommandSlice) Less(i, j int) bool {
|
||||
return c[i].Name() < c[j].Name()
|
||||
}
|
||||
|
||||
func (c CommandSlice) Swap(i, j int) {
|
||||
c[i], c[j] = c[j], c[i]
|
||||
}
|
||||
|
||||
// Sort the commands
|
||||
func (c *Command) SortCommands() {
|
||||
sort.Sort(CommandSlice(c.Subcommands))
|
||||
}
|
||||
|
||||
// Init the command
|
||||
func (c *Command) init() {
|
||||
if c.Parent != nil {
|
||||
return // already initialized.
|
||||
}
|
||||
|
||||
// setup strings
|
||||
if len(c.UsageLine) < 1 {
|
||||
c.UsageLine = Defaults.UsageLine
|
||||
}
|
||||
if len(c.UsageTemplate) < 1 {
|
||||
c.UsageTemplate = Defaults.UsageTemplate
|
||||
}
|
||||
if len(c.HelpTemplate) < 1 {
|
||||
c.HelpTemplate = Defaults.HelpTemplate
|
||||
}
|
||||
|
||||
if c.Stderr == nil {
|
||||
c.Stderr = os.Stderr
|
||||
}
|
||||
if c.Stdout == nil {
|
||||
c.Stdout = os.Stdout
|
||||
}
|
||||
|
||||
// init subcommands
|
||||
for _, cmd := range c.Subcommands {
|
||||
cmd.init()
|
||||
}
|
||||
|
||||
// init hierarchy...
|
||||
for _, cmd := range c.Subcommands {
|
||||
cmd.Parent = c
|
||||
}
|
||||
|
||||
// merge flags
|
||||
c.mergedFlags = flag.NewFlagSet("merged", flag.ContinueOnError)
|
||||
c.mergedFlags.Merge(&c.Flag)
|
||||
|
||||
for _, cmd := range c.Subcommands {
|
||||
c.mergedFlags.Merge(cmd.mergedFlags)
|
||||
}
|
||||
}
|
||||
|
||||
// ParseFlags parses flags in whole command subtree and returns resulting FlagSet
|
||||
func (c *Command) ParseFlags(args []string) (result *flag.FlagSet, argsNoFlags []string, err error) {
|
||||
// Ensure command is initialized.
|
||||
c.init()
|
||||
|
||||
parseFlags := func(c *Command, args []string, flags *flag.FlagSet, setValue bool) (leftArgs []string, err error) {
|
||||
flags.Usage = func() {
|
||||
c.Usage()
|
||||
err = ErrFlagError
|
||||
}
|
||||
flags.Parse(args, setValue)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
leftArgs = flags.Args()
|
||||
return
|
||||
}
|
||||
|
||||
// First pass, go with merged flags and figure out command path
|
||||
path := []*Command{c}
|
||||
arguments := append([]string(nil), args...)
|
||||
argsNoFlags = []string{}
|
||||
|
||||
for len(arguments) > 0 {
|
||||
arguments, err = parseFlags(path[len(path)-1], arguments, c.mergedFlags, false)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(arguments) > 0 {
|
||||
found := false
|
||||
|
||||
for _, cmd := range path[len(path)-1].Subcommands {
|
||||
if cmd.Name() == arguments[0] {
|
||||
path = append(path, cmd)
|
||||
argsNoFlags = append(argsNoFlags, arguments[0])
|
||||
arguments = arguments[1:]
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
argsNoFlags = append(argsNoFlags, arguments...)
|
||||
|
||||
// Build resulting flagset
|
||||
result = flag.NewFlagSet("result", flag.ExitOnError)
|
||||
|
||||
for _, cmd := range path {
|
||||
result.Merge(&cmd.Flag)
|
||||
}
|
||||
|
||||
// Parse flags finally
|
||||
arguments = append([]string(nil), args...)
|
||||
|
||||
for _, cmd := range path {
|
||||
arguments, err = parseFlags(cmd, arguments, result, true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(arguments) > 0 {
|
||||
arguments = arguments[1:]
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Dispatch executes the command using the provided arguments.
|
||||
// If a subcommand exists matching the first argument, it is dispatched.
|
||||
// Otherwise, the command's Run function is called.
|
||||
func (c *Command) Dispatch(args []string) error {
|
||||
if c == nil {
|
||||
return fmt.Errorf("Called Run() on a nil Command")
|
||||
}
|
||||
|
||||
// Ensure command is initialized.
|
||||
c.init()
|
||||
|
||||
// First, try a sub-command
|
||||
if len(args) > 0 {
|
||||
for _, cmd := range c.Subcommands {
|
||||
n := cmd.Name()
|
||||
if n == args[0] {
|
||||
return cmd.Dispatch(args[1:])
|
||||
}
|
||||
}
|
||||
|
||||
// help is builtin (but after, to allow overriding)
|
||||
if args[0] == "help" {
|
||||
return c.help(args[1:])
|
||||
}
|
||||
|
||||
// then, try out an external binary (git-style)
|
||||
bin, err := exec.LookPath(c.FullName() + "-" + args[0])
|
||||
if err == nil {
|
||||
cmd := exec.Command(bin, args[1:]...)
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = c.Stdout
|
||||
cmd.Stderr = c.Stderr
|
||||
return cmd.Run()
|
||||
}
|
||||
}
|
||||
|
||||
// then, try running this command
|
||||
if c.Runnable() {
|
||||
return c.Run(c, args)
|
||||
}
|
||||
|
||||
// TODO: try an alias
|
||||
//...
|
||||
|
||||
// Last, print usage
|
||||
if err := c.usage(); err != nil {
|
||||
return err
|
||||
}
|
||||
return ErrCommandError
|
||||
}
|
||||
|
||||
func (c *Command) usage() error {
|
||||
c.SortCommands()
|
||||
err := tmpl(c.Stderr, c.UsageTemplate, c)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// help implements the 'help' command.
|
||||
func (c *Command) help(args []string) error {
|
||||
|
||||
// help exactly for this command?
|
||||
if len(args) == 0 {
|
||||
if len(c.Long) > 0 {
|
||||
return tmpl(c.Stdout, c.HelpTemplate, c)
|
||||
} else {
|
||||
return c.usage()
|
||||
}
|
||||
}
|
||||
|
||||
arg := args[0]
|
||||
|
||||
// is this help for a subcommand?
|
||||
for _, cmd := range c.Subcommands {
|
||||
n := cmd.Name()
|
||||
// strip out "<parent>-"" name
|
||||
if strings.HasPrefix(n, c.Name()+"-") {
|
||||
n = n[len(c.Name()+"-"):]
|
||||
}
|
||||
if n == arg {
|
||||
return cmd.help(args[1:])
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("Unknown help topic %#q. Run '%v help'.\n", arg, c.Name())
|
||||
}
|
||||
|
||||
func (c *Command) MaxLen() (res int) {
|
||||
res = 0
|
||||
for _, cmd := range c.Subcommands {
|
||||
i := len(cmd.Name())
|
||||
if i > res {
|
||||
res = i
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ColFormat returns the column header size format for printing in the template
|
||||
func (c *Command) ColFormat() string {
|
||||
sz := c.MaxLen()
|
||||
if sz < 11 {
|
||||
sz = 11
|
||||
}
|
||||
return fmt.Sprintf("%%-%ds", sz)
|
||||
}
|
||||
|
||||
// FullName returns the full name of the command, prefixed with parent commands
|
||||
func (c *Command) FullName() string {
|
||||
n := c.Name()
|
||||
if c.Parent != nil {
|
||||
n = c.Parent.FullName() + "-" + n
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// FullSpacedName returns the full name of the command, with ' ' instead of '-'
|
||||
func (c *Command) FullSpacedName() string {
|
||||
n := c.Name()
|
||||
if c.Parent != nil {
|
||||
n = c.Parent.FullSpacedName() + " " + n
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (c *Command) SubcommandList(list Listing) []*Command {
|
||||
var cmds []*Command
|
||||
for _, cmd := range c.Subcommands {
|
||||
if cmd.List == list {
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
}
|
||||
return cmds
|
||||
}
|
||||
|
||||
var Defaults = Command{
|
||||
UsageTemplate: `{{if .Runnable}}Usage: {{if .Parent}}{{.Parent.FullSpacedName}}{{end}} {{.UsageLine}}
|
||||
|
||||
{{end}}{{.FullSpacedName}} - {{.Short}}
|
||||
|
||||
{{if commandList}}Commands:
|
||||
{{range commandList}}
|
||||
{{.Name | printf (colfmt)}} {{.Short}}{{end}}
|
||||
|
||||
Use "{{.Name}} help <command>" for more information about a command.
|
||||
|
||||
{{end}}{{.FlagOptions}}{{if helpList}}
|
||||
Additional help topics:
|
||||
{{range helpList}}
|
||||
{{.Name | printf (colfmt)}} {{.Short}}{{end}}
|
||||
|
||||
Use "{{.Name}} help <topic>" for more information about that topic.
|
||||
|
||||
{{end}}`,
|
||||
|
||||
HelpTemplate: `{{if .Runnable}}Usage: {{if .Parent}}{{.Parent.FullSpacedName}}{{end}} {{.UsageLine}}
|
||||
|
||||
{{end}}{{.Long | trim}}
|
||||
{{.FlagOptions}}
|
||||
`,
|
||||
}
|
||||
|
||||
// tmpl executes the given template text on data, writing the result to w.
|
||||
func tmpl(w io.Writer, text string, data interface{}) error {
|
||||
t := template.New("top")
|
||||
t.Funcs(template.FuncMap{
|
||||
"trim": strings.TrimSpace,
|
||||
"colfmt": func() string { return data.(*Command).ColFormat() },
|
||||
"commandList": func() []*Command { return data.(*Command).SubcommandList(CommandsList) },
|
||||
"helpList": func() []*Command { return data.(*Command).SubcommandList(HelpTopicsList) },
|
||||
})
|
||||
template.Must(t.Parse(text))
|
||||
return t.Execute(w, data)
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gonuts/commander"
|
||||
"github.com/gonuts/flag"
|
||||
)
|
||||
|
||||
var cmd_cmd1 = &commander.Command{
|
||||
Run: ex_run_cmd_cmd1,
|
||||
UsageLine: "cmd1 [options]",
|
||||
Short: "runs cmd1 and exits",
|
||||
Long: `
|
||||
runs cmd1 and exits.
|
||||
|
||||
ex:
|
||||
$ my-cmd cmd1
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("my-cmd-cmd1", flag.ExitOnError),
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmd_cmd1.Flag.Bool("q", true, "only print error and warning messages, all other output will be suppressed")
|
||||
}
|
||||
|
||||
func ex_run_cmd_cmd1(cmd *commander.Command, args []string) error {
|
||||
name := "my-cmd-" + cmd.Name()
|
||||
quiet := cmd.Flag.Lookup("q").Value.Get().(bool)
|
||||
fmt.Printf("%s: hello from cmd1 (quiet=%v)\n", name, quiet)
|
||||
return nil
|
||||
}
|
||||
|
||||
// EOF
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gonuts/commander"
|
||||
"github.com/gonuts/flag"
|
||||
)
|
||||
|
||||
func ex_make_cmd_cmd2() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: ex_run_cmd_cmd2,
|
||||
UsageLine: "cmd2 [options]",
|
||||
Short: "runs cmd2 and exits",
|
||||
Long: `
|
||||
runs cmd2 and exits.
|
||||
|
||||
ex:
|
||||
$ my-cmd cmd2
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("my-cmd-cmd2", flag.ExitOnError),
|
||||
}
|
||||
cmd.Flag.Bool("q", true, "only print error and warning messages, all other output will be suppressed")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func ex_run_cmd_cmd2(cmd *commander.Command, args []string) error {
|
||||
name := "my-cmd-" + cmd.Name()
|
||||
quiet := cmd.Flag.Lookup("q").Value.Get().(bool)
|
||||
fmt.Printf("%s: hello from cmd2 (quiet=%v)\n", name, quiet)
|
||||
return nil
|
||||
}
|
||||
|
||||
// EOF
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gonuts/commander"
|
||||
"github.com/gonuts/flag"
|
||||
)
|
||||
|
||||
var cmd_subcmd1 = &commander.Command{
|
||||
UsageLine: "subcmd1 <command>",
|
||||
Short: "subcmd1 subcommand. does subcmd1 thingies",
|
||||
Subcommands: []*commander.Command{
|
||||
cmd_subcmd1_cmd1,
|
||||
cmd_subcmd1_cmd2,
|
||||
},
|
||||
Flag: *flag.NewFlagSet("my-cmd-subcmd1", flag.ExitOnError),
|
||||
}
|
||||
|
||||
// EOF
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gonuts/commander"
|
||||
"github.com/gonuts/flag"
|
||||
)
|
||||
|
||||
var cmd_subcmd1_cmd1 = &commander.Command{
|
||||
Run: ex_run_cmd_subcmd1_cmd1,
|
||||
UsageLine: "cmd1 [options]",
|
||||
Short: "runs cmd1 and exits",
|
||||
Long: `
|
||||
runs cmd1 and exits.
|
||||
|
||||
ex:
|
||||
$ my-cmd subcmd1 cmd1
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("my-cmd-subcmd1-cmd1", flag.ExitOnError),
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmd_subcmd1_cmd1.Flag.Bool("q", true, "only print error and warning messages, all other output will be suppressed")
|
||||
}
|
||||
|
||||
func ex_run_cmd_subcmd1_cmd1(cmd *commander.Command, args []string) error {
|
||||
name := "my-cmd-subcmd1-" + cmd.Name()
|
||||
quiet := cmd.Flag.Lookup("q").Value.Get().(bool)
|
||||
fmt.Printf("%s: hello from subcmd1-cmd1 (quiet=%v)\n", name, quiet)
|
||||
return nil
|
||||
}
|
||||
|
||||
// EOF
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gonuts/commander"
|
||||
"github.com/gonuts/flag"
|
||||
)
|
||||
|
||||
var cmd_subcmd1_cmd2 = &commander.Command{
|
||||
Run: ex_run_cmd_subcmd1_cmd2,
|
||||
UsageLine: "cmd2 [options]",
|
||||
Short: "runs cmd2 and exits",
|
||||
Long: `
|
||||
runs cmd2 and exits.
|
||||
|
||||
ex:
|
||||
$ my-cmd subcmd1 cmd2
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("my-cmd-subcmd1-cmd2", flag.ExitOnError),
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmd_subcmd1_cmd2.Flag.Bool("q", true, "only print error and warning messages, all other output will be suppressed")
|
||||
}
|
||||
|
||||
func ex_run_cmd_subcmd1_cmd2(cmd *commander.Command, args []string) error {
|
||||
name := "my-cmd-subcmd1-" + cmd.Name()
|
||||
quiet := cmd.Flag.Lookup("q").Value.Get().(bool)
|
||||
fmt.Printf("%s: hello from subcmd1-cmd2 (quiet=%v)\n", name, quiet)
|
||||
return nil
|
||||
}
|
||||
|
||||
// EOF
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gonuts/commander"
|
||||
"github.com/gonuts/flag"
|
||||
)
|
||||
|
||||
func ex_make_cmd_subcmd2() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
UsageLine: "subcmd2",
|
||||
Short: "subcmd2 subcommand. does subcmd2 thingies (help list)",
|
||||
List: commander.HelpTopicsList,
|
||||
Subcommands: []*commander.Command{
|
||||
ex_make_cmd_subcmd2_cmd1(),
|
||||
ex_make_cmd_subcmd2_cmd2(),
|
||||
},
|
||||
Flag: *flag.NewFlagSet("my-cmd-subcmd2", flag.ExitOnError),
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
// EOF
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gonuts/commander"
|
||||
"github.com/gonuts/flag"
|
||||
)
|
||||
|
||||
func ex_make_cmd_subcmd2_cmd1() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: ex_run_cmd_subcmd2_cmd1,
|
||||
UsageLine: "cmd1 [options]",
|
||||
Short: "runs cmd1 and exits",
|
||||
Long: `
|
||||
runs cmd1 and exits.
|
||||
|
||||
ex:
|
||||
$ my-cmd subcmd2 cmd1
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("my-cmd-subcmd2-cmd1", flag.ExitOnError),
|
||||
}
|
||||
cmd.Flag.Bool("q", true, "only print error and warning messages, all other output will be suppressed")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func ex_run_cmd_subcmd2_cmd1(cmd *commander.Command, args []string) error {
|
||||
name := "my-cmd-subcmd2-" + cmd.Name()
|
||||
quiet := cmd.Flag.Lookup("q").Value.Get().(bool)
|
||||
fmt.Printf("%s: hello from subcmd2-cmd1 (quiet=%v)\n", name, quiet)
|
||||
return nil
|
||||
}
|
||||
|
||||
// EOF
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gonuts/commander"
|
||||
"github.com/gonuts/flag"
|
||||
)
|
||||
|
||||
func ex_make_cmd_subcmd2_cmd2() *commander.Command {
|
||||
cmd := &commander.Command{
|
||||
Run: ex_run_cmd_subcmd2_cmd2,
|
||||
UsageLine: "cmd2 [options]",
|
||||
Short: "runs cmd2 and exits",
|
||||
Long: `
|
||||
runs cmd2 and exits.
|
||||
|
||||
ex:
|
||||
$ my-cmd subcmd2 cmd2
|
||||
`,
|
||||
Flag: *flag.NewFlagSet("my-cmd-subcmd2-cmd2", flag.ExitOnError),
|
||||
}
|
||||
cmd.Flag.Bool("q", true, "only print error and warning messages, all other output will be suppressed")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func ex_run_cmd_subcmd2_cmd2(cmd *commander.Command, args []string) error {
|
||||
name := "my-cmd-subcmd2-" + cmd.Name()
|
||||
quiet := cmd.Flag.Lookup("q").Value.Get().(bool)
|
||||
fmt.Printf("%s: hello from subcmd2-cmd2 (quiet=%v)\n", name, quiet)
|
||||
return nil
|
||||
}
|
||||
|
||||
// EOF
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/gonuts/commander"
|
||||
)
|
||||
|
||||
var g_cmd = &commander.Command{
|
||||
UsageLine: os.Args[0] + " does cool things",
|
||||
}
|
||||
|
||||
func init() {
|
||||
g_cmd.Subcommands = []*commander.Command{
|
||||
cmd_cmd1,
|
||||
ex_make_cmd_cmd2(),
|
||||
cmd_subcmd1,
|
||||
ex_make_cmd_subcmd2(),
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
err := g_cmd.Dispatch(os.Args[1:])
|
||||
if err != nil {
|
||||
fmt.Printf("%v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// EOF
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
flag
|
||||
=======
|
||||
|
||||
A fork of fork of the official "flag" package but with:
|
||||
|
||||
* the flag.Value interface extended to provide a ``Get() interface{}`` method,
|
||||
* method ``(FlagSet *) Merge`` to merge two non-conficting flag sets
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// These examples demonstrate more intricate uses of the flag package.
|
||||
package flag_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Example 1: A single string flag called "species" with default value "gopher".
|
||||
var species = flag.String("species", "gopher", "the species we are studying")
|
||||
|
||||
// Example 2: Two flags sharing a variable, so we can have a shorthand.
|
||||
// The order of initialization is undefined, so make sure both use the
|
||||
// same default value. They must be set up with an init function.
|
||||
var gopherType string
|
||||
|
||||
func init() {
|
||||
const (
|
||||
defaultGopher = "pocket"
|
||||
usage = "the variety of gopher"
|
||||
)
|
||||
flag.StringVar(&gopherType, "gopher_type", defaultGopher, usage)
|
||||
flag.StringVar(&gopherType, "g", defaultGopher, usage+" (shorthand)")
|
||||
}
|
||||
|
||||
// Example 3: A user-defined flag type, a slice of durations.
|
||||
type interval []time.Duration
|
||||
|
||||
// String is the method to format the flag's value, part of the flag.Value interface.
|
||||
// The String method's output will be used in diagnostics.
|
||||
func (i *interval) String() string {
|
||||
return fmt.Sprint(*i)
|
||||
}
|
||||
|
||||
// Set is the method to set the flag value, part of the flag.Value interface.
|
||||
// Set's argument is a string to be parsed to set the flag.
|
||||
// It's a comma-separated list, so we split it.
|
||||
func (i *interval) Set(value string) error {
|
||||
// If we wanted to allow the flag to be set multiple times,
|
||||
// accumulating values, we would delete this if statement.
|
||||
// That would permit usages such as
|
||||
// -deltaT 10s -deltaT 15s
|
||||
// and other combinations.
|
||||
if len(*i) > 0 {
|
||||
return errors.New("interval flag already set")
|
||||
}
|
||||
for _, dt := range strings.Split(value, ",") {
|
||||
duration, err := time.ParseDuration(dt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*i = append(*i, duration)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Define a flag to accumulate durations. Because it has a special type,
|
||||
// we need to use the Var function and therefore create the flag during
|
||||
// init.
|
||||
|
||||
var intervalFlag interval
|
||||
|
||||
func init() {
|
||||
// Tie the command-line flag to the intervalFlag variable and
|
||||
// set a usage message.
|
||||
flag.Var(&intervalFlag, "deltaT", "comma-separated list of intervals to use between events")
|
||||
}
|
||||
|
||||
func Example() {
|
||||
// All the interesting pieces are with the variables declared above, but
|
||||
// to enable the flag package to see the flags defined there, one must
|
||||
// execute, typically at the start of main (not init!):
|
||||
// flag.Parse()
|
||||
// We don't run it here because this is not a main function and
|
||||
// the testing suite has already parsed the flags.
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package flag
|
||||
|
||||
import "os"
|
||||
|
||||
// Additional routines compiled into the package only during testing.
|
||||
|
||||
// ResetForTesting clears all flag state and sets the usage function as directed.
|
||||
// After calling ResetForTesting, parse errors in flag handling will not
|
||||
// exit the program.
|
||||
func ResetForTesting(usage func()) {
|
||||
commandLine = NewFlagSet(os.Args[0], ContinueOnError)
|
||||
Usage = usage
|
||||
}
|
||||
|
||||
// CommandLine returns the default FlagSet.
|
||||
func CommandLine() *FlagSet {
|
||||
return commandLine
|
||||
}
|
||||
+850
@@ -0,0 +1,850 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package flag implements command-line flag parsing.
|
||||
|
||||
Usage:
|
||||
|
||||
Define flags using flag.String(), Bool(), Int(), etc.
|
||||
|
||||
This declares an integer flag, -flagname, stored in the pointer ip, with type *int.
|
||||
import "flag"
|
||||
var ip = flag.Int("flagname", 1234, "help message for flagname")
|
||||
If you like, you can bind the flag to a variable using the Var() functions.
|
||||
var flagvar int
|
||||
func init() {
|
||||
flag.IntVar(&flagvar, "flagname", 1234, "help message for flagname")
|
||||
}
|
||||
Or you can create custom flags that satisfy the Value interface (with
|
||||
pointer receivers) and couple them to flag parsing by
|
||||
flag.Var(&flagVal, "name", "help message for flagname")
|
||||
For such flags, the default value is just the initial value of the variable.
|
||||
|
||||
After all flags are defined, call
|
||||
flag.Parse()
|
||||
to parse the command line into the defined flags.
|
||||
|
||||
Flags may then be used directly. If you're using the flags themselves,
|
||||
they are all pointers; if you bind to variables, they're values.
|
||||
fmt.Println("ip has value ", *ip)
|
||||
fmt.Println("flagvar has value ", flagvar)
|
||||
|
||||
After parsing, the arguments after the flag are available as the
|
||||
slice flag.Args() or individually as flag.Arg(i).
|
||||
The arguments are indexed from 0 up to flag.NArg().
|
||||
|
||||
Command line flag syntax:
|
||||
-flag
|
||||
-flag=x
|
||||
-flag x // non-boolean flags only
|
||||
One or two minus signs may be used; they are equivalent.
|
||||
The last form is not permitted for boolean flags because the
|
||||
meaning of the command
|
||||
cmd -x *
|
||||
will change if there is a file called 0, false, etc. You must
|
||||
use the -flag=false form to turn off a boolean flag.
|
||||
|
||||
Flag parsing stops just before the first non-flag argument
|
||||
("-" is a non-flag argument) or after the terminator "--".
|
||||
|
||||
Integer flags accept 1234, 0664, 0x1234 and may be negative.
|
||||
Boolean flags may be 1, 0, t, f, true, false, TRUE, FALSE, True, False.
|
||||
Duration flags accept any input valid for time.ParseDuration.
|
||||
|
||||
The default set of command-line flags is controlled by
|
||||
top-level functions. The FlagSet type allows one to define
|
||||
independent sets of flags, such as to implement subcommands
|
||||
in a command-line interface. The methods of FlagSet are
|
||||
analogous to the top-level functions for the command-line
|
||||
flag set.
|
||||
*/
|
||||
package flag
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ErrHelp is the error returned if the flag -help is invoked but no such flag is defined.
|
||||
var ErrHelp = errors.New("flag: help requested")
|
||||
|
||||
// -- bool Value
|
||||
type boolValue bool
|
||||
|
||||
func newBoolValue(val bool, p *bool) *boolValue {
|
||||
*p = val
|
||||
return (*boolValue)(p)
|
||||
}
|
||||
|
||||
func (b *boolValue) Set(s string) error {
|
||||
v, err := strconv.ParseBool(s)
|
||||
*b = boolValue(v)
|
||||
return err
|
||||
}
|
||||
|
||||
func (b *boolValue) Get() interface{} { return bool(*b) }
|
||||
|
||||
func (b *boolValue) String() string { return fmt.Sprintf("%v", *b) }
|
||||
|
||||
// -- int Value
|
||||
type intValue int
|
||||
|
||||
func newIntValue(val int, p *int) *intValue {
|
||||
*p = val
|
||||
return (*intValue)(p)
|
||||
}
|
||||
|
||||
func (i *intValue) Set(s string) error {
|
||||
v, err := strconv.ParseInt(s, 0, 64)
|
||||
*i = intValue(v)
|
||||
return err
|
||||
}
|
||||
|
||||
func (i *intValue) Get() interface{} { return int(*i) }
|
||||
|
||||
func (i *intValue) String() string { return fmt.Sprintf("%v", *i) }
|
||||
|
||||
// -- int64 Value
|
||||
type int64Value int64
|
||||
|
||||
func newInt64Value(val int64, p *int64) *int64Value {
|
||||
*p = val
|
||||
return (*int64Value)(p)
|
||||
}
|
||||
|
||||
func (i *int64Value) Set(s string) error {
|
||||
v, err := strconv.ParseInt(s, 0, 64)
|
||||
*i = int64Value(v)
|
||||
return err
|
||||
}
|
||||
|
||||
func (i *int64Value) Get() interface{} { return int64(*i) }
|
||||
|
||||
func (i *int64Value) String() string { return fmt.Sprintf("%v", *i) }
|
||||
|
||||
// -- uint Value
|
||||
type uintValue uint
|
||||
|
||||
func newUintValue(val uint, p *uint) *uintValue {
|
||||
*p = val
|
||||
return (*uintValue)(p)
|
||||
}
|
||||
|
||||
func (i *uintValue) Set(s string) error {
|
||||
v, err := strconv.ParseUint(s, 0, 64)
|
||||
*i = uintValue(v)
|
||||
return err
|
||||
}
|
||||
|
||||
func (i *uintValue) Get() interface{} { return uint(*i) }
|
||||
|
||||
func (i *uintValue) String() string { return fmt.Sprintf("%v", *i) }
|
||||
|
||||
// -- uint64 Value
|
||||
type uint64Value uint64
|
||||
|
||||
func newUint64Value(val uint64, p *uint64) *uint64Value {
|
||||
*p = val
|
||||
return (*uint64Value)(p)
|
||||
}
|
||||
|
||||
func (i *uint64Value) Set(s string) error {
|
||||
v, err := strconv.ParseUint(s, 0, 64)
|
||||
*i = uint64Value(v)
|
||||
return err
|
||||
}
|
||||
|
||||
func (i *uint64Value) Get() interface{} { return uint64(*i) }
|
||||
|
||||
func (i *uint64Value) String() string { return fmt.Sprintf("%v", *i) }
|
||||
|
||||
// -- string Value
|
||||
type stringValue string
|
||||
|
||||
func newStringValue(val string, p *string) *stringValue {
|
||||
*p = val
|
||||
return (*stringValue)(p)
|
||||
}
|
||||
|
||||
func (s *stringValue) Set(val string) error {
|
||||
*s = stringValue(val)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stringValue) Get() interface{} { return s.String() }
|
||||
|
||||
func (s *stringValue) String() string { return fmt.Sprintf("%s", *s) }
|
||||
|
||||
// -- float64 Value
|
||||
type float64Value float64
|
||||
|
||||
func newFloat64Value(val float64, p *float64) *float64Value {
|
||||
*p = val
|
||||
return (*float64Value)(p)
|
||||
}
|
||||
|
||||
func (f *float64Value) Set(s string) error {
|
||||
v, err := strconv.ParseFloat(s, 64)
|
||||
*f = float64Value(v)
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *float64Value) Get() interface{} { return float64(*f) }
|
||||
|
||||
func (f *float64Value) String() string { return fmt.Sprintf("%v", *f) }
|
||||
|
||||
// -- time.Duration Value
|
||||
type durationValue time.Duration
|
||||
|
||||
func newDurationValue(val time.Duration, p *time.Duration) *durationValue {
|
||||
*p = val
|
||||
return (*durationValue)(p)
|
||||
}
|
||||
|
||||
func (d *durationValue) Set(s string) error {
|
||||
v, err := time.ParseDuration(s)
|
||||
*d = durationValue(v)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *durationValue) Get() interface{} { return *(*time.Duration)(d) }
|
||||
|
||||
func (d *durationValue) String() string { return (*time.Duration)(d).String() }
|
||||
|
||||
// Value is the interface to the dynamic value stored in a flag.
|
||||
// (The default value is represented as a string.)
|
||||
type Value interface {
|
||||
String() string
|
||||
Set(string) error
|
||||
Get() interface{}
|
||||
}
|
||||
|
||||
// ErrorHandling defines how to handle flag parsing errors.
|
||||
type ErrorHandling int
|
||||
|
||||
const (
|
||||
ContinueOnError ErrorHandling = iota
|
||||
ExitOnError
|
||||
PanicOnError
|
||||
)
|
||||
|
||||
// A FlagSet represents a set of defined flags.
|
||||
type FlagSet struct {
|
||||
// Usage is the function called when an error occurs while parsing flags.
|
||||
// The field is a function (not a method) that may be changed to point to
|
||||
// a custom error handler.
|
||||
Usage func()
|
||||
|
||||
name string
|
||||
parsed bool
|
||||
actual map[string]*Flag
|
||||
formal map[string]*Flag
|
||||
args []string // arguments after flags
|
||||
exitOnError bool // does the program exit if there's an error?
|
||||
errorHandling ErrorHandling
|
||||
output io.Writer // nil means stderr; use out() accessor
|
||||
}
|
||||
|
||||
// A Flag represents the state of a flag.
|
||||
type Flag struct {
|
||||
Name string // name as it appears on command line
|
||||
Usage string // help message
|
||||
Value Value // value as set
|
||||
DefValue string // default value (as text); for usage message
|
||||
}
|
||||
|
||||
// sortFlags returns the flags as a slice in lexicographical sorted order.
|
||||
func sortFlags(flags map[string]*Flag) []*Flag {
|
||||
list := make(sort.StringSlice, len(flags))
|
||||
i := 0
|
||||
for _, f := range flags {
|
||||
list[i] = f.Name
|
||||
i++
|
||||
}
|
||||
list.Sort()
|
||||
result := make([]*Flag, len(list))
|
||||
for i, name := range list {
|
||||
result[i] = flags[name]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (f *FlagSet) out() io.Writer {
|
||||
if f.output == nil {
|
||||
return os.Stderr
|
||||
}
|
||||
return f.output
|
||||
}
|
||||
|
||||
// SetOutput sets the destination for usage and error messages.
|
||||
// If output is nil, os.Stderr is used.
|
||||
func (f *FlagSet) SetOutput(output io.Writer) {
|
||||
f.output = output
|
||||
}
|
||||
|
||||
// VisitAll visits the flags in lexicographical order, calling fn for each.
|
||||
// It visits all flags, even those not set.
|
||||
func (f *FlagSet) VisitAll(fn func(*Flag)) {
|
||||
for _, flag := range sortFlags(f.formal) {
|
||||
fn(flag)
|
||||
}
|
||||
}
|
||||
|
||||
// VisitAll visits the command-line flags in lexicographical order, calling
|
||||
// fn for each. It visits all flags, even those not set.
|
||||
func VisitAll(fn func(*Flag)) {
|
||||
commandLine.VisitAll(fn)
|
||||
}
|
||||
|
||||
// Visit visits the flags in lexicographical order, calling fn for each.
|
||||
// It visits only those flags that have been set.
|
||||
func (f *FlagSet) Visit(fn func(*Flag)) {
|
||||
for _, flag := range sortFlags(f.actual) {
|
||||
fn(flag)
|
||||
}
|
||||
}
|
||||
|
||||
// Visit visits the command-line flags in lexicographical order, calling fn
|
||||
// for each. It visits only those flags that have been set.
|
||||
func Visit(fn func(*Flag)) {
|
||||
commandLine.Visit(fn)
|
||||
}
|
||||
|
||||
// Lookup returns the Flag structure of the named flag, returning nil if none exists.
|
||||
func (f *FlagSet) Lookup(name string) *Flag {
|
||||
return f.formal[name]
|
||||
}
|
||||
|
||||
// Lookup returns the Flag structure of the named command-line flag,
|
||||
// returning nil if none exists.
|
||||
func Lookup(name string) *Flag {
|
||||
return commandLine.formal[name]
|
||||
}
|
||||
|
||||
// IsSet checks whether flag has been actually set
|
||||
func (f *FlagSet) IsSet(name string) bool {
|
||||
_, exists := f.actual[name]
|
||||
return exists
|
||||
}
|
||||
|
||||
// IsSet checks whether flag has been actually set on the command-lien
|
||||
func IsSet(name string) bool {
|
||||
return commandLine.IsSet(name)
|
||||
}
|
||||
|
||||
// Set sets the value of the named flag.
|
||||
func (f *FlagSet) Set(name, value string) error {
|
||||
flag, ok := f.formal[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("no such flag -%v", name)
|
||||
}
|
||||
err := flag.Value.Set(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if f.actual == nil {
|
||||
f.actual = make(map[string]*Flag)
|
||||
}
|
||||
f.actual[name] = flag
|
||||
return nil
|
||||
}
|
||||
|
||||
// Set sets the value of the named command-line flag.
|
||||
func Set(name, value string) error {
|
||||
return commandLine.Set(name, value)
|
||||
}
|
||||
|
||||
// PrintDefaults prints, to standard error unless configured
|
||||
// otherwise, the default values of all defined flags in the set.
|
||||
func (f *FlagSet) PrintDefaults() {
|
||||
f.VisitAll(func(flag *Flag) {
|
||||
format := " -%s=%s: %s\n"
|
||||
if _, ok := flag.Value.(*stringValue); ok {
|
||||
// put quotes on the value
|
||||
format = " -%s=%q: %s\n"
|
||||
}
|
||||
fmt.Fprintf(f.out(), format, flag.Name, flag.DefValue, flag.Usage)
|
||||
})
|
||||
}
|
||||
|
||||
// PrintDefaults prints to standard error the default values of all defined command-line flags.
|
||||
func PrintDefaults() {
|
||||
commandLine.PrintDefaults()
|
||||
}
|
||||
|
||||
// defaultUsage is the default function to print a usage message.
|
||||
func defaultUsage(f *FlagSet) {
|
||||
fmt.Fprintf(f.out(), "Usage of %s:\n", f.name)
|
||||
f.PrintDefaults()
|
||||
}
|
||||
|
||||
// NOTE: Usage is not just defaultUsage(commandLine)
|
||||
// because it serves (via godoc flag Usage) as the example
|
||||
// for how to write your own usage function.
|
||||
|
||||
// Usage prints to standard error a usage message documenting all defined command-line flags.
|
||||
// The function is a variable that may be changed to point to a custom function.
|
||||
var Usage = func() {
|
||||
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
|
||||
PrintDefaults()
|
||||
}
|
||||
|
||||
// NFlag returns the number of flags that have been set.
|
||||
func (f *FlagSet) NFlag() int { return len(f.actual) }
|
||||
|
||||
// NFlag returns the number of command-line flags that have been set.
|
||||
func NFlag() int { return len(commandLine.actual) }
|
||||
|
||||
// Arg returns the i'th argument. Arg(0) is the first remaining argument
|
||||
// after flags have been processed.
|
||||
func (f *FlagSet) Arg(i int) string {
|
||||
if i < 0 || i >= len(f.args) {
|
||||
return ""
|
||||
}
|
||||
return f.args[i]
|
||||
}
|
||||
|
||||
// Arg returns the i'th command-line argument. Arg(0) is the first remaining argument
|
||||
// after flags have been processed.
|
||||
func Arg(i int) string {
|
||||
return commandLine.Arg(i)
|
||||
}
|
||||
|
||||
// NArg is the number of arguments remaining after flags have been processed.
|
||||
func (f *FlagSet) NArg() int { return len(f.args) }
|
||||
|
||||
// NArg is the number of arguments remaining after flags have been processed.
|
||||
func NArg() int { return len(commandLine.args) }
|
||||
|
||||
// Args returns the non-flag arguments.
|
||||
func (f *FlagSet) Args() []string { return f.args }
|
||||
|
||||
// Args returns the non-flag command-line arguments.
|
||||
func Args() []string { return commandLine.args }
|
||||
|
||||
// BoolVar defines a bool flag with specified name, default value, and usage string.
|
||||
// The argument p points to a bool variable in which to store the value of the flag.
|
||||
func (f *FlagSet) BoolVar(p *bool, name string, value bool, usage string) {
|
||||
f.Var(newBoolValue(value, p), name, usage)
|
||||
}
|
||||
|
||||
// BoolVar defines a bool flag with specified name, default value, and usage string.
|
||||
// The argument p points to a bool variable in which to store the value of the flag.
|
||||
func BoolVar(p *bool, name string, value bool, usage string) {
|
||||
commandLine.Var(newBoolValue(value, p), name, usage)
|
||||
}
|
||||
|
||||
// Bool defines a bool flag with specified name, default value, and usage string.
|
||||
// The return value is the address of a bool variable that stores the value of the flag.
|
||||
func (f *FlagSet) Bool(name string, value bool, usage string) *bool {
|
||||
p := new(bool)
|
||||
f.BoolVar(p, name, value, usage)
|
||||
return p
|
||||
}
|
||||
|
||||
// Bool defines a bool flag with specified name, default value, and usage string.
|
||||
// The return value is the address of a bool variable that stores the value of the flag.
|
||||
func Bool(name string, value bool, usage string) *bool {
|
||||
return commandLine.Bool(name, value, usage)
|
||||
}
|
||||
|
||||
// IntVar defines an int flag with specified name, default value, and usage string.
|
||||
// The argument p points to an int variable in which to store the value of the flag.
|
||||
func (f *FlagSet) IntVar(p *int, name string, value int, usage string) {
|
||||
f.Var(newIntValue(value, p), name, usage)
|
||||
}
|
||||
|
||||
// IntVar defines an int flag with specified name, default value, and usage string.
|
||||
// The argument p points to an int variable in which to store the value of the flag.
|
||||
func IntVar(p *int, name string, value int, usage string) {
|
||||
commandLine.Var(newIntValue(value, p), name, usage)
|
||||
}
|
||||
|
||||
// Int defines an int flag with specified name, default value, and usage string.
|
||||
// The return value is the address of an int variable that stores the value of the flag.
|
||||
func (f *FlagSet) Int(name string, value int, usage string) *int {
|
||||
p := new(int)
|
||||
f.IntVar(p, name, value, usage)
|
||||
return p
|
||||
}
|
||||
|
||||
// Int defines an int flag with specified name, default value, and usage string.
|
||||
// The return value is the address of an int variable that stores the value of the flag.
|
||||
func Int(name string, value int, usage string) *int {
|
||||
return commandLine.Int(name, value, usage)
|
||||
}
|
||||
|
||||
// Int64Var defines an int64 flag with specified name, default value, and usage string.
|
||||
// The argument p points to an int64 variable in which to store the value of the flag.
|
||||
func (f *FlagSet) Int64Var(p *int64, name string, value int64, usage string) {
|
||||
f.Var(newInt64Value(value, p), name, usage)
|
||||
}
|
||||
|
||||
// Int64Var defines an int64 flag with specified name, default value, and usage string.
|
||||
// The argument p points to an int64 variable in which to store the value of the flag.
|
||||
func Int64Var(p *int64, name string, value int64, usage string) {
|
||||
commandLine.Var(newInt64Value(value, p), name, usage)
|
||||
}
|
||||
|
||||
// Int64 defines an int64 flag with specified name, default value, and usage string.
|
||||
// The return value is the address of an int64 variable that stores the value of the flag.
|
||||
func (f *FlagSet) Int64(name string, value int64, usage string) *int64 {
|
||||
p := new(int64)
|
||||
f.Int64Var(p, name, value, usage)
|
||||
return p
|
||||
}
|
||||
|
||||
// Int64 defines an int64 flag with specified name, default value, and usage string.
|
||||
// The return value is the address of an int64 variable that stores the value of the flag.
|
||||
func Int64(name string, value int64, usage string) *int64 {
|
||||
return commandLine.Int64(name, value, usage)
|
||||
}
|
||||
|
||||
// UintVar defines a uint flag with specified name, default value, and usage string.
|
||||
// The argument p points to a uint variable in which to store the value of the flag.
|
||||
func (f *FlagSet) UintVar(p *uint, name string, value uint, usage string) {
|
||||
f.Var(newUintValue(value, p), name, usage)
|
||||
}
|
||||
|
||||
// UintVar defines a uint flag with specified name, default value, and usage string.
|
||||
// The argument p points to a uint variable in which to store the value of the flag.
|
||||
func UintVar(p *uint, name string, value uint, usage string) {
|
||||
commandLine.Var(newUintValue(value, p), name, usage)
|
||||
}
|
||||
|
||||
// Uint defines a uint flag with specified name, default value, and usage string.
|
||||
// The return value is the address of a uint variable that stores the value of the flag.
|
||||
func (f *FlagSet) Uint(name string, value uint, usage string) *uint {
|
||||
p := new(uint)
|
||||
f.UintVar(p, name, value, usage)
|
||||
return p
|
||||
}
|
||||
|
||||
// Uint defines a uint flag with specified name, default value, and usage string.
|
||||
// The return value is the address of a uint variable that stores the value of the flag.
|
||||
func Uint(name string, value uint, usage string) *uint {
|
||||
return commandLine.Uint(name, value, usage)
|
||||
}
|
||||
|
||||
// Uint64Var defines a uint64 flag with specified name, default value, and usage string.
|
||||
// The argument p points to a uint64 variable in which to store the value of the flag.
|
||||
func (f *FlagSet) Uint64Var(p *uint64, name string, value uint64, usage string) {
|
||||
f.Var(newUint64Value(value, p), name, usage)
|
||||
}
|
||||
|
||||
// Uint64Var defines a uint64 flag with specified name, default value, and usage string.
|
||||
// The argument p points to a uint64 variable in which to store the value of the flag.
|
||||
func Uint64Var(p *uint64, name string, value uint64, usage string) {
|
||||
commandLine.Var(newUint64Value(value, p), name, usage)
|
||||
}
|
||||
|
||||
// Uint64 defines a uint64 flag with specified name, default value, and usage string.
|
||||
// The return value is the address of a uint64 variable that stores the value of the flag.
|
||||
func (f *FlagSet) Uint64(name string, value uint64, usage string) *uint64 {
|
||||
p := new(uint64)
|
||||
f.Uint64Var(p, name, value, usage)
|
||||
return p
|
||||
}
|
||||
|
||||
// Uint64 defines a uint64 flag with specified name, default value, and usage string.
|
||||
// The return value is the address of a uint64 variable that stores the value of the flag.
|
||||
func Uint64(name string, value uint64, usage string) *uint64 {
|
||||
return commandLine.Uint64(name, value, usage)
|
||||
}
|
||||
|
||||
// StringVar defines a string flag with specified name, default value, and usage string.
|
||||
// The argument p points to a string variable in which to store the value of the flag.
|
||||
func (f *FlagSet) StringVar(p *string, name string, value string, usage string) {
|
||||
f.Var(newStringValue(value, p), name, usage)
|
||||
}
|
||||
|
||||
// StringVar defines a string flag with specified name, default value, and usage string.
|
||||
// The argument p points to a string variable in which to store the value of the flag.
|
||||
func StringVar(p *string, name string, value string, usage string) {
|
||||
commandLine.Var(newStringValue(value, p), name, usage)
|
||||
}
|
||||
|
||||
// String defines a string flag with specified name, default value, and usage string.
|
||||
// The return value is the address of a string variable that stores the value of the flag.
|
||||
func (f *FlagSet) String(name string, value string, usage string) *string {
|
||||
p := new(string)
|
||||
f.StringVar(p, name, value, usage)
|
||||
return p
|
||||
}
|
||||
|
||||
// String defines a string flag with specified name, default value, and usage string.
|
||||
// The return value is the address of a string variable that stores the value of the flag.
|
||||
func String(name string, value string, usage string) *string {
|
||||
return commandLine.String(name, value, usage)
|
||||
}
|
||||
|
||||
// Float64Var defines a float64 flag with specified name, default value, and usage string.
|
||||
// The argument p points to a float64 variable in which to store the value of the flag.
|
||||
func (f *FlagSet) Float64Var(p *float64, name string, value float64, usage string) {
|
||||
f.Var(newFloat64Value(value, p), name, usage)
|
||||
}
|
||||
|
||||
// Float64Var defines a float64 flag with specified name, default value, and usage string.
|
||||
// The argument p points to a float64 variable in which to store the value of the flag.
|
||||
func Float64Var(p *float64, name string, value float64, usage string) {
|
||||
commandLine.Var(newFloat64Value(value, p), name, usage)
|
||||
}
|
||||
|
||||
// Float64 defines a float64 flag with specified name, default value, and usage string.
|
||||
// The return value is the address of a float64 variable that stores the value of the flag.
|
||||
func (f *FlagSet) Float64(name string, value float64, usage string) *float64 {
|
||||
p := new(float64)
|
||||
f.Float64Var(p, name, value, usage)
|
||||
return p
|
||||
}
|
||||
|
||||
// Float64 defines a float64 flag with specified name, default value, and usage string.
|
||||
// The return value is the address of a float64 variable that stores the value of the flag.
|
||||
func Float64(name string, value float64, usage string) *float64 {
|
||||
return commandLine.Float64(name, value, usage)
|
||||
}
|
||||
|
||||
// DurationVar defines a time.Duration flag with specified name, default value, and usage string.
|
||||
// The argument p points to a time.Duration variable in which to store the value of the flag.
|
||||
func (f *FlagSet) DurationVar(p *time.Duration, name string, value time.Duration, usage string) {
|
||||
f.Var(newDurationValue(value, p), name, usage)
|
||||
}
|
||||
|
||||
// DurationVar defines a time.Duration flag with specified name, default value, and usage string.
|
||||
// The argument p points to a time.Duration variable in which to store the value of the flag.
|
||||
func DurationVar(p *time.Duration, name string, value time.Duration, usage string) {
|
||||
commandLine.Var(newDurationValue(value, p), name, usage)
|
||||
}
|
||||
|
||||
// Duration defines a time.Duration flag with specified name, default value, and usage string.
|
||||
// The return value is the address of a time.Duration variable that stores the value of the flag.
|
||||
func (f *FlagSet) Duration(name string, value time.Duration, usage string) *time.Duration {
|
||||
p := new(time.Duration)
|
||||
f.DurationVar(p, name, value, usage)
|
||||
return p
|
||||
}
|
||||
|
||||
// Duration defines a time.Duration flag with specified name, default value, and usage string.
|
||||
// The return value is the address of a time.Duration variable that stores the value of the flag.
|
||||
func Duration(name string, value time.Duration, usage string) *time.Duration {
|
||||
return commandLine.Duration(name, value, usage)
|
||||
}
|
||||
|
||||
// Var defines a flag with the specified name and usage string. The type and
|
||||
// value of the flag are represented by the first argument, of type Value, which
|
||||
// typically holds a user-defined implementation of Value. For instance, the
|
||||
// caller could create a flag that turns a comma-separated string into a slice
|
||||
// of strings by giving the slice the methods of Value; in particular, Set would
|
||||
// decompose the comma-separated string into the slice.
|
||||
func (f *FlagSet) Var(value Value, name string, usage string) {
|
||||
// Remember the default value as a string; it won't change.
|
||||
flag := &Flag{name, usage, value, value.String()}
|
||||
_, alreadythere := f.formal[name]
|
||||
if alreadythere {
|
||||
msg := fmt.Sprintf("%s flag redefined: %s", f.name, name)
|
||||
fmt.Fprintln(f.out(), msg)
|
||||
panic(msg) // Happens only if flags are declared with identical names
|
||||
}
|
||||
if f.formal == nil {
|
||||
f.formal = make(map[string]*Flag)
|
||||
}
|
||||
f.formal[name] = flag
|
||||
}
|
||||
|
||||
// Var defines a flag with the specified name and usage string. The type and
|
||||
// value of the flag are represented by the first argument, of type Value, which
|
||||
// typically holds a user-defined implementation of Value. For instance, the
|
||||
// caller could create a flag that turns a comma-separated string into a slice
|
||||
// of strings by giving the slice the methods of Value; in particular, Set would
|
||||
// decompose the comma-separated string into the slice.
|
||||
func Var(value Value, name string, usage string) {
|
||||
commandLine.Var(value, name, usage)
|
||||
}
|
||||
|
||||
// failf prints to standard error a formatted error and usage message and
|
||||
// returns the error.
|
||||
func (f *FlagSet) failf(format string, a ...interface{}) error {
|
||||
err := fmt.Errorf(format, a...)
|
||||
fmt.Fprintln(f.out(), err)
|
||||
f.usage()
|
||||
return err
|
||||
}
|
||||
|
||||
// usage calls the Usage method for the flag set, or the usage function if
|
||||
// the flag set is commandLine.
|
||||
func (f *FlagSet) usage() {
|
||||
if f == commandLine {
|
||||
Usage()
|
||||
} else if f.Usage == nil {
|
||||
defaultUsage(f)
|
||||
} else {
|
||||
f.Usage()
|
||||
}
|
||||
}
|
||||
|
||||
// parseOne parses one flag. It returns whether a flag was seen.
|
||||
func (f *FlagSet) parseOne(setValue bool) (bool, error) {
|
||||
if len(f.args) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
s := f.args[0]
|
||||
if len(s) == 0 || s[0] != '-' || len(s) == 1 {
|
||||
return false, nil
|
||||
}
|
||||
num_minuses := 1
|
||||
if s[1] == '-' {
|
||||
num_minuses++
|
||||
if len(s) == 2 { // "--" terminates the flags
|
||||
f.args = f.args[1:]
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
name := s[num_minuses:]
|
||||
if len(name) == 0 || name[0] == '-' || name[0] == '=' {
|
||||
return false, f.failf("bad flag syntax: %s", s)
|
||||
}
|
||||
|
||||
// it's a flag. does it have an argument?
|
||||
f.args = f.args[1:]
|
||||
has_value := false
|
||||
value := ""
|
||||
for i := 1; i < len(name); i++ { // equals cannot be first
|
||||
if name[i] == '=' {
|
||||
value = name[i+1:]
|
||||
has_value = true
|
||||
name = name[0:i]
|
||||
break
|
||||
}
|
||||
}
|
||||
m := f.formal
|
||||
flag, alreadythere := m[name] // BUG
|
||||
if !alreadythere {
|
||||
if name == "help" || name == "h" { // special case for nice help message.
|
||||
f.usage()
|
||||
return false, ErrHelp
|
||||
}
|
||||
return false, f.failf("flag provided but not defined: -%s", name)
|
||||
}
|
||||
if fv, ok := flag.Value.(*boolValue); ok { // special case: doesn't need an arg
|
||||
if setValue {
|
||||
if has_value {
|
||||
if err := fv.Set(value); err != nil {
|
||||
f.failf("invalid boolean value %q for -%s: %v", value, name, err)
|
||||
}
|
||||
} else {
|
||||
fv.Set("true")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// It must have a value, which might be the next argument.
|
||||
if !has_value && len(f.args) > 0 {
|
||||
// value is the next arg
|
||||
has_value = true
|
||||
value, f.args = f.args[0], f.args[1:]
|
||||
}
|
||||
if !has_value {
|
||||
return false, f.failf("flag needs an argument: -%s", name)
|
||||
}
|
||||
if setValue {
|
||||
if err := flag.Value.Set(value); err != nil {
|
||||
return false, f.failf("invalid value %q for flag -%s: %v", value, name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if f.actual == nil {
|
||||
f.actual = make(map[string]*Flag)
|
||||
}
|
||||
f.actual[name] = flag
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Parse parses flag definitions from the argument list, which should not
|
||||
// include the command name. Must be called after all flags in the FlagSet
|
||||
// are defined and before flags are accessed by the program.
|
||||
// The return value will be ErrHelp if -help was set but not defined.
|
||||
func (f *FlagSet) Parse(arguments []string, setValue bool) error {
|
||||
f.parsed = true
|
||||
f.args = arguments
|
||||
for {
|
||||
seen, err := f.parseOne(setValue)
|
||||
if seen {
|
||||
continue
|
||||
}
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
switch f.errorHandling {
|
||||
case ContinueOnError:
|
||||
return err
|
||||
case ExitOnError:
|
||||
os.Exit(2)
|
||||
case PanicOnError:
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parsed reports whether f.Parse has been called.
|
||||
func (f *FlagSet) Parsed() bool {
|
||||
return f.parsed
|
||||
}
|
||||
|
||||
// Parse parses the command-line flags from os.Args[1:]. Must be called
|
||||
// after all flags are defined and before flags are accessed by the program.
|
||||
func Parse() {
|
||||
// Ignore errors; commandLine is set for ExitOnError.
|
||||
commandLine.Parse(os.Args[1:], true)
|
||||
}
|
||||
|
||||
// Parsed returns true if the command-line flags have been parsed.
|
||||
func Parsed() bool {
|
||||
return commandLine.Parsed()
|
||||
}
|
||||
|
||||
// The default set of command-line flags, parsed from os.Args.
|
||||
var commandLine = NewFlagSet(os.Args[0], ExitOnError)
|
||||
|
||||
// NewFlagSet returns a new, empty flag set with the specified name and
|
||||
// error handling property.
|
||||
func NewFlagSet(name string, errorHandling ErrorHandling) *FlagSet {
|
||||
f := &FlagSet{
|
||||
name: name,
|
||||
errorHandling: errorHandling,
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
// Init sets the name and error handling property for a flag set.
|
||||
// By default, the zero FlagSet uses an empty name and the
|
||||
// ContinueOnError error handling policy.
|
||||
func (f *FlagSet) Init(name string, errorHandling ErrorHandling) {
|
||||
f.name = name
|
||||
f.errorHandling = errorHandling
|
||||
}
|
||||
|
||||
// Merge combines two FlagSets, adding all flags from other
|
||||
func (f *FlagSet) Merge(other *FlagSet) {
|
||||
for name, flag := range other.formal {
|
||||
existing, alreadythere := f.formal[name]
|
||||
if alreadythere {
|
||||
if reflect.TypeOf(existing.Value) != reflect.TypeOf(flag.Value) {
|
||||
panic(fmt.Sprintf("unable to merge flag %s: %s != %s", name, reflect.TypeOf(existing.Value).String(),
|
||||
reflect.TypeOf(flag.Value).String()))
|
||||
}
|
||||
} else {
|
||||
if f.formal == nil {
|
||||
f.formal = make(map[string]*Flag)
|
||||
}
|
||||
f.formal[name] = flag
|
||||
}
|
||||
}
|
||||
}
|
||||
+288
@@ -0,0 +1,288 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package flag_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/smira/flag"
|
||||
)
|
||||
|
||||
var (
|
||||
test_bool = Bool("test_bool", false, "bool value")
|
||||
test_int = Int("test_int", 0, "int value")
|
||||
test_int64 = Int64("test_int64", 0, "int64 value")
|
||||
test_uint = Uint("test_uint", 0, "uint value")
|
||||
test_uint64 = Uint64("test_uint64", 0, "uint64 value")
|
||||
test_string = String("test_string", "0", "string value")
|
||||
test_float64 = Float64("test_float64", 0, "float64 value")
|
||||
test_duration = Duration("test_duration", 0, "time.Duration value")
|
||||
)
|
||||
|
||||
func boolString(s string) string {
|
||||
if s == "0" {
|
||||
return "false"
|
||||
}
|
||||
return "true"
|
||||
}
|
||||
|
||||
func TestEverything(t *testing.T) {
|
||||
m := make(map[string]*Flag)
|
||||
desired := "0"
|
||||
visitor := func(f *Flag) {
|
||||
if len(f.Name) > 5 && f.Name[0:5] == "test_" {
|
||||
m[f.Name] = f
|
||||
ok := false
|
||||
switch {
|
||||
case f.Value.String() == desired:
|
||||
ok = true
|
||||
case f.Name == "test_bool" && f.Value.String() == boolString(desired):
|
||||
ok = true
|
||||
case f.Name == "test_duration" && f.Value.String() == desired+"s":
|
||||
ok = true
|
||||
}
|
||||
if !ok {
|
||||
t.Error("Visit: bad value", f.Value.String(), "for", f.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
VisitAll(visitor)
|
||||
if len(m) != 8 {
|
||||
t.Error("VisitAll misses some flags")
|
||||
for k, v := range m {
|
||||
t.Log(k, *v)
|
||||
}
|
||||
}
|
||||
m = make(map[string]*Flag)
|
||||
Visit(visitor)
|
||||
if len(m) != 0 {
|
||||
t.Errorf("Visit sees unset flags")
|
||||
for k, v := range m {
|
||||
t.Log(k, *v)
|
||||
}
|
||||
}
|
||||
// Now set all flags
|
||||
Set("test_bool", "true")
|
||||
Set("test_int", "1")
|
||||
Set("test_int64", "1")
|
||||
Set("test_uint", "1")
|
||||
Set("test_uint64", "1")
|
||||
Set("test_string", "1")
|
||||
Set("test_float64", "1")
|
||||
Set("test_duration", "1s")
|
||||
desired = "1"
|
||||
Visit(visitor)
|
||||
if len(m) != 8 {
|
||||
t.Error("Visit fails after set")
|
||||
for k, v := range m {
|
||||
t.Log(k, *v)
|
||||
}
|
||||
}
|
||||
// Now test they're visited in sort order.
|
||||
var flagNames []string
|
||||
Visit(func(f *Flag) { flagNames = append(flagNames, f.Name) })
|
||||
if !sort.StringsAreSorted(flagNames) {
|
||||
t.Errorf("flag names not sorted: %v", flagNames)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUsage(t *testing.T) {
|
||||
called := false
|
||||
ResetForTesting(func() { called = true })
|
||||
if CommandLine().Parse([]string{"-x"}, true) == nil {
|
||||
t.Error("parse did not fail for unknown flag")
|
||||
}
|
||||
if !called {
|
||||
t.Error("did not call Usage for unknown flag")
|
||||
}
|
||||
}
|
||||
|
||||
func testParse(f *FlagSet, t *testing.T) {
|
||||
if f.Parsed() {
|
||||
t.Error("f.Parse() = true before Parse")
|
||||
}
|
||||
boolFlag := f.Bool("bool", false, "bool value")
|
||||
bool2Flag := f.Bool("bool2", false, "bool2 value")
|
||||
intFlag := f.Int("int", 0, "int value")
|
||||
int64Flag := f.Int64("int64", 0, "int64 value")
|
||||
uintFlag := f.Uint("uint", 0, "uint value")
|
||||
uint64Flag := f.Uint64("uint64", 0, "uint64 value")
|
||||
stringFlag := f.String("string", "0", "string value")
|
||||
float64Flag := f.Float64("float64", 0, "float64 value")
|
||||
durationFlag := f.Duration("duration", 5*time.Second, "time.Duration value")
|
||||
extra := "one-extra-argument"
|
||||
args := []string{
|
||||
"-bool",
|
||||
"-bool2=true",
|
||||
"--int", "22",
|
||||
"--int64", "0x23",
|
||||
"-uint", "24",
|
||||
"--uint64", "25",
|
||||
"-string", "hello",
|
||||
"-float64", "2718e28",
|
||||
"-duration", "2m",
|
||||
extra,
|
||||
}
|
||||
if err := f.Parse(args, true); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !f.Parsed() {
|
||||
t.Error("f.Parse() = false after Parse")
|
||||
}
|
||||
if *boolFlag != true {
|
||||
t.Error("bool flag should be true, is ", *boolFlag)
|
||||
}
|
||||
if *bool2Flag != true {
|
||||
t.Error("bool2 flag should be true, is ", *bool2Flag)
|
||||
}
|
||||
if *intFlag != 22 {
|
||||
t.Error("int flag should be 22, is ", *intFlag)
|
||||
}
|
||||
if *int64Flag != 0x23 {
|
||||
t.Error("int64 flag should be 0x23, is ", *int64Flag)
|
||||
}
|
||||
if *uintFlag != 24 {
|
||||
t.Error("uint flag should be 24, is ", *uintFlag)
|
||||
}
|
||||
if *uint64Flag != 25 {
|
||||
t.Error("uint64 flag should be 25, is ", *uint64Flag)
|
||||
}
|
||||
if *stringFlag != "hello" {
|
||||
t.Error("string flag should be `hello`, is ", *stringFlag)
|
||||
}
|
||||
if *float64Flag != 2718e28 {
|
||||
t.Error("float64 flag should be 2718e28, is ", *float64Flag)
|
||||
}
|
||||
if *durationFlag != 2*time.Minute {
|
||||
t.Error("duration flag should be 2m, is ", *durationFlag)
|
||||
}
|
||||
if len(f.Args()) != 1 {
|
||||
t.Error("expected one argument, got", len(f.Args()))
|
||||
} else if f.Args()[0] != extra {
|
||||
t.Errorf("expected argument %q got %q", extra, f.Args()[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
ResetForTesting(func() { t.Error("bad parse") })
|
||||
testParse(CommandLine(), t)
|
||||
}
|
||||
|
||||
func TestFlagSetParse(t *testing.T) {
|
||||
testParse(NewFlagSet("test", ContinueOnError), t)
|
||||
}
|
||||
|
||||
// Declare a user-defined flag type.
|
||||
type flagVar []string
|
||||
|
||||
func (f *flagVar) String() string {
|
||||
return fmt.Sprint([]string(*f))
|
||||
}
|
||||
|
||||
func (f *flagVar) Set(value string) error {
|
||||
*f = append(*f, value)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *flagVar) Get() interface{} { return []string(*f) }
|
||||
|
||||
func TestUserDefined(t *testing.T) {
|
||||
var flags FlagSet
|
||||
flags.Init("test", ContinueOnError)
|
||||
var v flagVar
|
||||
flags.Var(&v, "v", "usage")
|
||||
if err := flags.Parse([]string{"-v", "1", "-v", "2", "-v=3"}, true); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if len(v) != 3 {
|
||||
t.Fatal("expected 3 args; got ", len(v))
|
||||
}
|
||||
expect := "[1 2 3]"
|
||||
if v.String() != expect {
|
||||
t.Errorf("expected value %q got %q", expect, v.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetOutput(t *testing.T) {
|
||||
var flags FlagSet
|
||||
var buf bytes.Buffer
|
||||
flags.SetOutput(&buf)
|
||||
flags.Init("test", ContinueOnError)
|
||||
flags.Parse([]string{"-unknown"}, true)
|
||||
if out := buf.String(); !strings.Contains(out, "-unknown") {
|
||||
t.Logf("expected output mentioning unknown; got %q", out)
|
||||
}
|
||||
}
|
||||
|
||||
// This tests that one can reset the flags. This still works but not well, and is
|
||||
// superseded by FlagSet.
|
||||
func TestChangingArgs(t *testing.T) {
|
||||
ResetForTesting(func() { t.Fatal("bad parse") })
|
||||
oldArgs := os.Args
|
||||
defer func() { os.Args = oldArgs }()
|
||||
os.Args = []string{"cmd", "-before", "subcmd", "-after", "args"}
|
||||
before := Bool("before", false, "")
|
||||
if err := CommandLine().Parse(os.Args[1:], true); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cmd := Arg(0)
|
||||
os.Args = Args()
|
||||
after := Bool("after", false, "")
|
||||
Parse()
|
||||
args := Args()
|
||||
|
||||
if !*before || cmd != "subcmd" || !*after || len(args) != 1 || args[0] != "args" {
|
||||
t.Fatalf("expected true subcmd true [args] got %v %v %v %v", *before, cmd, *after, args)
|
||||
}
|
||||
}
|
||||
|
||||
// Test that -help invokes the usage message and returns ErrHelp.
|
||||
func TestHelp(t *testing.T) {
|
||||
var helpCalled = false
|
||||
fs := NewFlagSet("help test", ContinueOnError)
|
||||
fs.Usage = func() { helpCalled = true }
|
||||
var flag bool
|
||||
fs.BoolVar(&flag, "flag", false, "regular flag")
|
||||
// Regular flag invocation should work
|
||||
err := fs.Parse([]string{"-flag=true"}, true)
|
||||
if err != nil {
|
||||
t.Fatal("expected no error; got ", err)
|
||||
}
|
||||
if !flag {
|
||||
t.Error("flag was not set by -flag")
|
||||
}
|
||||
if helpCalled {
|
||||
t.Error("help called for regular flag")
|
||||
helpCalled = false // reset for next test
|
||||
}
|
||||
// Help flag should work as expected.
|
||||
err = fs.Parse([]string{"-help"}, true)
|
||||
if err == nil {
|
||||
t.Fatal("error expected")
|
||||
}
|
||||
if err != ErrHelp {
|
||||
t.Fatal("expected ErrHelp; got ", err)
|
||||
}
|
||||
if !helpCalled {
|
||||
t.Fatal("help was not called")
|
||||
}
|
||||
// If we define a help flag, that should override.
|
||||
var help bool
|
||||
fs.BoolVar(&help, "help", false, "help flag")
|
||||
helpCalled = false
|
||||
err = fs.Parse([]string{"-help"}, true)
|
||||
if err != nil {
|
||||
t.Fatal("expected no error for defined -help; got ", err)
|
||||
}
|
||||
if helpCalled {
|
||||
t.Fatal("help was called; should not have been for defined help flag")
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 SmartyStreets
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
go-aws-auth
|
||||
===========
|
||||
|
||||
[](http://godoc.org/github.com/smartystreets/go-aws-auth)
|
||||
|
||||
Go-AWS-Auth is a comprehensive, lightweight library for signing requests to Amazon Web Services.
|
||||
|
||||
It's easy to use: simply build your HTTP request and call `awsauth.Sign(req)` before sending your request over the wire.
|
||||
|
||||
|
||||
|
||||
### Supported signing mechanisms
|
||||
|
||||
- [Signed Signature Version 2](http://docs.aws.amazon.com/general/latest/gr/signature-version-2.html)
|
||||
- [Signed Signature Version 3](http://docs.aws.amazon.com/general/latest/gr/signing_aws_api_requests.html)
|
||||
- [Signed Signature Version 4](http://docs.aws.amazon.com/general/latest/gr/signature-version-4.html)
|
||||
- [Custom S3 Authentication Scheme](http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html)
|
||||
- [Security Token Service](http://docs.aws.amazon.com/STS/latest/APIReference/Welcome.html)
|
||||
- [S3 Query String Authentication](http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html#RESTAuthenticationQueryStringAuth)
|
||||
- [IAM Role](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html#instance-metadata-security-credentials)
|
||||
|
||||
For more info about AWS authentication, see the [comprehensive docs](http://docs.aws.amazon.com/general/latest/gr/signing_aws_api_requests.html) at AWS.
|
||||
|
||||
|
||||
### Install
|
||||
|
||||
Go get it:
|
||||
|
||||
$ go get github.com/smartystreets/go-aws-auth
|
||||
|
||||
Then import it:
|
||||
|
||||
import "github.com/smartystreets/go-aws-auth"
|
||||
|
||||
|
||||
### Using your AWS Credentials
|
||||
|
||||
The library looks for credentials in this order:
|
||||
|
||||
1. **Hard-code:** You can manually pass in an instance of `awsauth.Credentials` to any call to a signing function as a second argument:
|
||||
|
||||
```go
|
||||
awsauth.Sign(req, awsauth.Credentials{
|
||||
AccessKeyID: "Access Key ID",
|
||||
SecretAccessKey: "Secret Access Key",
|
||||
SecurityToken: "Security Token", // STS (optional)
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
2. **Environment variables:** Set the `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` environment variables with your credentials. The library will automatically detect and use them. Optionally, you may also set the `AWS_SECURITY_TOKEN` environment variable if you are using temporary credentials from [STS](http://docs.aws.amazon.com/STS/latest/APIReference/Welcome.html).
|
||||
|
||||
3. **IAM Role:** If running on EC2 and the credentials are neither hard-coded nor in the environment, go-aws-auth will detect the first IAM role assigned to the current EC2 instance and use those credentials.
|
||||
|
||||
(Be especially careful hard-coding credentials into your application if the code is committed to source control.)
|
||||
|
||||
|
||||
|
||||
### Signing requests
|
||||
|
||||
Just make the request, have it signed, and perform the request as you normally would.
|
||||
|
||||
```go
|
||||
url := "https://iam.amazonaws.com/?Action=ListRoles&Version=2010-05-08"
|
||||
client := new(http.Client)
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
|
||||
awsauth.Sign(req) // Automatically chooses the best signing mechanism for the service
|
||||
|
||||
resp, err := client.Do(req)
|
||||
```
|
||||
|
||||
You can use `Sign` to have the library choose the best signing algorithm depending on the service, or you can specify it manually if you know what you need:
|
||||
|
||||
- `Sign2`
|
||||
- `Sign3`
|
||||
- `Sign4`
|
||||
- `SignS3` (deprecated for Sign4)
|
||||
- `SignS3Url` (for pre-signed S3 URLs; GETs only)
|
||||
|
||||
|
||||
|
||||
### Contributing
|
||||
|
||||
Please feel free to contribute! Bug fixes are more than welcome any time, as long as tests assert correct behavior. If you'd like to change an existing implementation or see a new feature, open an issue first so we can discuss it. Thanks to all contributors!
|
||||
+219
@@ -0,0 +1,219 @@
|
||||
// Package awsauth implements AWS request signing using Signed Signature Version 2,
|
||||
// Signed Signature Version 3, and Signed Signature Version 4. Supports S3 and STS.
|
||||
package awsauth
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Credentials stores the information necessary to authorize with AWS and it
|
||||
// is from this information that requests are signed.
|
||||
type Credentials struct {
|
||||
AccessKeyID string
|
||||
SecretAccessKey string
|
||||
SecurityToken string `json:"Token"`
|
||||
Expiration time.Time
|
||||
}
|
||||
|
||||
// Sign signs a request bound for AWS. It automatically chooses the best
|
||||
// authentication scheme based on the service the request is going to.
|
||||
func Sign(request *http.Request, credentials ...Credentials) *http.Request {
|
||||
service, _ := serviceAndRegion(request.URL.Host)
|
||||
signVersion := awsSignVersion[service]
|
||||
|
||||
switch signVersion {
|
||||
case 2:
|
||||
return Sign2(request, credentials...)
|
||||
case 3:
|
||||
return Sign3(request, credentials...)
|
||||
case 4:
|
||||
return Sign4(request, credentials...)
|
||||
case -1:
|
||||
return SignS3(request, credentials...)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sign4 signs a request with Signed Signature Version 4.
|
||||
func Sign4(request *http.Request, credentials ...Credentials) *http.Request {
|
||||
keys := chooseKeys(credentials)
|
||||
|
||||
// Add the X-Amz-Security-Token header when using STS
|
||||
if keys.SecurityToken != "" {
|
||||
request.Header.Set("X-Amz-Security-Token", keys.SecurityToken)
|
||||
}
|
||||
|
||||
prepareRequestV4(request)
|
||||
meta := new(metadata)
|
||||
|
||||
// Task 1
|
||||
hashedCanonReq := hashedCanonicalRequestV4(request, meta)
|
||||
|
||||
// Task 2
|
||||
stringToSign := stringToSignV4(request, hashedCanonReq, meta)
|
||||
|
||||
// Task 3
|
||||
signingKey := signingKeyV4(keys.SecretAccessKey, meta.date, meta.region, meta.service)
|
||||
signature := signatureV4(signingKey, stringToSign)
|
||||
|
||||
request.Header.Set("Authorization", buildAuthHeaderV4(signature, meta, keys))
|
||||
|
||||
return request
|
||||
}
|
||||
|
||||
// Sign3 signs a request with Signed Signature Version 3.
|
||||
// If the service you're accessing supports Version 4, use that instead.
|
||||
func Sign3(request *http.Request, credentials ...Credentials) *http.Request {
|
||||
keys := chooseKeys(credentials)
|
||||
|
||||
// Add the X-Amz-Security-Token header when using STS
|
||||
if keys.SecurityToken != "" {
|
||||
request.Header.Set("X-Amz-Security-Token", keys.SecurityToken)
|
||||
}
|
||||
|
||||
prepareRequestV3(request)
|
||||
|
||||
// Task 1
|
||||
stringToSign := stringToSignV3(request)
|
||||
|
||||
// Task 2
|
||||
signature := signatureV3(stringToSign, keys)
|
||||
|
||||
// Task 3
|
||||
request.Header.Set("X-Amzn-Authorization", buildAuthHeaderV3(signature, keys))
|
||||
|
||||
return request
|
||||
}
|
||||
|
||||
// Sign2 signs a request with Signed Signature Version 2.
|
||||
// If the service you're accessing supports Version 4, use that instead.
|
||||
func Sign2(request *http.Request, credentials ...Credentials) *http.Request {
|
||||
keys := chooseKeys(credentials)
|
||||
|
||||
// Add the SecurityToken parameter when using STS
|
||||
// This must be added before the signature is calculated
|
||||
if keys.SecurityToken != "" {
|
||||
values := url.Values{}
|
||||
values.Set("SecurityToken", keys.SecurityToken)
|
||||
augmentRequestQuery(request, values)
|
||||
}
|
||||
|
||||
prepareRequestV2(request, keys)
|
||||
|
||||
stringToSign := stringToSignV2(request)
|
||||
signature := signatureV2(stringToSign, keys)
|
||||
|
||||
values := url.Values{}
|
||||
values.Set("Signature", signature)
|
||||
|
||||
augmentRequestQuery(request, values)
|
||||
|
||||
return request
|
||||
}
|
||||
|
||||
// SignS3 signs a request bound for Amazon S3 using their custom
|
||||
// HTTP authentication scheme.
|
||||
func SignS3(request *http.Request, credentials ...Credentials) *http.Request {
|
||||
keys := chooseKeys(credentials)
|
||||
|
||||
// Add the X-Amz-Security-Token header when using STS
|
||||
if keys.SecurityToken != "" {
|
||||
request.Header.Set("X-Amz-Security-Token", keys.SecurityToken)
|
||||
}
|
||||
|
||||
prepareRequestS3(request)
|
||||
|
||||
stringToSign := stringToSignS3(request)
|
||||
signature := signatureS3(stringToSign, keys)
|
||||
|
||||
authHeader := "AWS " + keys.AccessKeyID + ":" + signature
|
||||
request.Header.Set("Authorization", authHeader)
|
||||
|
||||
return request
|
||||
}
|
||||
|
||||
// SignS3Url signs a GET request for a resource on Amazon S3 by appending
|
||||
// query string parameters containing credentials and signature. You must
|
||||
// specify an expiration date for these signed requests. After that date,
|
||||
// a request signed with this method will be rejected by S3.
|
||||
func SignS3Url(request *http.Request, expire time.Time, credentials ...Credentials) *http.Request {
|
||||
keys := chooseKeys(credentials)
|
||||
|
||||
stringToSign := stringToSignS3Url("GET", expire, request.URL.Path)
|
||||
signature := signatureS3(stringToSign, keys)
|
||||
|
||||
query := request.URL.Query()
|
||||
query.Set("AWSAccessKeyId", keys.AccessKeyID)
|
||||
query.Set("Signature", signature)
|
||||
query.Set("Expires", timeToUnixEpochString(expire))
|
||||
request.URL.RawQuery = query.Encode()
|
||||
|
||||
return request
|
||||
}
|
||||
|
||||
// expired checks to see if the temporary credentials from an IAM role are
|
||||
// within 4 minutes of expiration (The IAM documentation says that new keys
|
||||
// will be provisioned 5 minutes before the old keys expire). Credentials
|
||||
// that do not have an Expiration cannot expire.
|
||||
func (this *Credentials) expired() bool {
|
||||
if this.Expiration.IsZero() {
|
||||
// Credentials with no expiration can't expire
|
||||
return false
|
||||
}
|
||||
expireTime := this.Expiration.Add(-4 * time.Minute)
|
||||
// if t - 4 mins is before now, true
|
||||
if expireTime.Before(time.Now()) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
type metadata struct {
|
||||
algorithm string
|
||||
credentialScope string
|
||||
signedHeaders string
|
||||
date string
|
||||
region string
|
||||
service string
|
||||
}
|
||||
|
||||
const (
|
||||
envAccessKey = "AWS_ACCESS_KEY"
|
||||
envAccessKeyID = "AWS_ACCESS_KEY_ID"
|
||||
envSecretKey = "AWS_SECRET_KEY"
|
||||
envSecretAccessKey = "AWS_SECRET_ACCESS_KEY"
|
||||
envSecurityToken = "AWS_SECURITY_TOKEN"
|
||||
)
|
||||
|
||||
var (
|
||||
awsSignVersion = map[string]int{
|
||||
"autoscaling": 4,
|
||||
"cloudfront": 4,
|
||||
"cloudformation": 4,
|
||||
"cloudsearch": 4,
|
||||
"monitoring": 4,
|
||||
"dynamodb": 4,
|
||||
"ec2": 2,
|
||||
"elasticmapreduce": 4,
|
||||
"elastictranscoder": 4,
|
||||
"elasticache": 2,
|
||||
"glacier": 4,
|
||||
"kinesis": 4,
|
||||
"redshift": 4,
|
||||
"rds": 4,
|
||||
"sdb": 2,
|
||||
"sns": 4,
|
||||
"sqs": 4,
|
||||
"s3": 4,
|
||||
"elasticbeanstalk": 4,
|
||||
"importexport": 2,
|
||||
"iam": 4,
|
||||
"route53": 3,
|
||||
"elasticloadbalancing": 4,
|
||||
"email": 3,
|
||||
}
|
||||
)
|
||||
+289
@@ -0,0 +1,289 @@
|
||||
package awsauth
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func TestIntegration(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping long-running integration test.")
|
||||
}
|
||||
|
||||
Convey("Given real credentials from environment variables", t, func() {
|
||||
Convey("A request (with out-of-order query string) with to IAM should succeed (assuming Administrator Access policy)", func() {
|
||||
request := newRequest("GET", "https://iam.amazonaws.com/?Version=2010-05-08&Action=ListRoles", nil)
|
||||
|
||||
if !credentialsSet() {
|
||||
SkipSo(http.StatusOK, ShouldEqual, http.StatusOK)
|
||||
} else {
|
||||
response := sign4AndDo(request)
|
||||
if response.StatusCode != http.StatusOK {
|
||||
message, _ := ioutil.ReadAll(response.Body)
|
||||
t.Error(string(message))
|
||||
}
|
||||
So(response.StatusCode, ShouldEqual, http.StatusOK)
|
||||
}
|
||||
})
|
||||
|
||||
Convey("A request to S3 should succeed", func() {
|
||||
request, _ := http.NewRequest("GET", "https://s3.amazonaws.com", nil)
|
||||
|
||||
if !credentialsSet() {
|
||||
SkipSo(http.StatusOK, ShouldEqual, http.StatusOK)
|
||||
} else {
|
||||
response := sign4AndDo(request)
|
||||
if response.StatusCode != http.StatusOK {
|
||||
message, _ := ioutil.ReadAll(response.Body)
|
||||
t.Error(string(message))
|
||||
}
|
||||
So(response.StatusCode, ShouldEqual, http.StatusOK)
|
||||
}
|
||||
})
|
||||
|
||||
Convey("A request to EC2 should succeed", func() {
|
||||
request := newRequest("GET", "https://ec2.amazonaws.com/?Version=2013-10-15&Action=DescribeInstances", nil)
|
||||
|
||||
if !credentialsSet() {
|
||||
SkipSo(http.StatusOK, ShouldEqual, http.StatusOK)
|
||||
} else {
|
||||
response := sign2AndDo(request)
|
||||
if response.StatusCode != http.StatusOK {
|
||||
message, _ := ioutil.ReadAll(response.Body)
|
||||
t.Error(string(message))
|
||||
}
|
||||
So(response.StatusCode, ShouldEqual, http.StatusOK)
|
||||
}
|
||||
})
|
||||
|
||||
Convey("A request to SQS should succeed", func() {
|
||||
request := newRequest("POST", "https://sqs.us-west-2.amazonaws.com", url.Values{
|
||||
"Action": []string{"ListQueues"},
|
||||
})
|
||||
|
||||
if !credentialsSet() {
|
||||
SkipSo(http.StatusOK, ShouldEqual, http.StatusOK)
|
||||
} else {
|
||||
response := sign4AndDo(request)
|
||||
if response.StatusCode != http.StatusOK {
|
||||
message, _ := ioutil.ReadAll(response.Body)
|
||||
t.Error(string(message))
|
||||
}
|
||||
So(response.StatusCode, ShouldEqual, http.StatusOK)
|
||||
}
|
||||
})
|
||||
|
||||
Convey("A request to SES should succeed", func() {
|
||||
request := newRequest("GET", "https://email.us-east-1.amazonaws.com/?Action=GetSendStatistics", nil)
|
||||
|
||||
if !credentialsSet() {
|
||||
SkipSo(http.StatusOK, ShouldEqual, http.StatusOK)
|
||||
} else {
|
||||
response := sign3AndDo(request)
|
||||
if response.StatusCode != http.StatusOK {
|
||||
message, _ := ioutil.ReadAll(response.Body)
|
||||
t.Error(string(message))
|
||||
}
|
||||
So(response.StatusCode, ShouldEqual, http.StatusOK)
|
||||
}
|
||||
})
|
||||
|
||||
Convey("A request to Route 53 should succeed", func() {
|
||||
request := newRequest("GET", "https://route53.amazonaws.com/2013-04-01/hostedzone?maxitems=1", nil)
|
||||
|
||||
if !credentialsSet() {
|
||||
SkipSo(http.StatusOK, ShouldEqual, http.StatusOK)
|
||||
} else {
|
||||
response := sign3AndDo(request)
|
||||
if response.StatusCode != http.StatusOK {
|
||||
message, _ := ioutil.ReadAll(response.Body)
|
||||
t.Error(string(message))
|
||||
}
|
||||
So(response.StatusCode, ShouldEqual, http.StatusOK)
|
||||
}
|
||||
})
|
||||
|
||||
Convey("A request to SimpleDB should succeed", func() {
|
||||
request := newRequest("GET", "https://sdb.amazonaws.com/?Action=ListDomains&Version=2009-04-15", nil)
|
||||
|
||||
if !credentialsSet() {
|
||||
SkipSo(http.StatusOK, ShouldEqual, http.StatusOK)
|
||||
} else {
|
||||
response := sign2AndDo(request)
|
||||
if response.StatusCode != http.StatusOK {
|
||||
message, _ := ioutil.ReadAll(response.Body)
|
||||
t.Error(string(message))
|
||||
}
|
||||
So(response.StatusCode, ShouldEqual, http.StatusOK)
|
||||
}
|
||||
})
|
||||
|
||||
Convey("If S3Resource env variable is set", func() {
|
||||
s3res := os.Getenv("S3Resource")
|
||||
|
||||
Convey("A URL-signed request to that S3 resource should succeed", func() {
|
||||
request, _ := http.NewRequest("GET", s3res, nil)
|
||||
|
||||
if !credentialsSet() || s3res == "" {
|
||||
SkipSo(http.StatusOK, ShouldEqual, http.StatusOK)
|
||||
} else {
|
||||
response := signS3UrlAndDo(request)
|
||||
if response.StatusCode != http.StatusOK {
|
||||
message, _ := ioutil.ReadAll(response.Body)
|
||||
t.Error(string(message))
|
||||
}
|
||||
So(response.StatusCode, ShouldEqual, http.StatusOK)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestSign(t *testing.T) {
|
||||
Convey("Requests to services using Version 2 should be signed accordingly", t, func() {
|
||||
reqs := []*http.Request{
|
||||
newRequest("GET", "https://ec2.amazonaws.com", url.Values{}),
|
||||
newRequest("GET", "https://elasticache.amazonaws.com/", url.Values{}),
|
||||
}
|
||||
for _, request := range reqs {
|
||||
signedReq := Sign(request)
|
||||
So(signedReq.URL.Query().Get("SignatureVersion"), ShouldEqual, "2")
|
||||
}
|
||||
})
|
||||
|
||||
Convey("Requests to services using Version 3 should be signed accordingly", t, func() {
|
||||
reqs := []*http.Request{
|
||||
newRequest("GET", "https://route53.amazonaws.com", url.Values{}),
|
||||
newRequest("GET", "https://email.us-east-1.amazonaws.com/", url.Values{}),
|
||||
}
|
||||
for _, request := range reqs {
|
||||
signedReq := Sign(request)
|
||||
So(signedReq.Header.Get("X-Amzn-Authorization"), ShouldNotBeBlank)
|
||||
}
|
||||
})
|
||||
|
||||
Convey("Requests to services using Version 4 should be signed accordingly", t, func() {
|
||||
reqs := []*http.Request{
|
||||
newRequest("POST", "https://sqs.amazonaws.com/", url.Values{}),
|
||||
newRequest("GET", "https://iam.amazonaws.com", url.Values{}),
|
||||
newRequest("GET", "https://s3.amazonaws.com", url.Values{}),
|
||||
}
|
||||
for _, request := range reqs {
|
||||
signedReq := Sign(request)
|
||||
So(signedReq.Header.Get("Authorization"), ShouldContainSubstring, ", Signature=")
|
||||
}
|
||||
})
|
||||
|
||||
var keys Credentials
|
||||
keys = newKeys()
|
||||
Convey("Requests to services using existing credentials Version 2 should be signed accordingly", t, func() {
|
||||
reqs := []*http.Request{
|
||||
newRequest("GET", "https://ec2.amazonaws.com", url.Values{}),
|
||||
newRequest("GET", "https://elasticache.amazonaws.com/", url.Values{}),
|
||||
}
|
||||
for _, request := range reqs {
|
||||
signedReq := Sign(request, keys)
|
||||
So(signedReq.URL.Query().Get("SignatureVersion"), ShouldEqual, "2")
|
||||
}
|
||||
})
|
||||
|
||||
Convey("Requests to services using existing credentials Version 3 should be signed accordingly", t, func() {
|
||||
reqs := []*http.Request{
|
||||
newRequest("GET", "https://route53.amazonaws.com", url.Values{}),
|
||||
newRequest("GET", "https://email.us-east-1.amazonaws.com/", url.Values{}),
|
||||
}
|
||||
for _, request := range reqs {
|
||||
signedReq := Sign(request, keys)
|
||||
So(signedReq.Header.Get("X-Amzn-Authorization"), ShouldNotBeBlank)
|
||||
}
|
||||
})
|
||||
|
||||
Convey("Requests to services using existing credentials Version 4 should be signed accordingly", t, func() {
|
||||
reqs := []*http.Request{
|
||||
newRequest("POST", "https://sqs.amazonaws.com/", url.Values{}),
|
||||
newRequest("GET", "https://iam.amazonaws.com", url.Values{}),
|
||||
newRequest("GET", "https://s3.amazonaws.com", url.Values{}),
|
||||
}
|
||||
for _, request := range reqs {
|
||||
signedReq := Sign(request, keys)
|
||||
So(signedReq.Header.Get("Authorization"), ShouldContainSubstring, ", Signature=")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestExpiration(t *testing.T) {
|
||||
var credentials = &Credentials{}
|
||||
|
||||
Convey("Credentials without an expiration can't expire", t, func() {
|
||||
So(credentials.expired(), ShouldBeFalse)
|
||||
})
|
||||
|
||||
Convey("Credentials that expire in 5 minutes aren't expired", t, func() {
|
||||
credentials.Expiration = time.Now().Add(5 * time.Minute)
|
||||
So(credentials.expired(), ShouldBeFalse)
|
||||
})
|
||||
|
||||
Convey("Credentials that expire in 1 minute are expired", t, func() {
|
||||
credentials.Expiration = time.Now().Add(1 * time.Minute)
|
||||
So(credentials.expired(), ShouldBeTrue)
|
||||
})
|
||||
|
||||
Convey("Credentials that expired 2 hours ago are expired", t, func() {
|
||||
credentials.Expiration = time.Now().Add(-2 * time.Hour)
|
||||
So(credentials.expired(), ShouldBeTrue)
|
||||
})
|
||||
}
|
||||
|
||||
func credentialsSet() bool {
|
||||
var keys Credentials
|
||||
keys = newKeys()
|
||||
if keys.AccessKeyID == "" {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func newRequest(method string, url string, v url.Values) *http.Request {
|
||||
request, _ := http.NewRequest(method, url, strings.NewReader(v.Encode()))
|
||||
return request
|
||||
}
|
||||
|
||||
func sign2AndDo(request *http.Request) *http.Response {
|
||||
Sign2(request)
|
||||
response, _ := client.Do(request)
|
||||
return response
|
||||
}
|
||||
|
||||
func sign3AndDo(request *http.Request) *http.Response {
|
||||
Sign3(request)
|
||||
response, _ := client.Do(request)
|
||||
return response
|
||||
}
|
||||
|
||||
func sign4AndDo(request *http.Request) *http.Response {
|
||||
Sign4(request)
|
||||
response, _ := client.Do(request)
|
||||
return response
|
||||
}
|
||||
|
||||
func signS3AndDo(request *http.Request) *http.Response {
|
||||
SignS3(request)
|
||||
response, _ := client.Do(request)
|
||||
return response
|
||||
}
|
||||
|
||||
func signS3UrlAndDo(request *http.Request) *http.Response {
|
||||
SignS3Url(request, time.Now().AddDate(0, 0, 1))
|
||||
response, _ := client.Do(request)
|
||||
return response
|
||||
}
|
||||
|
||||
var client = &http.Client{}
|
||||
+311
@@ -0,0 +1,311 @@
|
||||
package awsauth
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type location struct {
|
||||
ec2 bool
|
||||
checked bool
|
||||
}
|
||||
|
||||
var loc *location
|
||||
|
||||
// serviceAndRegion parsers a hostname to find out which ones it is.
|
||||
// http://docs.aws.amazon.com/general/latest/gr/rande.html
|
||||
func serviceAndRegion(host string) (service string, region string) {
|
||||
// These are the defaults if the hostname doesn't suggest something else
|
||||
region = "us-east-1"
|
||||
service = "s3"
|
||||
|
||||
parts := strings.Split(host, ".")
|
||||
if len(parts) == 4 {
|
||||
// Either service.region.amazonaws.com or virtual-host.region.amazonaws.com
|
||||
if parts[1] == "s3" {
|
||||
service = "s3"
|
||||
} else if strings.HasPrefix(parts[1], "s3-") {
|
||||
region = parts[1][3:]
|
||||
service = "s3"
|
||||
} else {
|
||||
service = parts[0]
|
||||
region = parts[1]
|
||||
}
|
||||
} else if len(parts) == 5 {
|
||||
service = parts[2]
|
||||
region = parts[1]
|
||||
} else {
|
||||
// Either service.amazonaws.com or s3-region.amazonaws.com
|
||||
if strings.HasPrefix(parts[0], "s3-") {
|
||||
region = parts[0][3:]
|
||||
} else {
|
||||
service = parts[0]
|
||||
}
|
||||
}
|
||||
|
||||
if region == "external-1" {
|
||||
region = "us-east-1"
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// newKeys produces a set of credentials based on the environment
|
||||
func newKeys() (newCredentials Credentials) {
|
||||
// First use credentials from environment variables
|
||||
newCredentials.AccessKeyID = os.Getenv(envAccessKeyID)
|
||||
if newCredentials.AccessKeyID == "" {
|
||||
newCredentials.AccessKeyID = os.Getenv(envAccessKey)
|
||||
}
|
||||
|
||||
newCredentials.SecretAccessKey = os.Getenv(envSecretAccessKey)
|
||||
if newCredentials.SecretAccessKey == "" {
|
||||
newCredentials.SecretAccessKey = os.Getenv(envSecretKey)
|
||||
}
|
||||
|
||||
newCredentials.SecurityToken = os.Getenv(envSecurityToken)
|
||||
|
||||
// If there is no Access Key and you are on EC2, get the key from the role
|
||||
if (newCredentials.AccessKeyID == "" || newCredentials.SecretAccessKey == "") && onEC2() {
|
||||
newCredentials = *getIAMRoleCredentials()
|
||||
}
|
||||
|
||||
// If the key is expiring, get a new key
|
||||
if newCredentials.expired() && onEC2() {
|
||||
newCredentials = *getIAMRoleCredentials()
|
||||
}
|
||||
|
||||
return newCredentials
|
||||
}
|
||||
|
||||
// checkKeys gets credentials depending on if any were passed in as an argument
|
||||
// or it makes new ones based on the environment.
|
||||
func chooseKeys(cred []Credentials) Credentials {
|
||||
if len(cred) == 0 {
|
||||
return newKeys()
|
||||
} else {
|
||||
return cred[0]
|
||||
}
|
||||
}
|
||||
|
||||
// onEC2 checks to see if the program is running on an EC2 instance.
|
||||
// It does this by looking for the EC2 metadata service.
|
||||
// This caches that information in a struct so that it doesn't waste time.
|
||||
func onEC2() bool {
|
||||
if loc == nil {
|
||||
loc = &location{}
|
||||
}
|
||||
if !(loc.checked) {
|
||||
c, err := net.DialTimeout("tcp", "169.254.169.254:80", time.Millisecond*100)
|
||||
|
||||
if err != nil {
|
||||
loc.ec2 = false
|
||||
} else {
|
||||
c.Close()
|
||||
loc.ec2 = true
|
||||
}
|
||||
loc.checked = true
|
||||
}
|
||||
|
||||
return loc.ec2
|
||||
}
|
||||
|
||||
// getIAMRoleList gets a list of the roles that are available to this instance
|
||||
func getIAMRoleList() []string {
|
||||
|
||||
var roles []string
|
||||
url := "http://169.254.169.254/latest/meta-data/iam/security-credentials/"
|
||||
|
||||
client := &http.Client{}
|
||||
|
||||
request, err := http.NewRequest("GET", url, nil)
|
||||
|
||||
if err != nil {
|
||||
return roles
|
||||
}
|
||||
|
||||
response, err := client.Do(request)
|
||||
|
||||
if err != nil {
|
||||
return roles
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
scanner := bufio.NewScanner(response.Body)
|
||||
for scanner.Scan() {
|
||||
roles = append(roles, scanner.Text())
|
||||
}
|
||||
return roles
|
||||
}
|
||||
|
||||
func getIAMRoleCredentials() *Credentials {
|
||||
|
||||
roles := getIAMRoleList()
|
||||
|
||||
if len(roles) < 1 {
|
||||
return &Credentials{}
|
||||
}
|
||||
|
||||
// Use the first role in the list
|
||||
role := roles[0]
|
||||
|
||||
url := "http://169.254.169.254/latest/meta-data/iam/security-credentials/"
|
||||
|
||||
// Create the full URL of the role
|
||||
var buffer bytes.Buffer
|
||||
buffer.WriteString(url)
|
||||
buffer.WriteString(role)
|
||||
roleURL := buffer.String()
|
||||
|
||||
// Get the role
|
||||
roleRequest, err := http.NewRequest("GET", roleURL, nil)
|
||||
|
||||
if err != nil {
|
||||
return &Credentials{}
|
||||
}
|
||||
|
||||
client := &http.Client{}
|
||||
roleResponse, err := client.Do(roleRequest)
|
||||
|
||||
if err != nil {
|
||||
return &Credentials{}
|
||||
}
|
||||
defer roleResponse.Body.Close()
|
||||
|
||||
roleBuffer := new(bytes.Buffer)
|
||||
roleBuffer.ReadFrom(roleResponse.Body)
|
||||
|
||||
credentials := Credentials{}
|
||||
|
||||
err = json.Unmarshal(roleBuffer.Bytes(), &credentials)
|
||||
|
||||
if err != nil {
|
||||
return &Credentials{}
|
||||
}
|
||||
|
||||
return &credentials
|
||||
|
||||
}
|
||||
|
||||
func augmentRequestQuery(request *http.Request, values url.Values) *http.Request {
|
||||
for key, array := range request.URL.Query() {
|
||||
for _, value := range array {
|
||||
values.Set(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
request.URL.RawQuery = values.Encode()
|
||||
|
||||
return request
|
||||
}
|
||||
|
||||
func hmacSHA256(key []byte, content string) []byte {
|
||||
mac := hmac.New(sha256.New, key)
|
||||
mac.Write([]byte(content))
|
||||
return mac.Sum(nil)
|
||||
}
|
||||
|
||||
func hmacSHA1(key []byte, content string) []byte {
|
||||
mac := hmac.New(sha1.New, key)
|
||||
mac.Write([]byte(content))
|
||||
return mac.Sum(nil)
|
||||
}
|
||||
|
||||
func hashSHA256(content []byte) string {
|
||||
h := sha256.New()
|
||||
h.Write(content)
|
||||
return fmt.Sprintf("%x", h.Sum(nil))
|
||||
}
|
||||
|
||||
func hashMD5(content []byte) string {
|
||||
h := md5.New()
|
||||
h.Write(content)
|
||||
return base64.StdEncoding.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
func readAndReplaceBody(request *http.Request) []byte {
|
||||
if request.Body == nil {
|
||||
return []byte{}
|
||||
}
|
||||
payload, _ := ioutil.ReadAll(request.Body)
|
||||
request.Body = ioutil.NopCloser(bytes.NewReader(payload))
|
||||
return payload
|
||||
}
|
||||
|
||||
func concat(delim string, str ...string) string {
|
||||
return strings.Join(str, delim)
|
||||
}
|
||||
|
||||
var now = func() time.Time {
|
||||
return time.Now().UTC()
|
||||
}
|
||||
|
||||
func normuri(uri string) string {
|
||||
parts := strings.Split(uri, "/")
|
||||
for i := range parts {
|
||||
parts[i] = encodePathFrag(parts[i])
|
||||
}
|
||||
return strings.Join(parts, "/")
|
||||
}
|
||||
|
||||
func encodePathFrag(s string) string {
|
||||
hexCount := 0
|
||||
for i := 0; i < len(s); i++ {
|
||||
c := s[i]
|
||||
if shouldEscape(c) {
|
||||
hexCount++
|
||||
}
|
||||
}
|
||||
t := make([]byte, len(s)+2*hexCount)
|
||||
j := 0
|
||||
for i := 0; i < len(s); i++ {
|
||||
c := s[i]
|
||||
if shouldEscape(c) {
|
||||
t[j] = '%'
|
||||
t[j+1] = "0123456789ABCDEF"[c>>4]
|
||||
t[j+2] = "0123456789ABCDEF"[c&15]
|
||||
j += 3
|
||||
} else {
|
||||
t[j] = c
|
||||
j++
|
||||
}
|
||||
}
|
||||
return string(t)
|
||||
}
|
||||
|
||||
func shouldEscape(c byte) bool {
|
||||
if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' {
|
||||
return false
|
||||
}
|
||||
if '0' <= c && c <= '9' {
|
||||
return false
|
||||
}
|
||||
if c == '-' || c == '_' || c == '.' || c == '~' {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func normquery(v url.Values) string {
|
||||
queryString := v.Encode()
|
||||
|
||||
// Go encodes a space as '+' but Amazon requires '%20'. Luckily any '+' in the
|
||||
// original query string has been percent escaped so all '+' chars that are left
|
||||
// were originally spaces.
|
||||
|
||||
return strings.Replace(queryString, "+", "%20", -1)
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
package awsauth
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func TestCommonFunctions(t *testing.T) {
|
||||
Convey("Service and region should be properly extracted from host strings", t, func() {
|
||||
service, region := serviceAndRegion("sqs.us-west-2.amazonaws.com")
|
||||
So(service, ShouldEqual, "sqs")
|
||||
So(region, ShouldEqual, "us-west-2")
|
||||
|
||||
service, region = serviceAndRegion("iam.amazonaws.com")
|
||||
So(service, ShouldEqual, "iam")
|
||||
So(region, ShouldEqual, "us-east-1")
|
||||
|
||||
service, region = serviceAndRegion("sns.us-west-2.amazonaws.com")
|
||||
So(service, ShouldEqual, "sns")
|
||||
So(region, ShouldEqual, "us-west-2")
|
||||
|
||||
service, region = serviceAndRegion("bucketname.s3.amazonaws.com")
|
||||
So(service, ShouldEqual, "s3")
|
||||
So(region, ShouldEqual, "us-east-1")
|
||||
|
||||
service, region = serviceAndRegion("s3.amazonaws.com")
|
||||
So(service, ShouldEqual, "s3")
|
||||
So(region, ShouldEqual, "us-east-1")
|
||||
|
||||
service, region = serviceAndRegion("s3-us-west-1.amazonaws.com")
|
||||
So(service, ShouldEqual, "s3")
|
||||
So(region, ShouldEqual, "us-west-1")
|
||||
|
||||
service, region = serviceAndRegion("s3-external-1.amazonaws.com")
|
||||
So(service, ShouldEqual, "s3")
|
||||
So(region, ShouldEqual, "us-east-1")
|
||||
})
|
||||
|
||||
Convey("MD5 hashes should be properly computed and base-64 encoded", t, func() {
|
||||
input := []byte("Pretend this is a REALLY long byte array...")
|
||||
actual := hashMD5(input)
|
||||
|
||||
So(actual, ShouldEqual, "KbVTY8Vl6VccnzQf1AGOFw==")
|
||||
})
|
||||
|
||||
Convey("SHA-256 hashes should be properly hex-encoded (base 16)", t, func() {
|
||||
input := []byte("This is... Sparta!!")
|
||||
actual := hashSHA256(input)
|
||||
|
||||
So(actual, ShouldEqual, "5c81a4ef1172e89b1a9d575f4cd82f4ed20ea9137e61aa7f1ab936291d24e79a")
|
||||
})
|
||||
|
||||
Convey("Given a key and contents", t, func() {
|
||||
key := []byte("asdf1234")
|
||||
contents := "SmartyStreets was here"
|
||||
|
||||
Convey("HMAC-SHA256 should be properly computed", func() {
|
||||
expected := []byte{65, 46, 186, 78, 2, 155, 71, 104, 49, 37, 5, 66, 195, 129, 159, 227, 239, 53, 240, 107, 83, 21, 235, 198, 238, 216, 108, 149, 143, 222, 144, 94}
|
||||
actual := hmacSHA256(key, contents)
|
||||
|
||||
So(actual, ShouldResemble, expected)
|
||||
})
|
||||
|
||||
Convey("HMAC-SHA1 should be properly computed", func() {
|
||||
expected := []byte{164, 77, 252, 0, 87, 109, 207, 110, 163, 75, 228, 122, 83, 255, 233, 237, 125, 206, 85, 70}
|
||||
actual := hmacSHA1(key, contents)
|
||||
|
||||
So(actual, ShouldResemble, expected)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Strings should be properly concatenated with a delimiter", t, func() {
|
||||
So(concat("\n", "Test1", "Test2"), ShouldEqual, "Test1\nTest2")
|
||||
So(concat(".", "Test1"), ShouldEqual, "Test1")
|
||||
So(concat("\t", "1", "2", "3", "4"), ShouldEqual, "1\t2\t3\t4")
|
||||
})
|
||||
|
||||
Convey("URI components should be properly encoded", t, func() {
|
||||
So(normuri("/-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"), ShouldEqual, "/-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")
|
||||
So(normuri("/ /foo"), ShouldEqual, "/%20/foo")
|
||||
So(normuri("/(foo)"), ShouldEqual, "/%28foo%29")
|
||||
})
|
||||
|
||||
Convey("URI query strings should be properly encoded", t, func() {
|
||||
So(normquery(url.Values{"p": []string{" +&;-=._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"}}), ShouldEqual, "p=%20%2B%26%3B-%3D._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")
|
||||
})
|
||||
}
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
package awsauth
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func signatureS3(stringToSign string, keys Credentials) string {
|
||||
hashed := hmacSHA1([]byte(keys.SecretAccessKey), stringToSign)
|
||||
return base64.StdEncoding.EncodeToString(hashed)
|
||||
}
|
||||
|
||||
func stringToSignS3(request *http.Request) string {
|
||||
str := request.Method + "\n"
|
||||
|
||||
if request.Header.Get("Content-Md5") != "" {
|
||||
str += request.Header.Get("Content-Md5")
|
||||
}
|
||||
str += "\n"
|
||||
|
||||
str += request.Header.Get("Content-Type") + "\n"
|
||||
|
||||
if request.Header.Get("Date") != "" {
|
||||
str += request.Header.Get("Date")
|
||||
} else {
|
||||
str += timestampS3()
|
||||
}
|
||||
|
||||
str += "\n"
|
||||
|
||||
canonicalHeaders := canonicalAmzHeadersS3(request)
|
||||
if canonicalHeaders != "" {
|
||||
str += canonicalHeaders
|
||||
}
|
||||
|
||||
str += canonicalResourceS3(request)
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
func stringToSignS3Url(method string, expire time.Time, path string) string {
|
||||
return method + "\n\n\n" + timeToUnixEpochString(expire) + "\n" + path
|
||||
}
|
||||
|
||||
func timeToUnixEpochString(t time.Time) string {
|
||||
return strconv.FormatInt(t.Unix(), 10)
|
||||
}
|
||||
|
||||
func canonicalAmzHeadersS3(request *http.Request) string {
|
||||
var headers []string
|
||||
|
||||
for header := range request.Header {
|
||||
standardized := strings.ToLower(strings.TrimSpace(header))
|
||||
if strings.HasPrefix(standardized, "x-amz") {
|
||||
headers = append(headers, standardized)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(headers)
|
||||
|
||||
for i, header := range headers {
|
||||
headers[i] = header + ":" + strings.Replace(request.Header.Get(header), "\n", " ", -1)
|
||||
}
|
||||
|
||||
if len(headers) > 0 {
|
||||
return strings.Join(headers, "\n") + "\n"
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func canonicalResourceS3(request *http.Request) string {
|
||||
res := ""
|
||||
|
||||
if isS3VirtualHostedStyle(request) {
|
||||
bucketname := strings.Split(request.Host, ".")[0]
|
||||
res += "/" + bucketname
|
||||
}
|
||||
|
||||
uri := request.URL.Opaque
|
||||
if uri != "" {
|
||||
uri = "/" + strings.Join(strings.Split(uri, "/")[3:], "/")
|
||||
} else {
|
||||
uri = request.URL.Path
|
||||
}
|
||||
if uri == "" {
|
||||
uri = "/"
|
||||
}
|
||||
|
||||
res += uri
|
||||
|
||||
for _, subres := range strings.Split(subresourcesS3, ",") {
|
||||
if strings.HasPrefix(request.URL.RawQuery, subres) {
|
||||
res += "?" + subres
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func prepareRequestS3(request *http.Request) *http.Request {
|
||||
request.Header.Set("Date", timestampS3())
|
||||
if request.URL.Path == "" {
|
||||
request.URL.Path += "/"
|
||||
}
|
||||
return request
|
||||
}
|
||||
|
||||
// Info: http://docs.aws.amazon.com/AmazonS3/latest/dev/VirtualHosting.html
|
||||
func isS3VirtualHostedStyle(request *http.Request) bool {
|
||||
service, _ := serviceAndRegion(request.Host)
|
||||
return service == "s3" && strings.Count(request.Host, ".") == 3
|
||||
}
|
||||
|
||||
func timestampS3() string {
|
||||
return now().Format(timeFormatS3)
|
||||
}
|
||||
|
||||
const (
|
||||
timeFormatS3 = time.RFC1123Z
|
||||
subresourcesS3 = "acl,lifecycle,location,logging,notification,partNumber,policy,requestPayment,torrent,uploadId,uploads,versionId,versioning,versions,website"
|
||||
)
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
package awsauth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func TestSignatureS3(t *testing.T) {
|
||||
// http://docs.aws.amazon.com/AmazonS3/2006-03-01/dev/RESTAuthentication.html
|
||||
// Note: S3 now supports signed signature version 4
|
||||
// (but signed URL requests still utilize a lot of the same functionality)
|
||||
|
||||
Convey("Given a GET request to Amazon S3", t, func() {
|
||||
keys := *testCredS3
|
||||
request := test_plainRequestS3()
|
||||
|
||||
// Mock time
|
||||
now = func() time.Time {
|
||||
parsed, _ := time.Parse(timeFormatS3, exampleReqTsS3)
|
||||
return parsed
|
||||
}
|
||||
|
||||
Convey("The request should be prepared with a Date header", func() {
|
||||
prepareRequestS3(request)
|
||||
So(request.Header.Get("Date"), ShouldEqual, exampleReqTsS3)
|
||||
})
|
||||
|
||||
Convey("The CanonicalizedAmzHeaders should be built properly", func() {
|
||||
req2 := test_headerRequestS3()
|
||||
actual := canonicalAmzHeadersS3(req2)
|
||||
So(actual, ShouldEqual, expectedCanonAmzHeadersS3)
|
||||
})
|
||||
|
||||
Convey("The CanonicalizedResource should be built properly", func() {
|
||||
actual := canonicalResourceS3(request)
|
||||
So(actual, ShouldEqual, expectedCanonResourceS3)
|
||||
})
|
||||
|
||||
Convey("The string to sign should be correct", func() {
|
||||
actual := stringToSignS3(request)
|
||||
So(actual, ShouldEqual, expectedStringToSignS3)
|
||||
})
|
||||
|
||||
Convey("The final signature string should be exactly correct", func() {
|
||||
actual := signatureS3(stringToSignS3(request), keys)
|
||||
So(actual, ShouldEqual, "bWq2s1WEIj+Ydj0vQ697zp+IXMU=")
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Given a GET request for a resource on S3 for query string authentication", t, func() {
|
||||
keys := *testCredS3
|
||||
request, _ := http.NewRequest("GET", "https://johnsmith.s3.amazonaws.com/johnsmith/photos/puppy.jpg", nil)
|
||||
|
||||
now = func() time.Time {
|
||||
parsed, _ := time.Parse(timeFormatS3, exampleReqTsS3)
|
||||
return parsed
|
||||
}
|
||||
|
||||
Convey("The string to sign should be correct", func() {
|
||||
actual := stringToSignS3Url("GET", now(), request.URL.Path)
|
||||
So(actual, ShouldEqual, expectedStringToSignS3Url)
|
||||
})
|
||||
|
||||
Convey("The signature of string to sign should be correct", func() {
|
||||
actual := signatureS3(expectedStringToSignS3Url, keys)
|
||||
So(actual, ShouldEqual, "R2K/+9bbnBIbVDCs7dqlz3XFtBQ=")
|
||||
})
|
||||
|
||||
Convey("The finished signed URL should be correct", func() {
|
||||
expiry := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
|
||||
So(SignS3Url(request, expiry, keys).URL.String(), ShouldEqual, expectedSignedS3Url)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestS3STSRequestPreparer(t *testing.T) {
|
||||
Convey("Given a plain request with no custom headers", t, func() {
|
||||
request := test_plainRequestS3()
|
||||
|
||||
Convey("And a set of credentials with an STS token", func() {
|
||||
keys := *testCredS3WithSTS
|
||||
|
||||
Convey("It should include an X-Amz-Security-Token when the request is signed", func() {
|
||||
actualSigned := SignS3(request, keys)
|
||||
actual := actualSigned.Header.Get("X-Amz-Security-Token")
|
||||
|
||||
So(actual, ShouldNotBeBlank)
|
||||
So(actual, ShouldEqual, testCredS3WithSTS.SecurityToken)
|
||||
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func test_plainRequestS3() *http.Request {
|
||||
request, _ := http.NewRequest("GET", "https://johnsmith.s3.amazonaws.com/photos/puppy.jpg", nil)
|
||||
return request
|
||||
}
|
||||
|
||||
func test_headerRequestS3() *http.Request {
|
||||
request := test_plainRequestS3()
|
||||
request.Header.Set("X-Amz-Meta-Something", "more foobar")
|
||||
request.Header.Set("X-Amz-Date", "foobar")
|
||||
request.Header.Set("X-Foobar", "nanoo-nanoo")
|
||||
return request
|
||||
}
|
||||
|
||||
func TestCanonical(t *testing.T) {
|
||||
expectedCanonicalString := "PUT\nc8fdb181845a4ca6b8fec737b3581d76\ntext/html\nThu, 17 Nov 2005 18:49:58 GMT\nx-amz-magic:abracadabra\nx-amz-meta-author:foo@bar.com\n/quotes/nelson"
|
||||
|
||||
origUrl := "https://s3.amazonaws.com/"
|
||||
resource := "/quotes/nelson"
|
||||
|
||||
u, _ := url.ParseRequestURI(origUrl)
|
||||
u.Path = resource
|
||||
urlStr := fmt.Sprintf("%v", u)
|
||||
|
||||
request, _ := http.NewRequest("PUT", urlStr, nil)
|
||||
request.Header.Add("Content-Md5", "c8fdb181845a4ca6b8fec737b3581d76")
|
||||
request.Header.Add("Content-Type", "text/html")
|
||||
request.Header.Add("Date", "Thu, 17 Nov 2005 18:49:58 GMT")
|
||||
request.Header.Add("X-Amz-Meta-Author", "foo@bar.com")
|
||||
request.Header.Add("X-Amz-Magic", "abracadabra")
|
||||
|
||||
if stringToSignS3(request) != expectedCanonicalString {
|
||||
t.Errorf("----Got\n***%s***\n----Expected\n***%s***", stringToSignS3(request), expectedCanonicalString)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
testCredS3 = &Credentials{
|
||||
AccessKeyID: "AKIAIOSFODNN7EXAMPLE",
|
||||
SecretAccessKey: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
|
||||
}
|
||||
|
||||
testCredS3WithSTS = &Credentials{
|
||||
AccessKeyID: "AKIDEXAMPLE",
|
||||
SecretAccessKey: "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
|
||||
SecurityToken: "AQoDYXdzEHcaoAJ1Aqwx1Sum0iW2NQjXJcWlKR7vuB6lnAeGBaQnjDRZPVyniwc48ml5hx+0qiXenVJdfusMMl9XLhSncfhx9Rb1UF8IAOaQ+CkpWXvoH67YYN+93dgckSVgVEBRByTl/BvLOZhe0ii/pOWkuQtBm5T7lBHRe4Dfmxy9X6hd8L3FrWxgnGV3fWZ3j0gASdYXaa+VBJlU0E2/GmCzn3T+t2mjYaeoInAnYVKVpmVMOrh6lNAeETTOHElLopblSa7TAmROq5xHIyu4a9i2qwjERTwa3Yk4Jk6q7JYVA5Cu7kS8wKVml8LdzzCTsy+elJgvH+Jf6ivpaHt/En0AJ5PZUJDev2+Y5+9j4AYfrmXfm4L73DC1ZJFJrv+Yh+EXAMPLE=",
|
||||
}
|
||||
|
||||
expectedCanonAmzHeadersS3 = "x-amz-date:foobar\nx-amz-meta-something:more foobar\n"
|
||||
expectedCanonResourceS3 = "/johnsmith/photos/puppy.jpg"
|
||||
expectedStringToSignS3 = "GET\n\n\nTue, 27 Mar 2007 19:36:42 +0000\n/johnsmith/photos/puppy.jpg"
|
||||
expectedStringToSignS3Url = "GET\n\n\n1175024202\n/johnsmith/photos/puppy.jpg"
|
||||
expectedSignedS3Url = "https://johnsmith.s3.amazonaws.com/johnsmith/photos/puppy.jpg?AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE&Expires=1257894000&Signature=X%2FarTLAJP08uP1Bsap52rwmsVok%3D"
|
||||
exampleReqTsS3 = "Tue, 27 Mar 2007 19:36:42 +0000"
|
||||
)
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package awsauth
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func prepareRequestV2(request *http.Request, keys Credentials) *http.Request {
|
||||
|
||||
keyID := keys.AccessKeyID
|
||||
|
||||
values := url.Values{}
|
||||
values.Set("AWSAccessKeyId", keyID)
|
||||
values.Set("SignatureVersion", "2")
|
||||
values.Set("SignatureMethod", "HmacSHA256")
|
||||
values.Set("Timestamp", timestampV2())
|
||||
|
||||
augmentRequestQuery(request, values)
|
||||
|
||||
if request.URL.Path == "" {
|
||||
request.URL.Path += "/"
|
||||
}
|
||||
|
||||
return request
|
||||
}
|
||||
|
||||
func stringToSignV2(request *http.Request) string {
|
||||
str := request.Method + "\n"
|
||||
str += strings.ToLower(request.URL.Host) + "\n"
|
||||
str += request.URL.Path + "\n"
|
||||
str += canonicalQueryStringV2(request)
|
||||
return str
|
||||
}
|
||||
|
||||
func signatureV2(strToSign string, keys Credentials) string {
|
||||
hashed := hmacSHA256([]byte(keys.SecretAccessKey), strToSign)
|
||||
return base64.StdEncoding.EncodeToString(hashed)
|
||||
}
|
||||
|
||||
func canonicalQueryStringV2(request *http.Request) string {
|
||||
return request.URL.RawQuery
|
||||
}
|
||||
|
||||
func timestampV2() string {
|
||||
return now().Format(timeFormatV2)
|
||||
}
|
||||
|
||||
const timeFormatV2 = "2006-01-02T15:04:05"
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
package awsauth
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func TestSignature2(t *testing.T) {
|
||||
// http://docs.aws.amazon.com/general/latest/gr/signature-version-2.html
|
||||
|
||||
Convey("Given bogus credentials", t, func() {
|
||||
keys := *testCredV2
|
||||
|
||||
// Mock time
|
||||
now = func() time.Time {
|
||||
parsed, _ := time.Parse(timeFormatV2, exampleReqTsV2)
|
||||
return parsed
|
||||
}
|
||||
|
||||
Convey("Given a plain request that is unprepared", func() {
|
||||
request := test_plainRequestV2()
|
||||
|
||||
Convey("The request should be prepared to be signed", func() {
|
||||
expectedUnsigned := test_unsignedRequestV2()
|
||||
prepareRequestV2(request, keys)
|
||||
So(request, ShouldResemble, expectedUnsigned)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Given a prepared, but unsigned, request", func() {
|
||||
request := test_unsignedRequestV2()
|
||||
|
||||
Convey("The canonical query string should be correct", func() {
|
||||
actual := canonicalQueryStringV2(request)
|
||||
expected := canonicalQsV2
|
||||
So(actual, ShouldEqual, expected)
|
||||
})
|
||||
|
||||
Convey("The absolute path should be extracted correctly", func() {
|
||||
So(request.URL.Path, ShouldEqual, "/")
|
||||
})
|
||||
|
||||
Convey("The string to sign should be well-formed", func() {
|
||||
actual := stringToSignV2(request)
|
||||
So(actual, ShouldEqual, expectedStringToSignV2)
|
||||
})
|
||||
|
||||
Convey("The resulting signature should be correct", func() {
|
||||
actual := signatureV2(stringToSignV2(request), keys)
|
||||
So(actual, ShouldEqual, "i91nKc4PWAt0JJIdXwz9HxZCJDdiy6cf/Mj6vPxyYIs=")
|
||||
})
|
||||
|
||||
Convey("The final signed request should be correctly formed", func() {
|
||||
Sign2(request, keys)
|
||||
actual := request.URL.String()
|
||||
So(actual, ShouldResemble, expectedFinalUrlV2)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestVersion2STSRequestPreparer(t *testing.T) {
|
||||
Convey("Given a plain request ", t, func() {
|
||||
request := test_plainRequestV2()
|
||||
|
||||
Convey("And a set of credentials with an STS token", func() {
|
||||
var keys Credentials
|
||||
keys = *testCredV2WithSTS
|
||||
|
||||
Convey("It should include the SecurityToken parameter when the request is signed", func() {
|
||||
actualSigned := Sign2(request, keys)
|
||||
actual := actualSigned.URL.Query()["SecurityToken"][0]
|
||||
|
||||
So(actual, ShouldNotBeBlank)
|
||||
So(actual, ShouldEqual, testCredV2WithSTS.SecurityToken)
|
||||
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func test_plainRequestV2() *http.Request {
|
||||
values := url.Values{}
|
||||
values.Set("Action", "DescribeJobFlows")
|
||||
values.Set("Version", "2009-03-31")
|
||||
|
||||
url := baseUrlV2 + "?" + values.Encode()
|
||||
|
||||
request, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return request
|
||||
}
|
||||
|
||||
func test_unsignedRequestV2() *http.Request {
|
||||
request := test_plainRequestV2()
|
||||
newUrl, _ := url.Parse(baseUrlV2 + "/?" + canonicalQsV2)
|
||||
request.URL = newUrl
|
||||
return request
|
||||
}
|
||||
|
||||
var (
|
||||
testCredV2 = &Credentials{
|
||||
AccessKeyID: "AKIAIOSFODNN7EXAMPLE",
|
||||
SecretAccessKey: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
|
||||
}
|
||||
|
||||
testCredV2WithSTS = &Credentials{
|
||||
AccessKeyID: "AKIDEXAMPLE",
|
||||
SecretAccessKey: "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
|
||||
SecurityToken: "AQoDYXdzEHcaoAJ1Aqwx1Sum0iW2NQjXJcWlKR7vuB6lnAeGBaQnjDRZPVyniwc48ml5hx+0qiXenVJdfusMMl9XLhSncfhx9Rb1UF8IAOaQ+CkpWXvoH67YYN+93dgckSVgVEBRByTl/BvLOZhe0ii/pOWkuQtBm5T7lBHRe4Dfmxy9X6hd8L3FrWxgnGV3fWZ3j0gASdYXaa+VBJlU0E2/GmCzn3T+t2mjYaeoInAnYVKVpmVMOrh6lNAeETTOHElLopblSa7TAmROq5xHIyu4a9i2qwjERTwa3Yk4Jk6q7JYVA5Cu7kS8wKVml8LdzzCTsy+elJgvH+Jf6ivpaHt/En0AJ5PZUJDev2+Y5+9j4AYfrmXfm4L73DC1ZJFJrv+Yh+EXAMPLE=",
|
||||
}
|
||||
|
||||
exampleReqTsV2 = "2011-10-03T15:19:30"
|
||||
baseUrlV2 = "https://elasticmapreduce.amazonaws.com"
|
||||
canonicalQsV2 = "AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE&Action=DescribeJobFlows&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2011-10-03T15%3A19%3A30&Version=2009-03-31"
|
||||
expectedStringToSignV2 = "GET\nelasticmapreduce.amazonaws.com\n/\n" + canonicalQsV2
|
||||
expectedFinalUrlV2 = baseUrlV2 + "/?AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE&Action=DescribeJobFlows&Signature=i91nKc4PWAt0JJIdXwz9HxZCJDdiy6cf%2FMj6vPxyYIs%3D&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2011-10-03T15%3A19%3A30&Version=2009-03-31"
|
||||
)
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
// Thanks to Michael Vierling for contributing sign3.go
|
||||
|
||||
package awsauth
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func stringToSignV3(request *http.Request) string {
|
||||
// TASK 1. http://docs.aws.amazon.com/Route53/latest/DeveloperGuide/RESTAuthentication.html#StringToSign
|
||||
|
||||
return request.Header.Get("Date") + request.Header.Get("x-amz-nonce")
|
||||
}
|
||||
|
||||
func signatureV3(stringToSign string, keys Credentials) string {
|
||||
// TASK 2. http://docs.aws.amazon.com/Route53/latest/DeveloperGuide/RESTAuthentication.html#Signature
|
||||
|
||||
hash := hmacSHA256([]byte(keys.SecretAccessKey), stringToSign)
|
||||
return base64.StdEncoding.EncodeToString(hash)
|
||||
}
|
||||
|
||||
func buildAuthHeaderV3(signature string, keys Credentials) string {
|
||||
// TASK 3. http://docs.aws.amazon.com/Route53/latest/DeveloperGuide/RESTAuthentication.html#AuthorizationHeader
|
||||
|
||||
return "AWS3-HTTPS AWSAccessKeyId=" + keys.AccessKeyID +
|
||||
", Algorithm=HmacSHA256" +
|
||||
", Signature=" + signature
|
||||
}
|
||||
|
||||
func prepareRequestV3(request *http.Request) *http.Request {
|
||||
ts := timestampV3()
|
||||
necessaryDefaults := map[string]string{
|
||||
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
|
||||
"x-amz-date": ts,
|
||||
"Date": ts,
|
||||
"x-amz-nonce": "",
|
||||
}
|
||||
|
||||
for header, value := range necessaryDefaults {
|
||||
if request.Header.Get(header) == "" {
|
||||
request.Header.Set(header, value)
|
||||
}
|
||||
}
|
||||
|
||||
if request.URL.Path == "" {
|
||||
request.URL.Path += "/"
|
||||
}
|
||||
|
||||
return request
|
||||
}
|
||||
|
||||
func timestampV3() string {
|
||||
return now().Format(timeFormatV3)
|
||||
}
|
||||
|
||||
const timeFormatV3 = time.RFC1123
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
package awsauth
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func TestSignature3(t *testing.T) {
|
||||
// http://docs.aws.amazon.com/Route53/latest/DeveloperGuide/RESTAuthentication.html
|
||||
// http://docs.aws.amazon.com/ses/latest/DeveloperGuide/query-interface-authentication.html
|
||||
|
||||
Convey("Given bogus credentials", t, func() {
|
||||
keys := *testCredV3
|
||||
|
||||
// Mock time
|
||||
now = func() time.Time {
|
||||
parsed, _ := time.Parse(timeFormatV3, exampleReqTsV3)
|
||||
return parsed
|
||||
}
|
||||
|
||||
Convey("Given a plain request that is unprepared", func() {
|
||||
request := test_plainRequestV3()
|
||||
|
||||
Convey("The request should be prepared to be signed", func() {
|
||||
expectedUnsigned := test_unsignedRequestV3()
|
||||
prepareRequestV3(request)
|
||||
So(request, ShouldResemble, expectedUnsigned)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Given a prepared, but unsigned, request", func() {
|
||||
request := test_unsignedRequestV3()
|
||||
|
||||
Convey("The absolute path should be extracted correctly", func() {
|
||||
So(request.URL.Path, ShouldEqual, "/")
|
||||
})
|
||||
|
||||
Convey("The string to sign should be well-formed", func() {
|
||||
actual := stringToSignV3(request)
|
||||
So(actual, ShouldEqual, expectedStringToSignV3)
|
||||
})
|
||||
|
||||
Convey("The resulting signature should be correct", func() {
|
||||
actual := signatureV3(stringToSignV3(request), keys)
|
||||
So(actual, ShouldEqual, "PjAJ6buiV6l4WyzmmuwtKE59NJXVg5Dr3Sn4PCMZ0Yk=")
|
||||
})
|
||||
|
||||
Convey("The final signed request should be correctly formed", func() {
|
||||
Sign3(request, keys)
|
||||
actual := request.Header.Get("X-Amzn-Authorization")
|
||||
So(actual, ShouldResemble, expectedAuthHeaderV3)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func test_plainRequestV3() *http.Request {
|
||||
values := url.Values{}
|
||||
values.Set("Action", "GetSendStatistics")
|
||||
values.Set("Version", "2010-12-01")
|
||||
|
||||
url := baseUrlV3 + "/?" + values.Encode()
|
||||
|
||||
request, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return request
|
||||
}
|
||||
|
||||
func test_unsignedRequestV3() *http.Request {
|
||||
request := test_plainRequestV3()
|
||||
request.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=utf-8")
|
||||
request.Header.Set("x-amz-date", exampleReqTsV3)
|
||||
request.Header.Set("Date", exampleReqTsV3)
|
||||
request.Header.Set("x-amz-nonce", "")
|
||||
return request
|
||||
}
|
||||
|
||||
func TestVersion3STSRequestPreparer(t *testing.T) {
|
||||
Convey("Given a plain request with no custom headers", t, func() {
|
||||
request := test_plainRequestV3()
|
||||
|
||||
Convey("And a set of credentials with an STS token", func() {
|
||||
var keys Credentials
|
||||
keys = *testCredV3WithSTS
|
||||
|
||||
Convey("It should include an X-Amz-Security-Token when the request is signed", func() {
|
||||
actualSigned := Sign3(request, keys)
|
||||
actual := actualSigned.Header.Get("X-Amz-Security-Token")
|
||||
|
||||
So(actual, ShouldNotBeBlank)
|
||||
So(actual, ShouldEqual, testCredV4WithSTS.SecurityToken)
|
||||
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
var (
|
||||
testCredV3 = &Credentials{
|
||||
AccessKeyID: "AKIAIOSFODNN7EXAMPLE",
|
||||
SecretAccessKey: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
|
||||
}
|
||||
|
||||
testCredV3WithSTS = &Credentials{
|
||||
AccessKeyID: "AKIDEXAMPLE",
|
||||
SecretAccessKey: "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
|
||||
SecurityToken: "AQoDYXdzEHcaoAJ1Aqwx1Sum0iW2NQjXJcWlKR7vuB6lnAeGBaQnjDRZPVyniwc48ml5hx+0qiXenVJdfusMMl9XLhSncfhx9Rb1UF8IAOaQ+CkpWXvoH67YYN+93dgckSVgVEBRByTl/BvLOZhe0ii/pOWkuQtBm5T7lBHRe4Dfmxy9X6hd8L3FrWxgnGV3fWZ3j0gASdYXaa+VBJlU0E2/GmCzn3T+t2mjYaeoInAnYVKVpmVMOrh6lNAeETTOHElLopblSa7TAmROq5xHIyu4a9i2qwjERTwa3Yk4Jk6q7JYVA5Cu7kS8wKVml8LdzzCTsy+elJgvH+Jf6ivpaHt/En0AJ5PZUJDev2+Y5+9j4AYfrmXfm4L73DC1ZJFJrv+Yh+EXAMPLE=",
|
||||
}
|
||||
|
||||
exampleReqTsV3 = "Thu, 14 Aug 2008 17:08:48 GMT"
|
||||
baseUrlV3 = "https://email.us-east-1.amazonaws.com"
|
||||
expectedStringToSignV3 = exampleReqTsV3
|
||||
expectedAuthHeaderV3 = "AWS3-HTTPS AWSAccessKeyId=" + testCredV3.AccessKeyID + ", Algorithm=HmacSHA256, Signature=PjAJ6buiV6l4WyzmmuwtKE59NJXVg5Dr3Sn4PCMZ0Yk="
|
||||
)
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
package awsauth
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func hashedCanonicalRequestV4(request *http.Request, meta *metadata) string {
|
||||
// TASK 1. http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
|
||||
|
||||
payload := readAndReplaceBody(request)
|
||||
payloadHash := hashSHA256(payload)
|
||||
request.Header.Set("X-Amz-Content-Sha256", payloadHash)
|
||||
|
||||
// Set this in header values to make it appear in the range of headers to sign
|
||||
request.Header.Set("Host", request.Host)
|
||||
|
||||
var sortedHeaderKeys []string
|
||||
for key, _ := range request.Header {
|
||||
switch key {
|
||||
case "Content-Type", "Content-Md5", "Host":
|
||||
default:
|
||||
if !strings.HasPrefix(key, "X-Amz-") {
|
||||
continue
|
||||
}
|
||||
}
|
||||
sortedHeaderKeys = append(sortedHeaderKeys, strings.ToLower(key))
|
||||
}
|
||||
sort.Strings(sortedHeaderKeys)
|
||||
|
||||
var headersToSign string
|
||||
for _, key := range sortedHeaderKeys {
|
||||
value := strings.TrimSpace(request.Header.Get(key))
|
||||
if key == "host" {
|
||||
//AWS does not include port in signing request.
|
||||
if strings.Contains(value, ":") {
|
||||
split := strings.Split(value, ":")
|
||||
port := split[1]
|
||||
if port == "80" || port == "443" {
|
||||
value = split[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
headersToSign += key + ":" + value + "\n"
|
||||
}
|
||||
meta.signedHeaders = concat(";", sortedHeaderKeys...)
|
||||
canonicalRequest := concat("\n", request.Method, normuri(request.URL.Path), normquery(request.URL.Query()), headersToSign, meta.signedHeaders, payloadHash)
|
||||
|
||||
return hashSHA256([]byte(canonicalRequest))
|
||||
}
|
||||
|
||||
func stringToSignV4(request *http.Request, hashedCanonReq string, meta *metadata) string {
|
||||
// TASK 2. http://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html
|
||||
|
||||
requestTs := request.Header.Get("X-Amz-Date")
|
||||
|
||||
meta.algorithm = "AWS4-HMAC-SHA256"
|
||||
meta.service, meta.region = serviceAndRegion(request.Host)
|
||||
meta.date = tsDateV4(requestTs)
|
||||
meta.credentialScope = concat("/", meta.date, meta.region, meta.service, "aws4_request")
|
||||
|
||||
return concat("\n", meta.algorithm, requestTs, meta.credentialScope, hashedCanonReq)
|
||||
}
|
||||
|
||||
func signatureV4(signingKey []byte, stringToSign string) string {
|
||||
// TASK 3. http://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
|
||||
|
||||
return hex.EncodeToString(hmacSHA256(signingKey, stringToSign))
|
||||
}
|
||||
|
||||
func prepareRequestV4(request *http.Request) *http.Request {
|
||||
necessaryDefaults := map[string]string{
|
||||
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
|
||||
"X-Amz-Date": timestampV4(),
|
||||
}
|
||||
|
||||
for header, value := range necessaryDefaults {
|
||||
if request.Header.Get(header) == "" {
|
||||
request.Header.Set(header, value)
|
||||
}
|
||||
}
|
||||
|
||||
if request.URL.Path == "" {
|
||||
request.URL.Path += "/"
|
||||
}
|
||||
|
||||
return request
|
||||
}
|
||||
|
||||
func signingKeyV4(secretKey, date, region, service string) []byte {
|
||||
kDate := hmacSHA256([]byte("AWS4"+secretKey), date)
|
||||
kRegion := hmacSHA256(kDate, region)
|
||||
kService := hmacSHA256(kRegion, service)
|
||||
kSigning := hmacSHA256(kService, "aws4_request")
|
||||
return kSigning
|
||||
}
|
||||
|
||||
func buildAuthHeaderV4(signature string, meta *metadata, keys Credentials) string {
|
||||
credential := keys.AccessKeyID + "/" + meta.credentialScope
|
||||
|
||||
return meta.algorithm +
|
||||
" Credential=" + credential +
|
||||
", SignedHeaders=" + meta.signedHeaders +
|
||||
", Signature=" + signature
|
||||
}
|
||||
|
||||
func timestampV4() string {
|
||||
return now().Format(timeFormatV4)
|
||||
}
|
||||
|
||||
func tsDateV4(timestamp string) string {
|
||||
return timestamp[:8]
|
||||
}
|
||||
|
||||
const timeFormatV4 = "20060102T150405Z"
|
||||
+217
@@ -0,0 +1,217 @@
|
||||
package awsauth
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func TestVersion4RequestPreparer(t *testing.T) {
|
||||
Convey("Given a plain request with no custom headers", t, func() {
|
||||
request := test_plainRequestV4(false)
|
||||
|
||||
expectedUnsigned := test_unsignedRequestV4(true, false)
|
||||
expectedUnsigned.Header.Set("X-Amz-Date", timestampV4())
|
||||
|
||||
Convey("The necessary, default headers should be appended", func() {
|
||||
prepareRequestV4(request)
|
||||
So(request, ShouldResemble, expectedUnsigned)
|
||||
})
|
||||
|
||||
Convey("Forward-slash should be appended to URI if not present", func() {
|
||||
prepareRequestV4(request)
|
||||
So(request.URL.Path, ShouldEqual, "/")
|
||||
})
|
||||
|
||||
Convey("And a set of credentials", func() {
|
||||
var keys Credentials
|
||||
keys = *testCredV4
|
||||
|
||||
Convey("It should be signed with an Authorization header", func() {
|
||||
actualSigned := Sign4(request, keys)
|
||||
actual := actualSigned.Header.Get("Authorization")
|
||||
|
||||
So(actual, ShouldNotBeBlank)
|
||||
So(actual, ShouldContainSubstring, "Credential="+testCredV4.AccessKeyID)
|
||||
So(actual, ShouldContainSubstring, "SignedHeaders=")
|
||||
So(actual, ShouldContainSubstring, "Signature=")
|
||||
So(actual, ShouldContainSubstring, "AWS4")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Given a request with custom, necessary headers", t, func() {
|
||||
Convey("The custom, necessary headers must not be changed", func() {
|
||||
request := test_unsignedRequestV4(true, false)
|
||||
prepareRequestV4(request)
|
||||
So(request, ShouldResemble, test_unsignedRequestV4(true, false))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestVersion4STSRequestPreparer(t *testing.T) {
|
||||
Convey("Given a plain request with no custom headers", t, func() {
|
||||
request := test_plainRequestV4(false)
|
||||
|
||||
Convey("And a set of credentials with an STS token", func() {
|
||||
var keys Credentials
|
||||
keys = *testCredV4WithSTS
|
||||
|
||||
Convey("It should include an X-Amz-Security-Token when the request is signed", func() {
|
||||
actualSigned := Sign4(request, keys)
|
||||
actual := actualSigned.Header.Get("X-Amz-Security-Token")
|
||||
|
||||
So(actual, ShouldNotBeBlank)
|
||||
So(actual, ShouldEqual, testCredV4WithSTS.SecurityToken)
|
||||
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestVersion4SigningTasks(t *testing.T) {
|
||||
// http://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html
|
||||
|
||||
Convey("Given a bogus request and credentials from AWS documentation with an additional meta tag", t, func() {
|
||||
request := test_unsignedRequestV4(true, true)
|
||||
meta := new(metadata)
|
||||
|
||||
Convey("(Task 1) The canonical request should be built correctly", func() {
|
||||
hashedCanonReq := hashedCanonicalRequestV4(request, meta)
|
||||
|
||||
So(hashedCanonReq, ShouldEqual, expectingV4["CanonicalHash"])
|
||||
})
|
||||
|
||||
Convey("(Task 2) The string to sign should be built correctly", func() {
|
||||
hashedCanonReq := hashedCanonicalRequestV4(request, meta)
|
||||
stringToSign := stringToSignV4(request, hashedCanonReq, meta)
|
||||
|
||||
So(stringToSign, ShouldEqual, expectingV4["StringToSign"])
|
||||
})
|
||||
|
||||
Convey("(Task 3) The version 4 signed signature should be correct", func() {
|
||||
hashedCanonReq := hashedCanonicalRequestV4(request, meta)
|
||||
stringToSign := stringToSignV4(request, hashedCanonReq, meta)
|
||||
signature := signatureV4(test_signingKeyV4(), stringToSign)
|
||||
|
||||
So(signature, ShouldEqual, expectingV4["SignatureV4"])
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestSignature4Helpers(t *testing.T) {
|
||||
|
||||
keys := *testCredV4
|
||||
|
||||
Convey("The signing key should be properly generated", t, func() {
|
||||
expected := []byte{152, 241, 216, 137, 254, 196, 244, 66, 26, 220, 82, 43, 171, 12, 225, 248, 46, 105, 41, 194, 98, 237, 21, 229, 169, 76, 144, 239, 209, 227, 176, 231}
|
||||
actual := test_signingKeyV4()
|
||||
|
||||
So(actual, ShouldResemble, expected)
|
||||
})
|
||||
|
||||
Convey("Authorization headers should be built properly", t, func() {
|
||||
meta := &metadata{
|
||||
algorithm: "AWS4-HMAC-SHA256",
|
||||
credentialScope: "20110909/us-east-1/iam/aws4_request",
|
||||
signedHeaders: "content-type;host;x-amz-date",
|
||||
}
|
||||
expected := expectingV4["AuthHeader"] + expectingV4["SignatureV4"]
|
||||
actual := buildAuthHeaderV4(expectingV4["SignatureV4"], meta, keys)
|
||||
|
||||
So(actual, ShouldEqual, expected)
|
||||
})
|
||||
|
||||
Convey("Timestamps should be in the correct format, in UTC time", t, func() {
|
||||
actual := timestampV4()
|
||||
|
||||
So(len(actual), ShouldEqual, 16)
|
||||
So(actual, ShouldNotContainSubstring, ":")
|
||||
So(actual, ShouldNotContainSubstring, "-")
|
||||
So(actual, ShouldNotContainSubstring, " ")
|
||||
So(actual, ShouldEndWith, "Z")
|
||||
So(actual, ShouldContainSubstring, "T")
|
||||
})
|
||||
|
||||
Convey("Given an Version 4 AWS-formatted timestamp", t, func() {
|
||||
ts := "20110909T233600Z"
|
||||
|
||||
Convey("The date string should be extracted properly", func() {
|
||||
So(tsDateV4(ts), ShouldEqual, "20110909")
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Given any request with a body", t, func() {
|
||||
request := test_plainRequestV4(false)
|
||||
|
||||
Convey("Its body should be read and replaced without differences", func() {
|
||||
expected := []byte(requestValuesV4.Encode())
|
||||
|
||||
actual1 := readAndReplaceBody(request)
|
||||
So(actual1, ShouldResemble, expected)
|
||||
|
||||
actual2 := readAndReplaceBody(request)
|
||||
So(actual2, ShouldResemble, expected)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func test_plainRequestV4(trailingSlash bool) *http.Request {
|
||||
url := "http://iam.amazonaws.com"
|
||||
body := strings.NewReader(requestValuesV4.Encode())
|
||||
|
||||
if trailingSlash {
|
||||
url += "/"
|
||||
}
|
||||
|
||||
request, err := http.NewRequest("POST", url, body)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return request
|
||||
}
|
||||
|
||||
func test_unsignedRequestV4(trailingSlash, tag bool) *http.Request {
|
||||
request := test_plainRequestV4(trailingSlash)
|
||||
request.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=utf-8")
|
||||
request.Header.Set("X-Amz-Date", "20110909T233600Z")
|
||||
if tag {
|
||||
request.Header.Set("X-Amz-Meta-Foo", "Bar!")
|
||||
}
|
||||
return request
|
||||
}
|
||||
|
||||
func test_signingKeyV4() []byte {
|
||||
return signingKeyV4(testCredV4.SecretAccessKey, "20110909", "us-east-1", "iam")
|
||||
}
|
||||
|
||||
var (
|
||||
testCredV4 = &Credentials{
|
||||
AccessKeyID: "AKIDEXAMPLE",
|
||||
SecretAccessKey: "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
|
||||
}
|
||||
|
||||
testCredV4WithSTS = &Credentials{
|
||||
AccessKeyID: "AKIDEXAMPLE",
|
||||
SecretAccessKey: "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
|
||||
SecurityToken: "AQoDYXdzEHcaoAJ1Aqwx1Sum0iW2NQjXJcWlKR7vuB6lnAeGBaQnjDRZPVyniwc48ml5hx+0qiXenVJdfusMMl9XLhSncfhx9Rb1UF8IAOaQ+CkpWXvoH67YYN+93dgckSVgVEBRByTl/BvLOZhe0ii/pOWkuQtBm5T7lBHRe4Dfmxy9X6hd8L3FrWxgnGV3fWZ3j0gASdYXaa+VBJlU0E2/GmCzn3T+t2mjYaeoInAnYVKVpmVMOrh6lNAeETTOHElLopblSa7TAmROq5xHIyu4a9i2qwjERTwa3Yk4Jk6q7JYVA5Cu7kS8wKVml8LdzzCTsy+elJgvH+Jf6ivpaHt/En0AJ5PZUJDev2+Y5+9j4AYfrmXfm4L73DC1ZJFJrv+Yh+EXAMPLE=",
|
||||
}
|
||||
|
||||
expectingV4 = map[string]string{
|
||||
"CanonicalHash": "41c56ed0df12052f7c10407a809e64cd61a4b0471956cdea28d6d1bb904f5d92",
|
||||
"StringToSign": "AWS4-HMAC-SHA256\n20110909T233600Z\n20110909/us-east-1/iam/aws4_request\n41c56ed0df12052f7c10407a809e64cd61a4b0471956cdea28d6d1bb904f5d92",
|
||||
"SignatureV4": "08292a4b86aae1a6f80f1988182a33cbf73ccc70c5da505303e355a67cc64cb4",
|
||||
"AuthHeader": "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-amz-date, Signature=",
|
||||
}
|
||||
|
||||
requestValuesV4 = &url.Values{
|
||||
"Action": []string{"ListUsers"},
|
||||
"Version": []string{"2010-05-08"},
|
||||
}
|
||||
)
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Andrey Smirnov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
go-ftp-protocol
|
||||
===============
|
||||
|
||||
Plugin for http.Transport with support for ftp:// protocol in Go.
|
||||
|
||||
Limitations: only anonymous FTP servers, only file retrieval operations.
|
||||
|
||||
Internally connections to FTP servers are cached and re-used when possible.
|
||||
|
||||
Example usage:
|
||||
|
||||
import "github.com/smira/go-ftp-protocol/protocol"
|
||||
|
||||
transport := &http.Transport{}
|
||||
transport.RegisterProtocol("ftp", &protocol.FTPRoundTripper{})
|
||||
|
||||
client := &http.Client{Transport: transport}
|
||||
|
||||
resp, err := client.Get("ftp://ftp.ru.debian.org/debian/README")
|
||||
|
||||
License: MIT
|
||||
|
||||
Base on FTP client library: http://github.com/jlaffaye/ftp/
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
// Package protocol implements ftp:// scheme plugin for http.Transport
|
||||
//
|
||||
// github.com/jlaffaye/ftp library is used internally as FTP client implementation.
|
||||
//
|
||||
// Limitations: only anonymous FTP servers, only file retrieval operations.
|
||||
//
|
||||
// Internally connections to FTP servers are cached and re-used when possible.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// transport := &http.Transport{}
|
||||
// transport.RegisterProtocol("ftp", &FTPRoundTripper{})
|
||||
// client := &http.Client{Transport: transport}
|
||||
// resp, err := client.Get("ftp://ftp.ru.debian.org/debian/README")
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/jlaffaye/ftp"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// FTPRoundTripper is an implementation of net/http.RoundTripper on top of FTP client
|
||||
type FTPRoundTripper struct {
|
||||
lock sync.Mutex
|
||||
idleConnections map[string][]*ftp.ServerConn
|
||||
}
|
||||
|
||||
// verify interface
|
||||
var (
|
||||
_ http.RoundTripper = &FTPRoundTripper{}
|
||||
)
|
||||
|
||||
type readCloserWrapper struct {
|
||||
body io.ReadCloser
|
||||
rt *FTPRoundTripper
|
||||
hostport string
|
||||
conn *ftp.ServerConn
|
||||
}
|
||||
|
||||
func (w *readCloserWrapper) Read(p []byte) (n int, err error) {
|
||||
return w.body.Read(p)
|
||||
}
|
||||
|
||||
func (w *readCloserWrapper) Close() error {
|
||||
err := w.body.Close()
|
||||
if err == nil {
|
||||
w.rt.putConnection(w.hostport, w.conn)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (rt *FTPRoundTripper) getConnection(hostport string) (conn *ftp.ServerConn, err error) {
|
||||
rt.lock.Lock()
|
||||
conns, ok := rt.idleConnections[hostport]
|
||||
if ok && len(conns) > 0 {
|
||||
conn = conns[0]
|
||||
rt.idleConnections[hostport] = conns[1:]
|
||||
rt.lock.Unlock()
|
||||
return
|
||||
}
|
||||
rt.lock.Unlock()
|
||||
|
||||
conn, err = ftp.Connect(hostport)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = conn.Login("anonymous", "anonymous")
|
||||
if err != nil {
|
||||
conn.Quit()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (rt *FTPRoundTripper) putConnection(hostport string, conn *ftp.ServerConn) {
|
||||
rt.lock.Lock()
|
||||
defer rt.lock.Unlock()
|
||||
|
||||
if rt.idleConnections == nil {
|
||||
rt.idleConnections = make(map[string][]*ftp.ServerConn)
|
||||
}
|
||||
|
||||
rt.idleConnections[hostport] = append(rt.idleConnections[hostport], conn)
|
||||
}
|
||||
|
||||
// RoundTrip parses incoming GET "HTTP" request and transforms it into
|
||||
// commands to ftp client
|
||||
func (rt *FTPRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) {
|
||||
if request.URL.Scheme != "ftp" {
|
||||
return nil, fmt.Errorf("only ftp protocol is supported, got %s", request.Proto)
|
||||
}
|
||||
|
||||
if request.Method != "GET" {
|
||||
return nil, fmt.Errorf("only GET method is supported, got %s", request.Method)
|
||||
}
|
||||
|
||||
hostport := request.URL.Host
|
||||
if strings.Index(hostport, ":") == -1 {
|
||||
hostport = hostport + ":21"
|
||||
}
|
||||
|
||||
connection, err := rt.getConnection(hostport)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var body io.ReadCloser
|
||||
body, err = connection.Retr(request.URL.Path)
|
||||
|
||||
if err != nil {
|
||||
if te, ok := err.(*textproto.Error); ok {
|
||||
rt.putConnection(hostport, connection)
|
||||
|
||||
if te.Code == ftp.StatusFileUnavailable {
|
||||
return &http.Response{
|
||||
Status: "404 Not Found",
|
||||
StatusCode: 404,
|
||||
Proto: "FTP/1.0",
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 0,
|
||||
Request: request,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &http.Response{
|
||||
Status: "200 OK",
|
||||
StatusCode: 200,
|
||||
Proto: "FTP/1.0",
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 0,
|
||||
Body: &readCloserWrapper{
|
||||
body: body,
|
||||
rt: rt,
|
||||
hostport: hostport,
|
||||
conn: connection,
|
||||
},
|
||||
ContentLength: -1,
|
||||
Request: request,
|
||||
}, nil
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestProtocol(t *testing.T) {
|
||||
transport := &http.Transport{}
|
||||
transport.RegisterProtocol("ftp", &FTPRoundTripper{})
|
||||
|
||||
client := &http.Client{Transport: transport}
|
||||
|
||||
resp, err := client.Get("ftp://ftp.ru.debian.org/debian/README")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
t.Fatalf("resp.StatusCode 200 != %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
content, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = resp.Body.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(string(content), "See http://www.debian.org/ for information about Debian GNU/Linux.") {
|
||||
t.Fatalf("unexpected content: %s", content)
|
||||
}
|
||||
|
||||
resp, err = client.Get("ftp://ftp.ru.debian.org/debian/missing")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != 404 {
|
||||
t.Fatalf("resp.StatusCode 404 != %d", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConcurrent(t *testing.T) {
|
||||
transport := &http.Transport{}
|
||||
transport.RegisterProtocol("ftp", &FTPRoundTripper{})
|
||||
|
||||
client := &http.Client{Transport: transport}
|
||||
|
||||
const concurrency = 4
|
||||
const count = 10
|
||||
|
||||
done := make(chan struct{}, concurrency)
|
||||
|
||||
for i := 0; i < concurrency; i++ {
|
||||
go func() {
|
||||
defer func() { done <- struct{}{} }()
|
||||
|
||||
for j := 0; j < 10; j++ {
|
||||
resp, err := client.Get("ftp://ftp.ru.debian.org/debian/README")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
t.Fatalf("resp.StatusCode 200 != %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
content, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = resp.Body.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(string(content), "See http://www.debian.org/ for information about Debian GNU/Linux.") {
|
||||
t.Fatalf("unexpected content: %s", content)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
for i := 0; i < concurrency; i++ {
|
||||
<-done
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
Paul Borman <borman@google.com>
|
||||
+1
@@ -0,0 +1 @@
|
||||
defaultcc: borman@google.com
|
||||
+3291
File diff suppressed because it is too large.
Load diff
+27
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2009,2014 Google Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
// Copyright 2011 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// A Domain represents a Version 2 domain
|
||||
type Domain byte
|
||||
|
||||
// Domain constants for DCE Security (Version 2) UUIDs.
|
||||
const (
|
||||
Person = Domain(0)
|
||||
Group = Domain(1)
|
||||
Org = Domain(2)
|
||||
)
|
||||
|
||||
// NewDCESecurity returns a DCE Security (Version 2) UUID.
|
||||
//
|
||||
// The domain should be one of Person, Group or Org.
|
||||
// On a POSIX system the id should be the users UID for the Person
|
||||
// domain and the users GID for the Group. The meaning of id for
|
||||
// the domain Org or on non-POSIX systems is site defined.
|
||||
//
|
||||
// For a given domain/id pair the same token may be returned for up to
|
||||
// 7 minutes and 10 seconds.
|
||||
func NewDCESecurity(domain Domain, id uint32) UUID {
|
||||
uuid := NewUUID()
|
||||
if uuid != nil {
|
||||
uuid[6] = (uuid[6] & 0x0f) | 0x20 // Version 2
|
||||
uuid[9] = byte(domain)
|
||||
binary.BigEndian.PutUint32(uuid[0:], id)
|
||||
}
|
||||
return uuid
|
||||
}
|
||||
|
||||
// NewDCEPerson returns a DCE Security (Version 2) UUID in the person
|
||||
// domain with the id returned by os.Getuid.
|
||||
//
|
||||
// NewDCEPerson(Person, uint32(os.Getuid()))
|
||||
func NewDCEPerson() UUID {
|
||||
return NewDCESecurity(Person, uint32(os.Getuid()))
|
||||
}
|
||||
|
||||
// NewDCEGroup returns a DCE Security (Version 2) UUID in the group
|
||||
// domain with the id returned by os.Getgid.
|
||||
//
|
||||
// NewDCEGroup(Group, uint32(os.Getgid()))
|
||||
func NewDCEGroup() UUID {
|
||||
return NewDCESecurity(Group, uint32(os.Getgid()))
|
||||
}
|
||||
|
||||
// Domain returns the domain for a Version 2 UUID or false.
|
||||
func (uuid UUID) Domain() (Domain, bool) {
|
||||
if v, _ := uuid.Version(); v != 2 {
|
||||
return 0, false
|
||||
}
|
||||
return Domain(uuid[9]), true
|
||||
}
|
||||
|
||||
// Id returns the id for a Version 2 UUID or false.
|
||||
func (uuid UUID) Id() (uint32, bool) {
|
||||
if v, _ := uuid.Version(); v != 2 {
|
||||
return 0, false
|
||||
}
|
||||
return binary.BigEndian.Uint32(uuid[0:4]), true
|
||||
}
|
||||
|
||||
func (d Domain) String() string {
|
||||
switch d {
|
||||
case Person:
|
||||
return "Person"
|
||||
case Group:
|
||||
return "Group"
|
||||
case Org:
|
||||
return "Org"
|
||||
}
|
||||
return fmt.Sprintf("Domain%d", int(d))
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// Copyright 2011 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// The uuid package generates and inspects UUIDs.
|
||||
//
|
||||
// UUIDs are based on RFC 4122 and DCE 1.1: Authentication and Security Services.
|
||||
package uuid
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
// Copyright 2011 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"hash"
|
||||
)
|
||||
|
||||
// Well known Name Space IDs and UUIDs
|
||||
var (
|
||||
NameSpace_DNS = Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8")
|
||||
NameSpace_URL = Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8")
|
||||
NameSpace_OID = Parse("6ba7b812-9dad-11d1-80b4-00c04fd430c8")
|
||||
NameSpace_X500 = Parse("6ba7b814-9dad-11d1-80b4-00c04fd430c8")
|
||||
NIL = Parse("00000000-0000-0000-0000-000000000000")
|
||||
)
|
||||
|
||||
// NewHash returns a new UUID dervied from the hash of space concatenated with
|
||||
// data generated by h. The hash should be at least 16 byte in length. The
|
||||
// first 16 bytes of the hash are used to form the UUID. The version of the
|
||||
// UUID will be the lower 4 bits of version. NewHash is used to implement
|
||||
// NewMD5 and NewSHA1.
|
||||
func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID {
|
||||
h.Reset()
|
||||
h.Write(space)
|
||||
h.Write([]byte(data))
|
||||
s := h.Sum(nil)
|
||||
uuid := make([]byte, 16)
|
||||
copy(uuid, s)
|
||||
uuid[6] = (uuid[6] & 0x0f) | uint8((version&0xf)<<4)
|
||||
uuid[8] = (uuid[8] & 0x3f) | 0x80 // RFC 4122 variant
|
||||
return uuid
|
||||
}
|
||||
|
||||
// NewMD5 returns a new MD5 (Version 3) UUID based on the
|
||||
// supplied name space and data.
|
||||
//
|
||||
// NewHash(md5.New(), space, data, 3)
|
||||
func NewMD5(space UUID, data []byte) UUID {
|
||||
return NewHash(md5.New(), space, data, 3)
|
||||
}
|
||||
|
||||
// NewSHA1 returns a new SHA1 (Version 5) UUID based on the
|
||||
// supplied name space and data.
|
||||
//
|
||||
// NewHash(sha1.New(), space, data, 5)
|
||||
func NewSHA1(space UUID, data []byte) UUID {
|
||||
return NewHash(sha1.New(), space, data, 5)
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
// Copyright 2014 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import "errors"
|
||||
|
||||
func (u UUID) MarshalJSON() ([]byte, error) {
|
||||
if len(u) == 0 {
|
||||
return []byte(`""`), nil
|
||||
}
|
||||
return []byte(`"` + u.String() + `"`), nil
|
||||
}
|
||||
|
||||
func (u *UUID) UnmarshalJSON(data []byte) error {
|
||||
if len(data) == 0 || string(data) == `""` {
|
||||
return nil
|
||||
}
|
||||
if len(data) < 2 || data[0] != '"' || data[len(data)-1] != '"' {
|
||||
return errors.New("invalid UUID format")
|
||||
}
|
||||
data = data[1 : len(data)-1]
|
||||
uu := Parse(string(data))
|
||||
if uu == nil {
|
||||
return errors.New("invalid UUID format")
|
||||
}
|
||||
*u = uu
|
||||
return nil
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
// Copyright 2014 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var testUUID = Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479")
|
||||
|
||||
func TestJSON(t *testing.T) {
|
||||
type S struct {
|
||||
ID1 UUID
|
||||
ID2 UUID
|
||||
}
|
||||
s1 := S{ID1: testUUID}
|
||||
data, err := json.Marshal(&s1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var s2 S
|
||||
if err := json.Unmarshal(data, &s2); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(&s1, &s2) {
|
||||
t.Errorf("got %#v, want %#v", s2, s1)
|
||||
}
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
// Copyright 2011 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import "net"
|
||||
|
||||
var (
|
||||
interfaces []net.Interface // cached list of interfaces
|
||||
ifname string // name of interface being used
|
||||
nodeID []byte // hardware for version 1 UUIDs
|
||||
)
|
||||
|
||||
// NodeInterface returns the name of the interface from which the NodeID was
|
||||
// derived. The interface "user" is returned if the NodeID was set by
|
||||
// SetNodeID.
|
||||
func NodeInterface() string {
|
||||
return ifname
|
||||
}
|
||||
|
||||
// SetNodeInterface selects the hardware address to be used for Version 1 UUIDs.
|
||||
// If name is "" then the first usable interface found will be used or a random
|
||||
// Node ID will be generated. If a named interface cannot be found then false
|
||||
// is returned.
|
||||
//
|
||||
// SetNodeInterface never fails when name is "".
|
||||
func SetNodeInterface(name string) bool {
|
||||
if interfaces == nil {
|
||||
var err error
|
||||
interfaces, err = net.Interfaces()
|
||||
if err != nil && name != "" {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for _, ifs := range interfaces {
|
||||
if len(ifs.HardwareAddr) >= 6 && (name == "" || name == ifs.Name) {
|
||||
if setNodeID(ifs.HardwareAddr) {
|
||||
ifname = ifs.Name
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We found no interfaces with a valid hardware address. If name
|
||||
// does not specify a specific interface generate a random Node ID
|
||||
// (section 4.1.6)
|
||||
if name == "" {
|
||||
if nodeID == nil {
|
||||
nodeID = make([]byte, 6)
|
||||
}
|
||||
randomBits(nodeID)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// NodeID returns a slice of a copy of the current Node ID, setting the Node ID
|
||||
// if not already set.
|
||||
func NodeID() []byte {
|
||||
if nodeID == nil {
|
||||
SetNodeInterface("")
|
||||
}
|
||||
nid := make([]byte, 6)
|
||||
copy(nid, nodeID)
|
||||
return nid
|
||||
}
|
||||
|
||||
// SetNodeID sets the Node ID to be used for Version 1 UUIDs. The first 6 bytes
|
||||
// of id are used. If id is less than 6 bytes then false is returned and the
|
||||
// Node ID is not set.
|
||||
func SetNodeID(id []byte) bool {
|
||||
if setNodeID(id) {
|
||||
ifname = "user"
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func setNodeID(id []byte) bool {
|
||||
if len(id) < 6 {
|
||||
return false
|
||||
}
|
||||
if nodeID == nil {
|
||||
nodeID = make([]byte, 6)
|
||||
}
|
||||
copy(nodeID, id)
|
||||
return true
|
||||
}
|
||||
|
||||
// NodeID returns the 6 byte node id encoded in uuid. It returns nil if uuid is
|
||||
// not valid. The NodeID is only well defined for version 1 and 2 UUIDs.
|
||||
func (uuid UUID) NodeID() []byte {
|
||||
if len(uuid) != 16 {
|
||||
return nil
|
||||
}
|
||||
node := make([]byte, 6)
|
||||
copy(node, uuid[10:])
|
||||
return node
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
// Copyright 2014 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// This test is only run when --regressions is passed on the go test line.
|
||||
var regressions = flag.Bool("regressions", false, "run uuid regression tests")
|
||||
|
||||
// TestClockSeqRace tests for a particular race condition of returning two
|
||||
// identical Version1 UUIDs. The duration of 1 minute was chosen as the race
|
||||
// condition, before being fixed, nearly always occured in under 30 seconds.
|
||||
func TestClockSeqRace(t *testing.T) {
|
||||
if !*regressions {
|
||||
t.Skip("skipping regression tests")
|
||||
}
|
||||
duration := time.Minute
|
||||
|
||||
done := make(chan struct{})
|
||||
defer close(done)
|
||||
|
||||
ch := make(chan UUID, 10000)
|
||||
ncpu := runtime.NumCPU()
|
||||
switch ncpu {
|
||||
case 0, 1:
|
||||
// We can't run the test effectively.
|
||||
t.Skip("skipping race test, only one CPU detected")
|
||||
return
|
||||
default:
|
||||
runtime.GOMAXPROCS(ncpu)
|
||||
}
|
||||
for i := 0; i < ncpu; i++ {
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
case ch <- NewUUID():
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
uuids := make(map[string]bool)
|
||||
cnt := 0
|
||||
start := time.Now()
|
||||
for u := range ch {
|
||||
s := u.String()
|
||||
if uuids[s] {
|
||||
t.Errorf("duplicate uuid after %d in %v: %s", cnt, time.Since(start), s)
|
||||
return
|
||||
}
|
||||
uuids[s] = true
|
||||
if time.Since(start) > duration {
|
||||
return
|
||||
}
|
||||
cnt++
|
||||
}
|
||||
}
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
// Copyright 2014 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// A Time represents a time as the number of 100's of nanoseconds since 15 Oct
|
||||
// 1582.
|
||||
type Time int64
|
||||
|
||||
const (
|
||||
lillian = 2299160 // Julian day of 15 Oct 1582
|
||||
unix = 2440587 // Julian day of 1 Jan 1970
|
||||
epoch = unix - lillian // Days between epochs
|
||||
g1582 = epoch * 86400 // seconds between epochs
|
||||
g1582ns100 = g1582 * 10000000 // 100s of a nanoseconds between epochs
|
||||
)
|
||||
|
||||
var (
|
||||
mu sync.Mutex
|
||||
lasttime uint64 // last time we returned
|
||||
clock_seq uint16 // clock sequence for this run
|
||||
|
||||
timeNow = time.Now // for testing
|
||||
)
|
||||
|
||||
// UnixTime converts t the number of seconds and nanoseconds using the Unix
|
||||
// epoch of 1 Jan 1970.
|
||||
func (t Time) UnixTime() (sec, nsec int64) {
|
||||
sec = int64(t - g1582ns100)
|
||||
nsec = (sec % 10000000) * 100
|
||||
sec /= 10000000
|
||||
return sec, nsec
|
||||
}
|
||||
|
||||
// GetTime returns the current Time (100s of nanoseconds since 15 Oct 1582) and
|
||||
// clock sequence as well as adjusting the clock sequence as needed. An error
|
||||
// is returned if the current time cannot be determined.
|
||||
func GetTime() (Time, uint16, error) {
|
||||
defer mu.Unlock()
|
||||
mu.Lock()
|
||||
return getTime()
|
||||
}
|
||||
|
||||
func getTime() (Time, uint16, error) {
|
||||
t := timeNow()
|
||||
|
||||
// If we don't have a clock sequence already, set one.
|
||||
if clock_seq == 0 {
|
||||
setClockSequence(-1)
|
||||
}
|
||||
now := uint64(t.UnixNano()/100) + g1582ns100
|
||||
|
||||
// If time has gone backwards with this clock sequence then we
|
||||
// increment the clock sequence
|
||||
if now <= lasttime {
|
||||
clock_seq = ((clock_seq + 1) & 0x3fff) | 0x8000
|
||||
}
|
||||
lasttime = now
|
||||
return Time(now), clock_seq, nil
|
||||
}
|
||||
|
||||
// ClockSequence returns the current clock sequence, generating one if not
|
||||
// already set. The clock sequence is only used for Version 1 UUIDs.
|
||||
//
|
||||
// The uuid package does not use global static storage for the clock sequence or
|
||||
// the last time a UUID was generated. Unless SetClockSequence a new random
|
||||
// clock sequence is generated the first time a clock sequence is requested by
|
||||
// ClockSequence, GetTime, or NewUUID. (section 4.2.1.1) sequence is generated
|
||||
// for
|
||||
func ClockSequence() int {
|
||||
defer mu.Unlock()
|
||||
mu.Lock()
|
||||
return clockSequence()
|
||||
}
|
||||
|
||||
func clockSequence() int {
|
||||
if clock_seq == 0 {
|
||||
setClockSequence(-1)
|
||||
}
|
||||
return int(clock_seq & 0x3fff)
|
||||
}
|
||||
|
||||
// SetClockSeq sets the clock sequence to the lower 14 bits of seq. Setting to
|
||||
// -1 causes a new sequence to be generated.
|
||||
func SetClockSequence(seq int) {
|
||||
defer mu.Unlock()
|
||||
mu.Lock()
|
||||
setClockSequence(seq)
|
||||
}
|
||||
|
||||
func setClockSequence(seq int) {
|
||||
if seq == -1 {
|
||||
var b [2]byte
|
||||
randomBits(b[:]) // clock sequence
|
||||
seq = int(b[0])<<8 | int(b[1])
|
||||
}
|
||||
old_seq := clock_seq
|
||||
clock_seq = uint16(seq&0x3fff) | 0x8000 // Set our variant
|
||||
if old_seq != clock_seq {
|
||||
lasttime = 0
|
||||
}
|
||||
}
|
||||
|
||||
// Time returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in
|
||||
// uuid. It returns false if uuid is not valid. The time is only well defined
|
||||
// for version 1 and 2 UUIDs.
|
||||
func (uuid UUID) Time() (Time, bool) {
|
||||
if len(uuid) != 16 {
|
||||
return 0, false
|
||||
}
|
||||
time := int64(binary.BigEndian.Uint32(uuid[0:4]))
|
||||
time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32
|
||||
time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48
|
||||
return Time(time), true
|
||||
}
|
||||
|
||||
// ClockSequence returns the clock sequence encoded in uuid. It returns false
|
||||
// if uuid is not valid. The clock sequence is only well defined for version 1
|
||||
// and 2 UUIDs.
|
||||
func (uuid UUID) ClockSequence() (int, bool) {
|
||||
if len(uuid) != 16 {
|
||||
return 0, false
|
||||
}
|
||||
return int(binary.BigEndian.Uint16(uuid[8:10])) & 0x3fff, true
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
// Copyright 2011 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// randomBits completely fills slice b with random data.
|
||||
func randomBits(b []byte) {
|
||||
if _, err := io.ReadFull(rander, b); err != nil {
|
||||
panic(err.Error()) // rand should never fail
|
||||
}
|
||||
}
|
||||
|
||||
// xvalues returns the value of a byte as a hexadecimal digit or 255.
|
||||
var xvalues = []byte{
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255,
|
||||
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
}
|
||||
|
||||
// xtob converts the the first two hex bytes of x into a byte.
|
||||
func xtob(x string) (byte, bool) {
|
||||
b1 := xvalues[x[0]]
|
||||
b2 := xvalues[x[1]]
|
||||
return (b1 << 4) | b2, b1 != 255 && b2 != 255
|
||||
}
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
// Copyright 2011 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC
|
||||
// 4122.
|
||||
type UUID []byte
|
||||
|
||||
// A Version represents a UUIDs version.
|
||||
type Version byte
|
||||
|
||||
// A Variant represents a UUIDs variant.
|
||||
type Variant byte
|
||||
|
||||
// Constants returned by Variant.
|
||||
const (
|
||||
Invalid = Variant(iota) // Invalid UUID
|
||||
RFC4122 // The variant specified in RFC4122
|
||||
Reserved // Reserved, NCS backward compatibility.
|
||||
Microsoft // Reserved, Microsoft Corporation backward compatibility.
|
||||
Future // Reserved for future definition.
|
||||
)
|
||||
|
||||
var rander = rand.Reader // random function
|
||||
|
||||
// New returns a new random (version 4) UUID as a string. It is a convenience
|
||||
// function for NewRandom().String().
|
||||
func New() string {
|
||||
return NewRandom().String()
|
||||
}
|
||||
|
||||
// Parse decodes s into a UUID or returns nil. Both the UUID form of
|
||||
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and
|
||||
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded.
|
||||
func Parse(s string) UUID {
|
||||
if len(s) == 36+9 {
|
||||
if strings.ToLower(s[:9]) != "urn:uuid:" {
|
||||
return nil
|
||||
}
|
||||
s = s[9:]
|
||||
} else if len(s) != 36 {
|
||||
return nil
|
||||
}
|
||||
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
|
||||
return nil
|
||||
}
|
||||
uuid := make([]byte, 16)
|
||||
for i, x := range []int{
|
||||
0, 2, 4, 6,
|
||||
9, 11,
|
||||
14, 16,
|
||||
19, 21,
|
||||
24, 26, 28, 30, 32, 34} {
|
||||
if v, ok := xtob(s[x:]); !ok {
|
||||
return nil
|
||||
} else {
|
||||
uuid[i] = v
|
||||
}
|
||||
}
|
||||
return uuid
|
||||
}
|
||||
|
||||
// Equal returns true if uuid1 and uuid2 are equal.
|
||||
func Equal(uuid1, uuid2 UUID) bool {
|
||||
return bytes.Equal(uuid1, uuid2)
|
||||
}
|
||||
|
||||
// String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
// , or "" if uuid is invalid.
|
||||
func (uuid UUID) String() string {
|
||||
if uuid == nil || len(uuid) != 16 {
|
||||
return ""
|
||||
}
|
||||
b := []byte(uuid)
|
||||
return fmt.Sprintf("%08x-%04x-%04x-%04x-%012x",
|
||||
b[:4], b[4:6], b[6:8], b[8:10], b[10:])
|
||||
}
|
||||
|
||||
// URN returns the RFC 2141 URN form of uuid,
|
||||
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, or "" if uuid is invalid.
|
||||
func (uuid UUID) URN() string {
|
||||
if uuid == nil || len(uuid) != 16 {
|
||||
return ""
|
||||
}
|
||||
b := []byte(uuid)
|
||||
return fmt.Sprintf("urn:uuid:%08x-%04x-%04x-%04x-%012x",
|
||||
b[:4], b[4:6], b[6:8], b[8:10], b[10:])
|
||||
}
|
||||
|
||||
// Variant returns the variant encoded in uuid. It returns Invalid if
|
||||
// uuid is invalid.
|
||||
func (uuid UUID) Variant() Variant {
|
||||
if len(uuid) != 16 {
|
||||
return Invalid
|
||||
}
|
||||
switch {
|
||||
case (uuid[8] & 0xc0) == 0x80:
|
||||
return RFC4122
|
||||
case (uuid[8] & 0xe0) == 0xc0:
|
||||
return Microsoft
|
||||
case (uuid[8] & 0xe0) == 0xe0:
|
||||
return Future
|
||||
default:
|
||||
return Reserved
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// Version returns the verison of uuid. It returns false if uuid is not
|
||||
// valid.
|
||||
func (uuid UUID) Version() (Version, bool) {
|
||||
if len(uuid) != 16 {
|
||||
return 0, false
|
||||
}
|
||||
return Version(uuid[6] >> 4), true
|
||||
}
|
||||
|
||||
func (v Version) String() string {
|
||||
if v > 15 {
|
||||
return fmt.Sprintf("BAD_VERSION_%d", v)
|
||||
}
|
||||
return fmt.Sprintf("VERSION_%d", v)
|
||||
}
|
||||
|
||||
func (v Variant) String() string {
|
||||
switch v {
|
||||
case RFC4122:
|
||||
return "RFC4122"
|
||||
case Reserved:
|
||||
return "Reserved"
|
||||
case Microsoft:
|
||||
return "Microsoft"
|
||||
case Future:
|
||||
return "Future"
|
||||
case Invalid:
|
||||
return "Invalid"
|
||||
}
|
||||
return fmt.Sprintf("BadVariant%d", int(v))
|
||||
}
|
||||
|
||||
// SetRand sets the random number generator to r, which implents io.Reader.
|
||||
// If r.Read returns an error when the package requests random data then
|
||||
// a panic will be issued.
|
||||
//
|
||||
// Calling SetRand with nil sets the random number generator to the default
|
||||
// generator.
|
||||
func SetRand(r io.Reader) {
|
||||
if r == nil {
|
||||
rander = rand.Reader
|
||||
return
|
||||
}
|
||||
rander = r
|
||||
}
|
||||
+390
@@ -0,0 +1,390 @@
|
||||
// Copyright 2011 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type test struct {
|
||||
in string
|
||||
version Version
|
||||
variant Variant
|
||||
isuuid bool
|
||||
}
|
||||
|
||||
var tests = []test{
|
||||
{"f47ac10b-58cc-0372-8567-0e02b2c3d479", 0, RFC4122, true},
|
||||
{"f47ac10b-58cc-1372-8567-0e02b2c3d479", 1, RFC4122, true},
|
||||
{"f47ac10b-58cc-2372-8567-0e02b2c3d479", 2, RFC4122, true},
|
||||
{"f47ac10b-58cc-3372-8567-0e02b2c3d479", 3, RFC4122, true},
|
||||
{"f47ac10b-58cc-4372-8567-0e02b2c3d479", 4, RFC4122, true},
|
||||
{"f47ac10b-58cc-5372-8567-0e02b2c3d479", 5, RFC4122, true},
|
||||
{"f47ac10b-58cc-6372-8567-0e02b2c3d479", 6, RFC4122, true},
|
||||
{"f47ac10b-58cc-7372-8567-0e02b2c3d479", 7, RFC4122, true},
|
||||
{"f47ac10b-58cc-8372-8567-0e02b2c3d479", 8, RFC4122, true},
|
||||
{"f47ac10b-58cc-9372-8567-0e02b2c3d479", 9, RFC4122, true},
|
||||
{"f47ac10b-58cc-a372-8567-0e02b2c3d479", 10, RFC4122, true},
|
||||
{"f47ac10b-58cc-b372-8567-0e02b2c3d479", 11, RFC4122, true},
|
||||
{"f47ac10b-58cc-c372-8567-0e02b2c3d479", 12, RFC4122, true},
|
||||
{"f47ac10b-58cc-d372-8567-0e02b2c3d479", 13, RFC4122, true},
|
||||
{"f47ac10b-58cc-e372-8567-0e02b2c3d479", 14, RFC4122, true},
|
||||
{"f47ac10b-58cc-f372-8567-0e02b2c3d479", 15, RFC4122, true},
|
||||
|
||||
{"urn:uuid:f47ac10b-58cc-4372-0567-0e02b2c3d479", 4, Reserved, true},
|
||||
{"URN:UUID:f47ac10b-58cc-4372-0567-0e02b2c3d479", 4, Reserved, true},
|
||||
{"f47ac10b-58cc-4372-0567-0e02b2c3d479", 4, Reserved, true},
|
||||
{"f47ac10b-58cc-4372-1567-0e02b2c3d479", 4, Reserved, true},
|
||||
{"f47ac10b-58cc-4372-2567-0e02b2c3d479", 4, Reserved, true},
|
||||
{"f47ac10b-58cc-4372-3567-0e02b2c3d479", 4, Reserved, true},
|
||||
{"f47ac10b-58cc-4372-4567-0e02b2c3d479", 4, Reserved, true},
|
||||
{"f47ac10b-58cc-4372-5567-0e02b2c3d479", 4, Reserved, true},
|
||||
{"f47ac10b-58cc-4372-6567-0e02b2c3d479", 4, Reserved, true},
|
||||
{"f47ac10b-58cc-4372-7567-0e02b2c3d479", 4, Reserved, true},
|
||||
{"f47ac10b-58cc-4372-8567-0e02b2c3d479", 4, RFC4122, true},
|
||||
{"f47ac10b-58cc-4372-9567-0e02b2c3d479", 4, RFC4122, true},
|
||||
{"f47ac10b-58cc-4372-a567-0e02b2c3d479", 4, RFC4122, true},
|
||||
{"f47ac10b-58cc-4372-b567-0e02b2c3d479", 4, RFC4122, true},
|
||||
{"f47ac10b-58cc-4372-c567-0e02b2c3d479", 4, Microsoft, true},
|
||||
{"f47ac10b-58cc-4372-d567-0e02b2c3d479", 4, Microsoft, true},
|
||||
{"f47ac10b-58cc-4372-e567-0e02b2c3d479", 4, Future, true},
|
||||
{"f47ac10b-58cc-4372-f567-0e02b2c3d479", 4, Future, true},
|
||||
|
||||
{"f47ac10b158cc-5372-a567-0e02b2c3d479", 0, Invalid, false},
|
||||
{"f47ac10b-58cc25372-a567-0e02b2c3d479", 0, Invalid, false},
|
||||
{"f47ac10b-58cc-53723a567-0e02b2c3d479", 0, Invalid, false},
|
||||
{"f47ac10b-58cc-5372-a56740e02b2c3d479", 0, Invalid, false},
|
||||
{"f47ac10b-58cc-5372-a567-0e02-2c3d479", 0, Invalid, false},
|
||||
{"g47ac10b-58cc-4372-a567-0e02b2c3d479", 0, Invalid, false},
|
||||
}
|
||||
|
||||
var constants = []struct {
|
||||
c interface{}
|
||||
name string
|
||||
}{
|
||||
{Person, "Person"},
|
||||
{Group, "Group"},
|
||||
{Org, "Org"},
|
||||
{Invalid, "Invalid"},
|
||||
{RFC4122, "RFC4122"},
|
||||
{Reserved, "Reserved"},
|
||||
{Microsoft, "Microsoft"},
|
||||
{Future, "Future"},
|
||||
{Domain(17), "Domain17"},
|
||||
{Variant(42), "BadVariant42"},
|
||||
}
|
||||
|
||||
func testTest(t *testing.T, in string, tt test) {
|
||||
uuid := Parse(in)
|
||||
if ok := (uuid != nil); ok != tt.isuuid {
|
||||
t.Errorf("Parse(%s) got %v expected %v\b", in, ok, tt.isuuid)
|
||||
}
|
||||
if uuid == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if v := uuid.Variant(); v != tt.variant {
|
||||
t.Errorf("Variant(%s) got %d expected %d\b", in, v, tt.variant)
|
||||
}
|
||||
if v, _ := uuid.Version(); v != tt.version {
|
||||
t.Errorf("Version(%s) got %d expected %d\b", in, v, tt.version)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUUID(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
testTest(t, tt.in, tt)
|
||||
testTest(t, strings.ToUpper(tt.in), tt)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConstants(t *testing.T) {
|
||||
for x, tt := range constants {
|
||||
v, ok := tt.c.(fmt.Stringer)
|
||||
if !ok {
|
||||
t.Errorf("%x: %v: not a stringer", x, v)
|
||||
} else if s := v.String(); s != tt.name {
|
||||
v, _ := tt.c.(int)
|
||||
t.Errorf("%x: Constant %T:%d gives %q, expected %q\n", x, tt.c, v, s, tt.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRandomUUID(t *testing.T) {
|
||||
m := make(map[string]bool)
|
||||
for x := 1; x < 32; x++ {
|
||||
uuid := NewRandom()
|
||||
s := uuid.String()
|
||||
if m[s] {
|
||||
t.Errorf("NewRandom returned duplicated UUID %s\n", s)
|
||||
}
|
||||
m[s] = true
|
||||
if v, _ := uuid.Version(); v != 4 {
|
||||
t.Errorf("Random UUID of version %s\n", v)
|
||||
}
|
||||
if uuid.Variant() != RFC4122 {
|
||||
t.Errorf("Random UUID is variant %d\n", uuid.Variant())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
m := make(map[string]bool)
|
||||
for x := 1; x < 32; x++ {
|
||||
s := New()
|
||||
if m[s] {
|
||||
t.Errorf("New returned duplicated UUID %s\n", s)
|
||||
}
|
||||
m[s] = true
|
||||
uuid := Parse(s)
|
||||
if uuid == nil {
|
||||
t.Errorf("New returned %q which does not decode\n", s)
|
||||
continue
|
||||
}
|
||||
if v, _ := uuid.Version(); v != 4 {
|
||||
t.Errorf("Random UUID of version %s\n", v)
|
||||
}
|
||||
if uuid.Variant() != RFC4122 {
|
||||
t.Errorf("Random UUID is variant %d\n", uuid.Variant())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func clockSeq(t *testing.T, uuid UUID) int {
|
||||
seq, ok := uuid.ClockSequence()
|
||||
if !ok {
|
||||
t.Fatalf("%s: invalid clock sequence\n", uuid)
|
||||
}
|
||||
return seq
|
||||
}
|
||||
|
||||
func TestClockSeq(t *testing.T) {
|
||||
// Fake time.Now for this test to return a monotonically advancing time; restore it at end.
|
||||
defer func(orig func() time.Time) { timeNow = orig }(timeNow)
|
||||
monTime := time.Now()
|
||||
timeNow = func() time.Time {
|
||||
monTime = monTime.Add(1 * time.Second)
|
||||
return monTime
|
||||
}
|
||||
|
||||
SetClockSequence(-1)
|
||||
uuid1 := NewUUID()
|
||||
uuid2 := NewUUID()
|
||||
|
||||
if clockSeq(t, uuid1) != clockSeq(t, uuid2) {
|
||||
t.Errorf("clock sequence %d != %d\n", clockSeq(t, uuid1), clockSeq(t, uuid2))
|
||||
}
|
||||
|
||||
SetClockSequence(-1)
|
||||
uuid2 = NewUUID()
|
||||
|
||||
// Just on the very off chance we generated the same sequence
|
||||
// two times we try again.
|
||||
if clockSeq(t, uuid1) == clockSeq(t, uuid2) {
|
||||
SetClockSequence(-1)
|
||||
uuid2 = NewUUID()
|
||||
}
|
||||
if clockSeq(t, uuid1) == clockSeq(t, uuid2) {
|
||||
t.Errorf("Duplicate clock sequence %d\n", clockSeq(t, uuid1))
|
||||
}
|
||||
|
||||
SetClockSequence(0x1234)
|
||||
uuid1 = NewUUID()
|
||||
if seq := clockSeq(t, uuid1); seq != 0x1234 {
|
||||
t.Errorf("%s: expected seq 0x1234 got 0x%04x\n", uuid1, seq)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCoding(t *testing.T) {
|
||||
text := "7d444840-9dc0-11d1-b245-5ffdce74fad2"
|
||||
urn := "urn:uuid:7d444840-9dc0-11d1-b245-5ffdce74fad2"
|
||||
data := UUID{
|
||||
0x7d, 0x44, 0x48, 0x40,
|
||||
0x9d, 0xc0,
|
||||
0x11, 0xd1,
|
||||
0xb2, 0x45,
|
||||
0x5f, 0xfd, 0xce, 0x74, 0xfa, 0xd2,
|
||||
}
|
||||
if v := data.String(); v != text {
|
||||
t.Errorf("%x: encoded to %s, expected %s\n", data, v, text)
|
||||
}
|
||||
if v := data.URN(); v != urn {
|
||||
t.Errorf("%x: urn is %s, expected %s\n", data, v, urn)
|
||||
}
|
||||
|
||||
uuid := Parse(text)
|
||||
if !Equal(uuid, data) {
|
||||
t.Errorf("%s: decoded to %s, expected %s\n", text, uuid, data)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVersion1(t *testing.T) {
|
||||
uuid1 := NewUUID()
|
||||
uuid2 := NewUUID()
|
||||
|
||||
if Equal(uuid1, uuid2) {
|
||||
t.Errorf("%s:duplicate uuid\n", uuid1)
|
||||
}
|
||||
if v, _ := uuid1.Version(); v != 1 {
|
||||
t.Errorf("%s: version %s expected 1\n", uuid1, v)
|
||||
}
|
||||
if v, _ := uuid2.Version(); v != 1 {
|
||||
t.Errorf("%s: version %s expected 1\n", uuid2, v)
|
||||
}
|
||||
n1 := uuid1.NodeID()
|
||||
n2 := uuid2.NodeID()
|
||||
if !bytes.Equal(n1, n2) {
|
||||
t.Errorf("Different nodes %x != %x\n", n1, n2)
|
||||
}
|
||||
t1, ok := uuid1.Time()
|
||||
if !ok {
|
||||
t.Errorf("%s: invalid time\n", uuid1)
|
||||
}
|
||||
t2, ok := uuid2.Time()
|
||||
if !ok {
|
||||
t.Errorf("%s: invalid time\n", uuid2)
|
||||
}
|
||||
q1, ok := uuid1.ClockSequence()
|
||||
if !ok {
|
||||
t.Errorf("%s: invalid clock sequence\n", uuid1)
|
||||
}
|
||||
q2, ok := uuid2.ClockSequence()
|
||||
if !ok {
|
||||
t.Errorf("%s: invalid clock sequence", uuid2)
|
||||
}
|
||||
|
||||
switch {
|
||||
case t1 == t2 && q1 == q2:
|
||||
t.Errorf("time stopped\n")
|
||||
case t1 > t2 && q1 == q2:
|
||||
t.Errorf("time reversed\n")
|
||||
case t1 < t2 && q1 != q2:
|
||||
t.Errorf("clock sequence chaned unexpectedly\n")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodeAndTime(t *testing.T) {
|
||||
// Time is February 5, 1998 12:30:23.136364800 AM GMT
|
||||
|
||||
uuid := Parse("7d444840-9dc0-11d1-b245-5ffdce74fad2")
|
||||
node := []byte{0x5f, 0xfd, 0xce, 0x74, 0xfa, 0xd2}
|
||||
|
||||
ts, ok := uuid.Time()
|
||||
if ok {
|
||||
c := time.Unix(ts.UnixTime())
|
||||
want := time.Date(1998, 2, 5, 0, 30, 23, 136364800, time.UTC)
|
||||
if !c.Equal(want) {
|
||||
t.Errorf("Got time %v, want %v", c, want)
|
||||
}
|
||||
} else {
|
||||
t.Errorf("%s: bad time\n", uuid)
|
||||
}
|
||||
if !bytes.Equal(node, uuid.NodeID()) {
|
||||
t.Errorf("Expected node %v got %v\n", node, uuid.NodeID())
|
||||
}
|
||||
}
|
||||
|
||||
func TestMD5(t *testing.T) {
|
||||
uuid := NewMD5(NameSpace_DNS, []byte("python.org")).String()
|
||||
want := "6fa459ea-ee8a-3ca4-894e-db77e160355e"
|
||||
if uuid != want {
|
||||
t.Errorf("MD5: got %q expected %q\n", uuid, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSHA1(t *testing.T) {
|
||||
uuid := NewSHA1(NameSpace_DNS, []byte("python.org")).String()
|
||||
want := "886313e1-3b8a-5372-9b90-0c9aee199e5d"
|
||||
if uuid != want {
|
||||
t.Errorf("SHA1: got %q expected %q\n", uuid, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodeID(t *testing.T) {
|
||||
nid := []byte{1, 2, 3, 4, 5, 6}
|
||||
SetNodeInterface("")
|
||||
s := NodeInterface()
|
||||
if s == "" || s == "user" {
|
||||
t.Errorf("NodeInterface %q after SetInteface\n", s)
|
||||
}
|
||||
node1 := NodeID()
|
||||
if node1 == nil {
|
||||
t.Errorf("NodeID nil after SetNodeInterface\n", s)
|
||||
}
|
||||
SetNodeID(nid)
|
||||
s = NodeInterface()
|
||||
if s != "user" {
|
||||
t.Errorf("Expected NodeInterface %q got %q\n", "user", s)
|
||||
}
|
||||
node2 := NodeID()
|
||||
if node2 == nil {
|
||||
t.Errorf("NodeID nil after SetNodeID\n", s)
|
||||
}
|
||||
if bytes.Equal(node1, node2) {
|
||||
t.Errorf("NodeID not changed after SetNodeID\n", s)
|
||||
} else if !bytes.Equal(nid, node2) {
|
||||
t.Errorf("NodeID is %x, expected %x\n", node2, nid)
|
||||
}
|
||||
}
|
||||
|
||||
func testDCE(t *testing.T, name string, uuid UUID, domain Domain, id uint32) {
|
||||
if uuid == nil {
|
||||
t.Errorf("%s failed\n", name)
|
||||
return
|
||||
}
|
||||
if v, _ := uuid.Version(); v != 2 {
|
||||
t.Errorf("%s: %s: expected version 2, got %s\n", name, uuid, v)
|
||||
return
|
||||
}
|
||||
if v, ok := uuid.Domain(); !ok || v != domain {
|
||||
if !ok {
|
||||
t.Errorf("%s: %d: Domain failed\n", name, uuid)
|
||||
} else {
|
||||
t.Errorf("%s: %s: expected domain %d, got %d\n", name, uuid, domain, v)
|
||||
}
|
||||
}
|
||||
if v, ok := uuid.Id(); !ok || v != id {
|
||||
if !ok {
|
||||
t.Errorf("%s: %d: Id failed\n", name, uuid)
|
||||
} else {
|
||||
t.Errorf("%s: %s: expected id %d, got %d\n", name, uuid, id, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDCE(t *testing.T) {
|
||||
testDCE(t, "NewDCESecurity", NewDCESecurity(42, 12345678), 42, 12345678)
|
||||
testDCE(t, "NewDCEPerson", NewDCEPerson(), Person, uint32(os.Getuid()))
|
||||
testDCE(t, "NewDCEGroup", NewDCEGroup(), Group, uint32(os.Getgid()))
|
||||
}
|
||||
|
||||
type badRand struct{}
|
||||
|
||||
func (r badRand) Read(buf []byte) (int, error) {
|
||||
for i, _ := range buf {
|
||||
buf[i] = byte(i)
|
||||
}
|
||||
return len(buf), nil
|
||||
}
|
||||
|
||||
func TestBadRand(t *testing.T) {
|
||||
SetRand(badRand{})
|
||||
uuid1 := New()
|
||||
uuid2 := New()
|
||||
if uuid1 != uuid2 {
|
||||
t.Errorf("execpted duplicates, got %q and %q\n", uuid1, uuid2)
|
||||
}
|
||||
SetRand(nil)
|
||||
uuid1 = New()
|
||||
uuid2 = New()
|
||||
if uuid1 == uuid2 {
|
||||
t.Errorf("unexecpted duplicates, got %q\n", uuid1)
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
// Copyright 2011 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
// NewUUID returns a Version 1 UUID based on the current NodeID and clock
|
||||
// sequence, and the current time. If the NodeID has not been set by SetNodeID
|
||||
// or SetNodeInterface then it will be set automatically. If the NodeID cannot
|
||||
// be set NewUUID returns nil. If clock sequence has not been set by
|
||||
// SetClockSequence then it will be set automatically. If GetTime fails to
|
||||
// return the current NewUUID returns nil.
|
||||
func NewUUID() UUID {
|
||||
if nodeID == nil {
|
||||
SetNodeInterface("")
|
||||
}
|
||||
|
||||
now, seq, err := GetTime()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
uuid := make([]byte, 16)
|
||||
|
||||
time_low := uint32(now & 0xffffffff)
|
||||
time_mid := uint16((now >> 32) & 0xffff)
|
||||
time_hi := uint16((now >> 48) & 0x0fff)
|
||||
time_hi |= 0x1000 // Version 1
|
||||
|
||||
binary.BigEndian.PutUint32(uuid[0:], time_low)
|
||||
binary.BigEndian.PutUint16(uuid[4:], time_mid)
|
||||
binary.BigEndian.PutUint16(uuid[6:], time_hi)
|
||||
binary.BigEndian.PutUint16(uuid[8:], seq)
|
||||
copy(uuid[10:], nodeID)
|
||||
|
||||
return uuid
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
// Copyright 2011 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
// Random returns a Random (Version 4) UUID or panics.
|
||||
//
|
||||
// The strength of the UUIDs is based on the strength of the crypto/rand
|
||||
// package.
|
||||
//
|
||||
// A note about uniqueness derived from from the UUID Wikipedia entry:
|
||||
//
|
||||
// Randomly generated UUIDs have 122 random bits. One's annual risk of being
|
||||
// hit by a meteorite is estimated to be one chance in 17 billion, that
|
||||
// means the probability is about 0.00000000006 (6 × 10−11),
|
||||
// equivalent to the odds of creating a few tens of trillions of UUIDs in a
|
||||
// year and having one duplicate.
|
||||
func NewRandom() UUID {
|
||||
uuid := make([]byte, 16)
|
||||
randomBits([]byte(uuid))
|
||||
uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
|
||||
uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
|
||||
return uuid
|
||||
}
|
||||
Generated
+24
@@ -0,0 +1,24 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.3.3
|
||||
- 1.4
|
||||
- tip
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Andrey Smirnov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
# go-xz
|
||||
|
||||
[](https://godoc.org/github.com/smira/go-xz)
|
||||
[](https://travis-ci.org/smira/go-xz)
|
||||
|
||||
Simple .xz decompression using external program (xz --decompress)
|
||||
|
||||
## Why?
|
||||
|
||||
All go implementation of .xz decompression rely on `liblzma` dependency providing
|
||||
in-process decompression.
|
||||
|
||||
This package uses external `xz` utility, so no depdendencies for the compiled binary.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
+6434
File diff suppressed because it is too large.
Load diff
BIN
Binary file not shown.
+71
@@ -0,0 +1,71 @@
|
||||
// Package xz implements simple .xz decompression using external xz program
|
||||
//
|
||||
// No shared library (liblzma) dependencies.
|
||||
package xz
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// Reader does decompression using xz utility
|
||||
type Reader struct {
|
||||
cmd *exec.Cmd
|
||||
input io.WriteCloser
|
||||
output io.ReadCloser
|
||||
}
|
||||
|
||||
// NewReader creates .xz decompression reader
|
||||
//
|
||||
// Internally it starts xz program, sets up input and output pipes
|
||||
func NewReader(src io.Reader) (*Reader, error) {
|
||||
var err error
|
||||
|
||||
result := &Reader{}
|
||||
|
||||
result.cmd = exec.Command("xz", "--decompress", "--stdout")
|
||||
result.input, err = result.cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result.output, err = result.cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = result.cmd.Start()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go func() {
|
||||
io.Copy(result.input, src)
|
||||
result.input.Close()
|
||||
}()
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Read implements io.Reader interface
|
||||
func (r *Reader) Read(p []byte) (n int, err error) {
|
||||
return r.output.Read(p)
|
||||
}
|
||||
|
||||
// Close implements io.Closer interface
|
||||
func (r *Reader) Close() error {
|
||||
if r.input != nil {
|
||||
r.input.Close()
|
||||
}
|
||||
if r.output != nil {
|
||||
r.output.Close()
|
||||
}
|
||||
if r.cmd != nil {
|
||||
return r.cmd.Wait()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check interface
|
||||
var (
|
||||
_ io.ReadCloser = &Reader{}
|
||||
)
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package xz
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDecompress(T *testing.T) {
|
||||
orig, err := os.Open("testdata/spec")
|
||||
if err != nil {
|
||||
T.Fatal(err)
|
||||
}
|
||||
defer orig.Close()
|
||||
|
||||
expected := &bytes.Buffer{}
|
||||
_, err = io.Copy(expected, orig)
|
||||
if err != nil {
|
||||
T.Fatal(err)
|
||||
}
|
||||
|
||||
source, err := os.Open("testdata/spec.xz")
|
||||
if err != nil {
|
||||
T.Fatal(err)
|
||||
}
|
||||
defer source.Close()
|
||||
|
||||
r, err := NewReader(source)
|
||||
if err != nil {
|
||||
T.Fatal(err)
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
n, err := io.Copy(buf, r)
|
||||
if n != int64(expected.Len()) {
|
||||
T.Fail()
|
||||
}
|
||||
|
||||
if bytes.Compare(buf.Bytes(), expected.Bytes()) != 0 {
|
||||
T.Fail()
|
||||
}
|
||||
|
||||
err = r.Close()
|
||||
if err != nil {
|
||||
T.Fail()
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) 2010, Andrei Vieru. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of the author nor the names of its contributors
|
||||
// may be used to endorse or promote products derived from this software
|
||||
// without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
BIN
Binary file not shown.
+3982
File diff suppressed because it is too large.
Load diff
+355
@@ -0,0 +1,355 @@
|
||||
// Copyright (c) 2010, Andrei Vieru. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package lzma
|
||||
|
||||
import "io"
|
||||
|
||||
const (
|
||||
kHash2Size = 1 << 10
|
||||
kHash3Size = 1 << 16
|
||||
kBT2HashSize = 1 << 16
|
||||
kStartMaxLen = 1
|
||||
kHash3Offset = kHash2Size
|
||||
kEmptyHashValue = 0
|
||||
kMaxValForNormalize = (1 << 30) - 1
|
||||
)
|
||||
|
||||
type lzBinTree struct {
|
||||
iw *lzInWindow
|
||||
son []uint32
|
||||
hash []uint32
|
||||
cyclicBufPos uint32
|
||||
cyclicBufSize uint32
|
||||
matchMaxLen uint32
|
||||
cutValue uint32
|
||||
hashMask uint32
|
||||
hashSizeSum uint32
|
||||
kvNumHashDirectBytes uint32
|
||||
kvMinMatchCheck uint32
|
||||
kvFixHashSize uint32
|
||||
hashArray bool
|
||||
}
|
||||
|
||||
func newLzBinTree(r io.Reader, historySize, keepAddBufBefore, matchMaxLen, keepAddBufAfter, numHashBytes uint32) *lzBinTree {
|
||||
bt := &lzBinTree{
|
||||
son: make([]uint32, (historySize+1)*2), // history size is the dictSize from the encoder
|
||||
cyclicBufPos: 0,
|
||||
cyclicBufSize: historySize + 1,
|
||||
matchMaxLen: matchMaxLen,
|
||||
cutValue: 16 + (matchMaxLen >> 1),
|
||||
}
|
||||
|
||||
winSizeReserv := (historySize+keepAddBufBefore+matchMaxLen+keepAddBufAfter)/2 + 256
|
||||
bt.iw = newLzInWindow(r, historySize+keepAddBufBefore, matchMaxLen+keepAddBufAfter, winSizeReserv)
|
||||
|
||||
if numHashBytes > 2 {
|
||||
bt.hashArray = true
|
||||
bt.kvNumHashDirectBytes = 0
|
||||
bt.kvMinMatchCheck = 4
|
||||
bt.kvFixHashSize = kHash2Size + kHash3Size
|
||||
} else {
|
||||
bt.hashArray = false
|
||||
bt.kvNumHashDirectBytes = 2
|
||||
bt.kvMinMatchCheck = 3
|
||||
bt.kvFixHashSize = 0
|
||||
}
|
||||
|
||||
hs := uint32(kBT2HashSize)
|
||||
if bt.hashArray == true {
|
||||
hs = historySize - 1
|
||||
hs |= hs >> 1
|
||||
hs |= hs >> 2
|
||||
hs |= hs >> 4
|
||||
hs |= hs >> 8
|
||||
hs >>= 1
|
||||
hs |= 0xFFFF
|
||||
if hs > 1<<24 {
|
||||
hs >>= 1
|
||||
}
|
||||
bt.hashMask = hs
|
||||
hs++
|
||||
hs += bt.kvFixHashSize
|
||||
}
|
||||
bt.hashSizeSum = hs
|
||||
bt.hash = make([]uint32, bt.hashSizeSum)
|
||||
for i := uint32(0); i < bt.hashSizeSum; i++ {
|
||||
bt.hash[i] = kEmptyHashValue
|
||||
}
|
||||
|
||||
bt.iw.reduceOffsets(0xFFFFFFFF)
|
||||
return bt
|
||||
}
|
||||
|
||||
func normalizeLinks(items []uint32, numItems, subValue uint32) {
|
||||
for i := uint32(0); i < numItems; i++ {
|
||||
value := items[i]
|
||||
if value <= subValue {
|
||||
value = kEmptyHashValue
|
||||
} else {
|
||||
value -= subValue
|
||||
}
|
||||
items[i] = value
|
||||
}
|
||||
}
|
||||
|
||||
func (bt *lzBinTree) normalize() {
|
||||
subValue := bt.iw.pos - bt.cyclicBufSize
|
||||
normalizeLinks(bt.son, bt.cyclicBufSize*2, subValue)
|
||||
normalizeLinks(bt.hash, bt.hashSizeSum, subValue)
|
||||
bt.iw.reduceOffsets(subValue)
|
||||
}
|
||||
|
||||
func (bt *lzBinTree) movePos() {
|
||||
bt.cyclicBufPos++
|
||||
if bt.cyclicBufPos >= bt.cyclicBufSize {
|
||||
bt.cyclicBufPos = 0
|
||||
}
|
||||
bt.iw.movePos()
|
||||
if bt.iw.pos == kMaxValForNormalize {
|
||||
bt.normalize()
|
||||
}
|
||||
}
|
||||
|
||||
func (bt *lzBinTree) getMatches(distances []uint32) uint32 {
|
||||
var lenLimit uint32
|
||||
if bt.iw.pos+bt.matchMaxLen <= bt.iw.streamPos {
|
||||
lenLimit = bt.matchMaxLen
|
||||
} else {
|
||||
lenLimit = bt.iw.streamPos - bt.iw.pos
|
||||
if lenLimit < bt.kvMinMatchCheck {
|
||||
bt.movePos()
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
offset := uint32(0)
|
||||
matchMinPos := uint32(0)
|
||||
if bt.iw.pos > bt.cyclicBufSize {
|
||||
matchMinPos = bt.iw.pos - bt.cyclicBufSize
|
||||
}
|
||||
cur := bt.iw.bufOffset + bt.iw.pos
|
||||
maxLen := uint32(kStartMaxLen)
|
||||
var hashValue uint32
|
||||
hash2Value := uint32(0)
|
||||
hash3Value := uint32(0)
|
||||
|
||||
if bt.hashArray == true {
|
||||
tmp := crcTable[bt.iw.buf[cur]] ^ uint32(bt.iw.buf[cur+1])
|
||||
hash2Value = tmp & (kHash2Size - 1)
|
||||
tmp ^= uint32(bt.iw.buf[cur+2]) << 8
|
||||
hash3Value = tmp & (kHash3Size - 1)
|
||||
hashValue = (tmp ^ crcTable[bt.iw.buf[cur+3]]<<5) & bt.hashMask
|
||||
} else {
|
||||
hashValue = uint32(bt.iw.buf[cur]) ^ uint32(bt.iw.buf[cur+1])<<8
|
||||
}
|
||||
|
||||
curMatch := bt.hash[bt.kvFixHashSize+hashValue]
|
||||
if bt.hashArray == true {
|
||||
curMatch2 := bt.hash[hash2Value]
|
||||
curMatch3 := bt.hash[kHash3Offset+hash3Value]
|
||||
bt.hash[hash2Value] = bt.iw.pos
|
||||
bt.hash[kHash3Offset+hash3Value] = bt.iw.pos
|
||||
if curMatch2 > matchMinPos {
|
||||
if bt.iw.buf[bt.iw.bufOffset+curMatch2] == bt.iw.buf[cur] {
|
||||
maxLen = 2
|
||||
distances[offset] = maxLen
|
||||
offset++
|
||||
distances[offset] = bt.iw.pos - curMatch2 - 1
|
||||
offset++
|
||||
}
|
||||
}
|
||||
if curMatch3 > matchMinPos {
|
||||
if bt.iw.buf[bt.iw.bufOffset+curMatch3] == bt.iw.buf[cur] {
|
||||
if curMatch3 == curMatch2 {
|
||||
offset -= 2
|
||||
}
|
||||
maxLen = 3
|
||||
distances[offset] = maxLen
|
||||
offset++
|
||||
distances[offset] = bt.iw.pos - curMatch3 - 1
|
||||
offset++
|
||||
curMatch2 = curMatch3
|
||||
}
|
||||
}
|
||||
if offset != 0 && curMatch2 == curMatch {
|
||||
offset -= 2
|
||||
maxLen = kStartMaxLen
|
||||
}
|
||||
}
|
||||
|
||||
bt.hash[bt.kvFixHashSize+hashValue] = bt.iw.pos
|
||||
|
||||
if bt.kvNumHashDirectBytes != 0 {
|
||||
if curMatch > matchMinPos {
|
||||
if bt.iw.buf[bt.iw.bufOffset+curMatch+bt.kvNumHashDirectBytes] != bt.iw.buf[cur+bt.kvNumHashDirectBytes] {
|
||||
maxLen = bt.kvNumHashDirectBytes
|
||||
distances[offset] = maxLen
|
||||
offset++
|
||||
distances[offset] = bt.iw.pos - curMatch - 1
|
||||
offset++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ptr0 := bt.cyclicBufPos<<1 + 1
|
||||
ptr1 := bt.cyclicBufPos << 1
|
||||
len0 := bt.kvNumHashDirectBytes
|
||||
len1 := bt.kvNumHashDirectBytes
|
||||
count := bt.cutValue
|
||||
|
||||
for {
|
||||
if curMatch <= matchMinPos || count == 0 {
|
||||
bt.son[ptr1] = kEmptyHashValue
|
||||
bt.son[ptr0] = kEmptyHashValue
|
||||
break
|
||||
}
|
||||
count--
|
||||
|
||||
delta := bt.iw.pos - curMatch
|
||||
var cyclicPos uint32
|
||||
if delta <= bt.cyclicBufPos {
|
||||
cyclicPos = (bt.cyclicBufPos - delta) << 1
|
||||
} else {
|
||||
cyclicPos = (bt.cyclicBufPos - delta + bt.cyclicBufSize) << 1
|
||||
}
|
||||
pby1 := bt.iw.bufOffset + curMatch
|
||||
length := minUInt32(len0, len1)
|
||||
if bt.iw.buf[pby1+length] == bt.iw.buf[cur+length] {
|
||||
for length++; length != lenLimit; length++ {
|
||||
if bt.iw.buf[pby1+length] != bt.iw.buf[cur+length] {
|
||||
break
|
||||
}
|
||||
}
|
||||
if maxLen < length {
|
||||
maxLen = length
|
||||
distances[offset] = maxLen
|
||||
offset++
|
||||
distances[offset] = delta - 1
|
||||
offset++
|
||||
if length == lenLimit {
|
||||
bt.son[ptr1] = bt.son[cyclicPos]
|
||||
bt.son[ptr0] = bt.son[cyclicPos+1]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if bt.iw.buf[pby1+length] < bt.iw.buf[cur+length] {
|
||||
bt.son[ptr1] = curMatch
|
||||
ptr1 = cyclicPos + 1
|
||||
curMatch = bt.son[ptr1]
|
||||
len1 = length
|
||||
} else {
|
||||
bt.son[ptr0] = curMatch
|
||||
ptr0 = cyclicPos
|
||||
curMatch = bt.son[ptr0]
|
||||
len0 = length
|
||||
}
|
||||
}
|
||||
bt.movePos()
|
||||
return offset
|
||||
}
|
||||
|
||||
func (bt *lzBinTree) skip(num uint32) {
|
||||
for i := uint32(0); i < num; i++ {
|
||||
var lenLimit uint32
|
||||
if bt.iw.pos+bt.matchMaxLen <= bt.iw.streamPos {
|
||||
lenLimit = bt.matchMaxLen
|
||||
} else {
|
||||
lenLimit = bt.iw.streamPos - bt.iw.pos
|
||||
if lenLimit < bt.kvMinMatchCheck {
|
||||
bt.movePos()
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
matchMinPos := uint32(0)
|
||||
if bt.iw.pos > bt.cyclicBufSize {
|
||||
matchMinPos = bt.iw.pos - bt.cyclicBufSize
|
||||
}
|
||||
cur := bt.iw.bufOffset + bt.iw.pos
|
||||
var hashValue uint32
|
||||
if bt.hashArray == true {
|
||||
tmp := crcTable[bt.iw.buf[cur]] ^ uint32(bt.iw.buf[cur+1])
|
||||
hash2Value := tmp & (kHash2Size - 1)
|
||||
bt.hash[hash2Value] = bt.iw.pos
|
||||
tmp ^= uint32(bt.iw.buf[cur+2]) << 8
|
||||
hash3Value := tmp & (kHash3Size - 1)
|
||||
bt.hash[kHash3Offset+hash3Value] = bt.iw.pos
|
||||
hashValue = (tmp ^ crcTable[bt.iw.buf[cur+3]]<<5) & bt.hashMask
|
||||
} else {
|
||||
hashValue = uint32(bt.iw.buf[cur]) ^ uint32(bt.iw.buf[cur+1])<<8
|
||||
}
|
||||
|
||||
curMatch := bt.hash[bt.kvFixHashSize+hashValue]
|
||||
bt.hash[bt.kvFixHashSize+hashValue] = bt.iw.pos
|
||||
ptr0 := bt.cyclicBufPos<<1 + 1
|
||||
ptr1 := bt.cyclicBufPos << 1
|
||||
len0 := bt.kvNumHashDirectBytes
|
||||
len1 := bt.kvNumHashDirectBytes
|
||||
count := bt.cutValue
|
||||
for {
|
||||
if curMatch <= matchMinPos || count == 0 {
|
||||
bt.son[ptr1] = kEmptyHashValue
|
||||
bt.son[ptr0] = kEmptyHashValue
|
||||
break
|
||||
}
|
||||
count--
|
||||
|
||||
delta := bt.iw.pos - curMatch
|
||||
var cyclicPos uint32
|
||||
if delta <= bt.cyclicBufPos {
|
||||
cyclicPos = (bt.cyclicBufPos - delta) << 1
|
||||
} else {
|
||||
cyclicPos = (bt.cyclicBufPos - delta + bt.cyclicBufSize) << 1
|
||||
}
|
||||
pby1 := bt.iw.bufOffset + curMatch
|
||||
length := minUInt32(len0, len1)
|
||||
if bt.iw.buf[pby1+length] == bt.iw.buf[cur+length] {
|
||||
for length++; length != lenLimit; length++ {
|
||||
if bt.iw.buf[pby1+length] != bt.iw.buf[cur+length] {
|
||||
break
|
||||
}
|
||||
}
|
||||
if length == lenLimit {
|
||||
bt.son[ptr1] = bt.son[cyclicPos]
|
||||
bt.son[ptr0] = bt.son[cyclicPos+1]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if bt.iw.buf[pby1+length] < bt.iw.buf[cur+length] {
|
||||
bt.son[ptr1] = curMatch
|
||||
ptr1 = cyclicPos + 1
|
||||
curMatch = bt.son[ptr1]
|
||||
len1 = length
|
||||
} else {
|
||||
bt.son[ptr0] = curMatch
|
||||
ptr0 = cyclicPos
|
||||
curMatch = bt.son[ptr0]
|
||||
len0 = length
|
||||
}
|
||||
}
|
||||
bt.movePos()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var crcTable []uint32 = make([]uint32, 256)
|
||||
|
||||
// should be called in the encoder's contructor
|
||||
func initCrcTable() {
|
||||
for i := uint32(0); i < 256; i++ {
|
||||
r := i
|
||||
for j := 0; j < 8; j++ {
|
||||
if r&1 != 0 {
|
||||
r = r>>1 ^ 0xEDB88320
|
||||
} else {
|
||||
r >>= 1
|
||||
}
|
||||
}
|
||||
crcTable[i] = r
|
||||
}
|
||||
}
|
||||
+193
@@ -0,0 +1,193 @@
|
||||
// Copyright (c) 2010, Andrei Vieru. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package lzma
|
||||
|
||||
import "io"
|
||||
|
||||
type lzOutWindow struct {
|
||||
w io.Writer
|
||||
buf []byte
|
||||
winSize uint32
|
||||
pos uint32
|
||||
streamPos uint32
|
||||
//unpacked uint32 // counter of unpacked bytes
|
||||
}
|
||||
|
||||
func newLzOutWindow(w io.Writer, windowSize uint32) *lzOutWindow {
|
||||
return &lzOutWindow{
|
||||
w: w,
|
||||
buf: make([]byte, windowSize),
|
||||
winSize: windowSize,
|
||||
pos: 0,
|
||||
streamPos: 0,
|
||||
//unpacked: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func (ow *lzOutWindow) flush() {
|
||||
size := ow.pos - ow.streamPos
|
||||
if size == 0 {
|
||||
return
|
||||
}
|
||||
n, err := ow.w.Write(ow.buf[ow.streamPos : ow.streamPos+size])
|
||||
if err != nil {
|
||||
throw(err)
|
||||
}
|
||||
if uint32(n) != size {
|
||||
throw(nWriteError)
|
||||
}
|
||||
//unpacked += size
|
||||
if ow.pos >= ow.winSize {
|
||||
ow.pos = 0
|
||||
}
|
||||
ow.streamPos = ow.pos
|
||||
}
|
||||
|
||||
func (ow *lzOutWindow) copyBlock(distance, length uint32) {
|
||||
pos := ow.pos - distance - 1
|
||||
if pos >= ow.winSize {
|
||||
pos += ow.winSize
|
||||
}
|
||||
for ; length != 0; length-- {
|
||||
if pos >= ow.winSize {
|
||||
pos = 0
|
||||
}
|
||||
ow.buf[ow.pos] = ow.buf[pos]
|
||||
ow.pos++
|
||||
pos++
|
||||
if ow.pos >= ow.winSize {
|
||||
ow.flush()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ow *lzOutWindow) putByte(b byte) {
|
||||
ow.buf[ow.pos] = b
|
||||
ow.pos++
|
||||
if ow.pos >= ow.winSize {
|
||||
ow.flush()
|
||||
}
|
||||
}
|
||||
|
||||
func (ow *lzOutWindow) getByte(distance uint32) byte {
|
||||
pos := ow.pos - distance - 1
|
||||
if pos >= ow.winSize {
|
||||
pos += ow.winSize
|
||||
}
|
||||
return ow.buf[pos]
|
||||
}
|
||||
|
||||
type lzInWindow struct {
|
||||
r io.Reader
|
||||
buf []byte
|
||||
posLimit uint32
|
||||
lastSafePos uint32
|
||||
bufOffset uint32
|
||||
blockSize uint32
|
||||
pos uint32
|
||||
keepSizeBefore uint32
|
||||
keepSizeAfter uint32
|
||||
streamPos uint32
|
||||
streamEnd bool
|
||||
}
|
||||
|
||||
func newLzInWindow(r io.Reader, keepSizeBefore, keepSizeAfter, keepSizeReserv uint32) *lzInWindow {
|
||||
blockSize := keepSizeBefore + keepSizeAfter + keepSizeReserv
|
||||
iw := &lzInWindow{
|
||||
r: r,
|
||||
buf: make([]byte, blockSize),
|
||||
lastSafePos: blockSize - keepSizeAfter,
|
||||
bufOffset: 0,
|
||||
blockSize: blockSize,
|
||||
pos: 0,
|
||||
keepSizeBefore: keepSizeBefore,
|
||||
keepSizeAfter: keepSizeAfter,
|
||||
streamPos: 0,
|
||||
streamEnd: false,
|
||||
}
|
||||
iw.readBlock()
|
||||
return iw
|
||||
}
|
||||
|
||||
func (iw *lzInWindow) moveBlock() {
|
||||
offset := iw.bufOffset + iw.pos - iw.keepSizeBefore
|
||||
if offset > 0 {
|
||||
offset--
|
||||
}
|
||||
numBytes := iw.bufOffset + iw.streamPos - offset
|
||||
for i := uint32(0); i < numBytes; i++ {
|
||||
iw.buf[i] = iw.buf[offset+i]
|
||||
}
|
||||
iw.bufOffset -= offset
|
||||
}
|
||||
|
||||
func (iw *lzInWindow) readBlock() {
|
||||
if iw.streamEnd {
|
||||
return
|
||||
}
|
||||
for {
|
||||
if iw.blockSize-iw.bufOffset-iw.streamPos == 0 {
|
||||
return
|
||||
}
|
||||
n, err := iw.r.Read(iw.buf[iw.bufOffset+iw.streamPos : iw.blockSize])
|
||||
if err != nil && err != io.EOF {
|
||||
throw(err)
|
||||
}
|
||||
if n == 0 && err == io.EOF {
|
||||
iw.posLimit = iw.streamPos
|
||||
ptr := iw.bufOffset + iw.posLimit
|
||||
if ptr > iw.lastSafePos {
|
||||
iw.posLimit = iw.lastSafePos - iw.bufOffset
|
||||
}
|
||||
iw.streamEnd = true
|
||||
return
|
||||
}
|
||||
iw.streamPos += uint32(n)
|
||||
if iw.streamPos >= iw.pos+iw.keepSizeAfter {
|
||||
iw.posLimit = iw.streamPos - iw.keepSizeAfter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (iw *lzInWindow) movePos() {
|
||||
iw.pos++
|
||||
if iw.pos > iw.posLimit {
|
||||
ptr := iw.bufOffset + iw.pos
|
||||
if ptr > iw.lastSafePos {
|
||||
iw.moveBlock()
|
||||
}
|
||||
iw.readBlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (iw *lzInWindow) getIndexByte(index int32) byte {
|
||||
return iw.buf[int32(iw.bufOffset+iw.pos)+index]
|
||||
}
|
||||
|
||||
func (iw *lzInWindow) getMatchLen(index int32, distance, limit uint32) (res uint32) {
|
||||
uIndex := uint32(index)
|
||||
if iw.streamEnd == true {
|
||||
if iw.pos+uIndex+limit > iw.streamPos {
|
||||
limit = iw.streamPos - (iw.pos + uIndex)
|
||||
}
|
||||
}
|
||||
distance++
|
||||
pby := iw.bufOffset + iw.pos + uIndex
|
||||
for res = uint32(0); res < limit && iw.buf[pby+res] == iw.buf[pby+res-distance]; res++ {
|
||||
// empty body
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (iw *lzInWindow) getNumAvailableBytes() uint32 {
|
||||
return iw.streamPos - iw.pos
|
||||
}
|
||||
|
||||
func (iw *lzInWindow) reduceOffsets(subValue uint32) {
|
||||
iw.bufOffset += subValue
|
||||
iw.posLimit -= subValue
|
||||
iw.pos -= subValue
|
||||
iw.streamPos -= subValue
|
||||
}
|
||||
+311
@@ -0,0 +1,311 @@
|
||||
// Copyright (c) 2010, Andrei Vieru. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package lzma
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
)
|
||||
|
||||
type lzmaTest struct {
|
||||
descr string
|
||||
level int
|
||||
size bool
|
||||
raw string
|
||||
lzma []byte
|
||||
err error
|
||||
}
|
||||
|
||||
var lzmaTests = []lzmaTest{
|
||||
// fmt -w 80 -s file
|
||||
// cat file | sed s/\\\\/\\\\\\\\/g | sed s/\"/\\\\\"/g | sed s/^/\"/ | sed s/$/\\\\n\"\ +/
|
||||
// hexdump -Cv file.lzma | awk '{for (i=2; i<18; i++) {printf("0x%s, ", $i); if (i==9) printf("\n");} printf("\n")}'
|
||||
lzmaTest{
|
||||
"empty test",
|
||||
3,
|
||||
true,
|
||||
"",
|
||||
[]byte{
|
||||
0x5d, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00,
|
||||
},
|
||||
nil,
|
||||
},
|
||||
lzmaTest{
|
||||
"empty (with size == -1) test",
|
||||
3,
|
||||
false,
|
||||
"",
|
||||
[]byte{
|
||||
0x5d, 0x00, 0x00, 0x10, 0x00, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x83, 0xff,
|
||||
0xfb, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00,
|
||||
},
|
||||
nil,
|
||||
},
|
||||
lzmaTest{
|
||||
"hello world test",
|
||||
3,
|
||||
true,
|
||||
"hello world\n",
|
||||
[]byte{
|
||||
0x5d, 0x00, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x19,
|
||||
0x49, 0xee, 0x8d, 0xe9, 0x17, 0x89, 0x3a, 0x33,
|
||||
0x5f, 0xfc, 0xac, 0xf7, 0x20, 0x00,
|
||||
},
|
||||
nil,
|
||||
},
|
||||
lzmaTest{
|
||||
"hello world (with size == -1) test",
|
||||
3,
|
||||
false,
|
||||
"hello world\n",
|
||||
[]byte{
|
||||
0x5d, 0x00, 0x00, 0x10, 0x00, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x34, 0x19,
|
||||
0x49, 0xee, 0x8d, 0xe9, 0x17, 0x89, 0x3a, 0x33,
|
||||
0x5f, 0xfc, 0xb2, 0x09, 0x82, 0x2f, 0xff, 0xfd,
|
||||
0xe2, 0x80, 0x00,
|
||||
},
|
||||
nil,
|
||||
},
|
||||
lzmaTest{
|
||||
"text test",
|
||||
3,
|
||||
true,
|
||||
"Two named types are identical if their type names originate in the same type\n" +
|
||||
"declaration (§Declarations and scope). A named and an unnamed type are never\n" +
|
||||
"identical. Two unnamed types are identical if the corresponding type literals\n" +
|
||||
"have the same literal structure and corresponding components have identical\n" +
|
||||
"types. In detail:\n" +
|
||||
"\n" +
|
||||
" * Two array types are identical if they have identical element types and\n" +
|
||||
"the same array length.\n" +
|
||||
" * Two slice types are identical if they have identical element types.\n" +
|
||||
" * Two struct types are identical if they have the same sequence of fields,\n" +
|
||||
"and if corresponding fields have the same names and identical types. Two\n" +
|
||||
"anonymous fields are considered to have the same name.\n" +
|
||||
" * Two pointer types are identical if they have identical base types.\n" +
|
||||
" * Two function types are identical if they have the same number of\n" +
|
||||
"parameters and result values and if corresponding parameter and result types\n" +
|
||||
"are identical. All \"...\" parameters without a specified type are defined to\n" +
|
||||
"have identical type. All \"...\" parameters with specified identical type T are\n" +
|
||||
"defined to have identical type. Parameter and result names are not required to\n" +
|
||||
"match.\n" +
|
||||
" * Two interface types are identical if they have the same set of methods\n" +
|
||||
"with the same names and identical function types. The order of the methods is\n" +
|
||||
"irrelevant.\n" +
|
||||
" * Two map types are identical if they have identical key and value types.\n" +
|
||||
" * Two channel types are identical if they have identical value types and\n" +
|
||||
"the same direction.\n",
|
||||
[]byte{
|
||||
0x5d, 0x00, 0x00, 0x10, 0x00, 0xe8, 0x05, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, 0x1d,
|
||||
0xc9, 0xe2, 0x03, 0x0c, 0x4e, 0x75, 0xc8, 0xee,
|
||||
0x65, 0x97, 0xae, 0x0a, 0x7b, 0x0a, 0x66, 0xfa,
|
||||
0x4a, 0x94, 0xe6, 0x32, 0x2f, 0x9b, 0x44, 0x8c,
|
||||
0xaf, 0xf6, 0xf4, 0x67, 0xe2, 0x8a, 0x8b, 0x2f,
|
||||
0x26, 0x41, 0xf6, 0x64, 0xe2, 0xf6, 0x00, 0xbb,
|
||||
0x1f, 0x4a, 0xb1, 0xca, 0xa6, 0xf0, 0x54, 0x9e,
|
||||
0xa5, 0x2d, 0xe7, 0x6e, 0x6a, 0x49, 0x84, 0xac,
|
||||
0xf5, 0x52, 0xcf, 0x57, 0xcd, 0xa7, 0x84, 0xe9,
|
||||
0x27, 0xdc, 0x3d, 0xf3, 0xf4, 0x1a, 0x8d, 0x98,
|
||||
0xba, 0xcc, 0x47, 0xca, 0x1b, 0x38, 0xa6, 0x8c,
|
||||
0x96, 0x24, 0x14, 0x8d, 0x1c, 0x81, 0x6b, 0xc0,
|
||||
0x1c, 0x69, 0xa1, 0x79, 0xa2, 0x20, 0x10, 0x6c,
|
||||
0x0c, 0x56, 0x40, 0x1f, 0x3f, 0x32, 0x4a, 0x4f,
|
||||
0x1f, 0x2a, 0x29, 0xf0, 0x23, 0x99, 0xbe, 0xc7,
|
||||
0x49, 0x7e, 0xc7, 0x60, 0xa3, 0x54, 0x9c, 0x06,
|
||||
0x06, 0xb4, 0xc4, 0x4d, 0x07, 0xd5, 0xef, 0xa0,
|
||||
0xcd, 0xac, 0x8f, 0x54, 0xf9, 0x15, 0x15, 0xa0,
|
||||
0xb5, 0xf1, 0x7d, 0xde, 0xeb, 0xe0, 0x41, 0x9a,
|
||||
0x3e, 0x5a, 0x66, 0xd2, 0xfc, 0x08, 0x00, 0xe8,
|
||||
0xf2, 0xc9, 0x04, 0x94, 0x7e, 0x0b, 0x06, 0x56,
|
||||
0x9d, 0xe7, 0x1f, 0x48, 0x21, 0x4d, 0x18, 0x21,
|
||||
0xa3, 0x5f, 0x69, 0x7a, 0xed, 0x4d, 0x2d, 0xb4,
|
||||
0x3e, 0x34, 0xcf, 0x9e, 0xae, 0x11, 0x2d, 0x15,
|
||||
0xbc, 0x2c, 0xd7, 0xef, 0x1c, 0xd5, 0xd8, 0xd3,
|
||||
0xc2, 0x4f, 0xcb, 0x2c, 0xb1, 0xa3, 0x85, 0x0a,
|
||||
0xad, 0x44, 0x68, 0x42, 0xab, 0x40, 0x58, 0xa2,
|
||||
0x9b, 0xdf, 0xbd, 0xa2, 0x10, 0xc7, 0x2e, 0x5a,
|
||||
0x1d, 0x07, 0xe4, 0xb5, 0xa6, 0xa8, 0xb3, 0x12,
|
||||
0x44, 0x64, 0x18, 0xaf, 0xa7, 0x72, 0x05, 0x29,
|
||||
0x48, 0xc6, 0x78, 0xef, 0x7f, 0x54, 0xe9, 0xb3,
|
||||
0xae, 0x10, 0x51, 0x11, 0x91, 0xf6, 0x96, 0x40,
|
||||
0x6a, 0xf1, 0xc8, 0x3d, 0x46, 0x86, 0x2e, 0xd3,
|
||||
0xa1, 0xdc, 0x38, 0x97, 0x11, 0x49, 0x4b, 0x03,
|
||||
0x91, 0xa5, 0xed, 0x53, 0x3f, 0xd5, 0x87, 0x04,
|
||||
0x25, 0xf0, 0xe2, 0x19, 0x55, 0x99, 0x6b, 0xad,
|
||||
0xa2, 0x9c, 0x7a, 0xcd, 0xc9, 0x34, 0x18, 0x8f,
|
||||
0x4c, 0xaa, 0xed, 0xd3, 0x49, 0x98, 0xd9, 0x67,
|
||||
0xbe, 0x41, 0x85, 0xbf, 0x09, 0x22, 0x16, 0xa0,
|
||||
0x81, 0x71, 0x38, 0xdb, 0xb2, 0xd6, 0xfe, 0x2b,
|
||||
0xf4, 0x03, 0xf8, 0xd1, 0x00, 0x16, 0x5e, 0x77,
|
||||
0x69, 0xd4, 0x28, 0x8f, 0x94, 0x4d, 0x58, 0x4f,
|
||||
0xae, 0x6b, 0xb1, 0x09, 0x85, 0x71, 0xd7, 0x3a,
|
||||
0x4b, 0xea, 0xd2, 0x70, 0xbb, 0xa2, 0x20, 0x85,
|
||||
0x2d, 0xbd, 0x57, 0x3f, 0x81, 0x3e, 0xe4, 0xa2,
|
||||
0x43, 0x3e, 0xee, 0x04, 0xbe, 0x42, 0x51, 0xaa,
|
||||
0xad, 0xda, 0x53, 0x87, 0xeb, 0xc0, 0x9a, 0xde,
|
||||
0xa2, 0x7e, 0x19, 0x4f, 0xa7, 0xdf, 0x23, 0x96,
|
||||
0xdd, 0xe3, 0x18, 0x0f, 0xc0, 0x48, 0xfd, 0x9f,
|
||||
0x13, 0xc3, 0x3c, 0x3a, 0x7b, 0x7c, 0xa8, 0x89,
|
||||
0x6a, 0xd6, 0x68, 0xdd, 0x3b, 0xc6, 0xc6, 0x44,
|
||||
0x79, 0xc5, 0x59, 0x1c, 0x23, 0xa9, 0x8b, 0x00,
|
||||
0x62, 0xeb, 0x3a, 0x51, 0x14, 0x5e, 0x63, 0x79,
|
||||
0x97, 0x60, 0x62, 0x13, 0x49, 0x41, 0x06, 0xf4,
|
||||
0x5f, 0xe7, 0x9b, 0xd6, 0x51, 0x31, 0x6e, 0x7d,
|
||||
0x5e, 0xe8, 0x72, 0xce, 0x5e, 0xd0, 0xa7, 0x9d,
|
||||
0xa3, 0xa9, 0x92, 0xa8, 0x2f, 0x78, 0x00, 0x92,
|
||||
0x4f, 0xf0, 0x21, 0xb6, 0x74, 0xfb, 0x3c, 0xe2,
|
||||
0x60, 0xdf, 0x82, 0x09, 0x07, 0xb3, 0x68, 0x5b,
|
||||
0xe2, 0x49, 0xeb, 0x81, 0x98, 0x23, 0x19, 0xdc,
|
||||
0x2c, 0xa1, 0xad, 0x95, 0xc0, 0x9a, 0x48, 0xcd,
|
||||
0xa8, 0xe4, 0xdf, 0xbb, 0xca, 0xd4, 0x47, 0x46,
|
||||
0xe2, 0xdb, 0x5c, 0xe2, 0xdb, 0xb2, 0x32, 0x44,
|
||||
0x87, 0x0a, 0x49, 0x99, 0xc6, 0x32, 0x75, 0xc6,
|
||||
0xe7, 0x1b, 0xf1, 0x71, 0x74, 0x32, 0x4a, 0x17,
|
||||
0x2f, 0xe8, 0x00,
|
||||
},
|
||||
nil,
|
||||
},
|
||||
lzmaTest{
|
||||
"text test with size == -1",
|
||||
3,
|
||||
false,
|
||||
"Two named types are identical if their type names originate in the same type\n" +
|
||||
"declaration (§Declarations and scope). A named and an unnamed type are never\n" +
|
||||
"identical. Two unnamed types are identical if the corresponding type literals\n" +
|
||||
"have the same literal structure and corresponding components have identical\n" +
|
||||
"types. In detail:\n" +
|
||||
"\n" +
|
||||
" * Two array types are identical if they have identical element types and\n" +
|
||||
"the same array length.\n" +
|
||||
" * Two slice types are identical if they have identical element types.\n" +
|
||||
" * Two struct types are identical if they have the same sequence of fields,\n" +
|
||||
"and if corresponding fields have the same names and identical types. Two\n" +
|
||||
"anonymous fields are considered to have the same name.\n" +
|
||||
" * Two pointer types are identical if they have identical base types.\n" +
|
||||
" * Two function types are identical if they have the same number of\n" +
|
||||
"parameters and result values and if corresponding parameter and result types\n" +
|
||||
"are identical. All \"...\" parameters without a specified type are defined to\n" +
|
||||
"have identical type. All \"...\" parameters with specified identical type T are\n" +
|
||||
"defined to have identical type. Parameter and result names are not required to\n" +
|
||||
"match.\n" +
|
||||
" * Two interface types are identical if they have the same set of methods\n" +
|
||||
"with the same names and identical function types. The order of the methods is\n" +
|
||||
"irrelevant.\n" +
|
||||
" * Two map types are identical if they have identical key and value types.\n" +
|
||||
" * Two channel types are identical if they have identical value types and\n" +
|
||||
"the same direction.\n",
|
||||
[]byte{
|
||||
0x5d, 0x00, 0x00, 0x10, 0x00, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x2a, 0x1d,
|
||||
0xc9, 0xe2, 0x03, 0x0c, 0x4e, 0x75, 0xc8, 0xee,
|
||||
0x65, 0x97, 0xae, 0x0a, 0x7b, 0x0a, 0x66, 0xfa,
|
||||
0x4a, 0x94, 0xe6, 0x32, 0x2f, 0x9b, 0x44, 0x8c,
|
||||
0xaf, 0xf6, 0xf4, 0x67, 0xe2, 0x8a, 0x8b, 0x2f,
|
||||
0x26, 0x41, 0xf6, 0x64, 0xe2, 0xf6, 0x00, 0xbb,
|
||||
0x1f, 0x4a, 0xb1, 0xca, 0xa6, 0xf0, 0x54, 0x9e,
|
||||
0xa5, 0x2d, 0xe7, 0x6e, 0x6a, 0x49, 0x84, 0xac,
|
||||
0xf5, 0x52, 0xcf, 0x57, 0xcd, 0xa7, 0x84, 0xe9,
|
||||
0x27, 0xdc, 0x3d, 0xf3, 0xf4, 0x1a, 0x8d, 0x98,
|
||||
0xba, 0xcc, 0x47, 0xca, 0x1b, 0x38, 0xa6, 0x8c,
|
||||
0x96, 0x24, 0x14, 0x8d, 0x1c, 0x81, 0x6b, 0xc0,
|
||||
0x1c, 0x69, 0xa1, 0x79, 0xa2, 0x20, 0x10, 0x6c,
|
||||
0x0c, 0x56, 0x40, 0x1f, 0x3f, 0x32, 0x4a, 0x4f,
|
||||
0x1f, 0x2a, 0x29, 0xf0, 0x23, 0x99, 0xbe, 0xc7,
|
||||
0x49, 0x7e, 0xc7, 0x60, 0xa3, 0x54, 0x9c, 0x06,
|
||||
0x06, 0xb4, 0xc4, 0x4d, 0x07, 0xd5, 0xef, 0xa0,
|
||||
0xcd, 0xac, 0x8f, 0x54, 0xf9, 0x15, 0x15, 0xa0,
|
||||
0xb5, 0xf1, 0x7d, 0xde, 0xeb, 0xe0, 0x41, 0x9a,
|
||||
0x3e, 0x5a, 0x66, 0xd2, 0xfc, 0x08, 0x00, 0xe8,
|
||||
0xf2, 0xc9, 0x04, 0x94, 0x7e, 0x0b, 0x06, 0x56,
|
||||
0x9d, 0xe7, 0x1f, 0x48, 0x21, 0x4d, 0x18, 0x21,
|
||||
0xa3, 0x5f, 0x69, 0x7a, 0xed, 0x4d, 0x2d, 0xb4,
|
||||
0x3e, 0x34, 0xcf, 0x9e, 0xae, 0x11, 0x2d, 0x15,
|
||||
0xbc, 0x2c, 0xd7, 0xef, 0x1c, 0xd5, 0xd8, 0xd3,
|
||||
0xc2, 0x4f, 0xcb, 0x2c, 0xb1, 0xa3, 0x85, 0x0a,
|
||||
0xad, 0x44, 0x68, 0x42, 0xab, 0x40, 0x58, 0xa2,
|
||||
0x9b, 0xdf, 0xbd, 0xa2, 0x10, 0xc7, 0x2e, 0x5a,
|
||||
0x1d, 0x07, 0xe4, 0xb5, 0xa6, 0xa8, 0xb3, 0x12,
|
||||
0x44, 0x64, 0x18, 0xaf, 0xa7, 0x72, 0x05, 0x29,
|
||||
0x48, 0xc6, 0x78, 0xef, 0x7f, 0x54, 0xe9, 0xb3,
|
||||
0xae, 0x10, 0x51, 0x11, 0x91, 0xf6, 0x96, 0x40,
|
||||
0x6a, 0xf1, 0xc8, 0x3d, 0x46, 0x86, 0x2e, 0xd3,
|
||||
0xa1, 0xdc, 0x38, 0x97, 0x11, 0x49, 0x4b, 0x03,
|
||||
0x91, 0xa5, 0xed, 0x53, 0x3f, 0xd5, 0x87, 0x04,
|
||||
0x25, 0xf0, 0xe2, 0x19, 0x55, 0x99, 0x6b, 0xad,
|
||||
0xa2, 0x9c, 0x7a, 0xcd, 0xc9, 0x34, 0x18, 0x8f,
|
||||
0x4c, 0xaa, 0xed, 0xd3, 0x49, 0x98, 0xd9, 0x67,
|
||||
0xbe, 0x41, 0x85, 0xbf, 0x09, 0x22, 0x16, 0xa0,
|
||||
0x81, 0x71, 0x38, 0xdb, 0xb2, 0xd6, 0xfe, 0x2b,
|
||||
0xf4, 0x03, 0xf8, 0xd1, 0x00, 0x16, 0x5e, 0x77,
|
||||
0x69, 0xd4, 0x28, 0x8f, 0x94, 0x4d, 0x58, 0x4f,
|
||||
0xae, 0x6b, 0xb1, 0x09, 0x85, 0x71, 0xd7, 0x3a,
|
||||
0x4b, 0xea, 0xd2, 0x70, 0xbb, 0xa2, 0x20, 0x85,
|
||||
0x2d, 0xbd, 0x57, 0x3f, 0x81, 0x3e, 0xe4, 0xa2,
|
||||
0x43, 0x3e, 0xee, 0x04, 0xbe, 0x42, 0x51, 0xaa,
|
||||
0xad, 0xda, 0x53, 0x87, 0xeb, 0xc0, 0x9a, 0xde,
|
||||
0xa2, 0x7e, 0x19, 0x4f, 0xa7, 0xdf, 0x23, 0x96,
|
||||
0xdd, 0xe3, 0x18, 0x0f, 0xc0, 0x48, 0xfd, 0x9f,
|
||||
0x13, 0xc3, 0x3c, 0x3a, 0x7b, 0x7c, 0xa8, 0x89,
|
||||
0x6a, 0xd6, 0x68, 0xdd, 0x3b, 0xc6, 0xc6, 0x44,
|
||||
0x79, 0xc5, 0x59, 0x1c, 0x23, 0xa9, 0x8b, 0x00,
|
||||
0x62, 0xeb, 0x3a, 0x51, 0x14, 0x5e, 0x63, 0x79,
|
||||
0x97, 0x60, 0x62, 0x13, 0x49, 0x41, 0x06, 0xf4,
|
||||
0x5f, 0xe7, 0x9b, 0xd6, 0x51, 0x31, 0x6e, 0x7d,
|
||||
0x5e, 0xe8, 0x72, 0xce, 0x5e, 0xd0, 0xa7, 0x9d,
|
||||
0xa3, 0xa9, 0x92, 0xa8, 0x2f, 0x78, 0x00, 0x92,
|
||||
0x4f, 0xf0, 0x21, 0xb6, 0x74, 0xfb, 0x3c, 0xe2,
|
||||
0x60, 0xdf, 0x82, 0x09, 0x07, 0xb3, 0x68, 0x5b,
|
||||
0xe2, 0x49, 0xeb, 0x81, 0x98, 0x23, 0x19, 0xdc,
|
||||
0x2c, 0xa1, 0xad, 0x95, 0xc0, 0x9a, 0x48, 0xcd,
|
||||
0xa8, 0xe4, 0xdf, 0xbb, 0xca, 0xd4, 0x47, 0x46,
|
||||
0xe2, 0xdb, 0x5c, 0xe2, 0xdb, 0xb2, 0x32, 0x44,
|
||||
0x87, 0x0a, 0x49, 0x99, 0xc6, 0x32, 0x75, 0xc6,
|
||||
0xe7, 0x1b, 0xf1, 0x71, 0x74, 0x32, 0x4a, 0x8d,
|
||||
0xf2, 0x0a, 0x64, 0xff, 0xe4, 0x0d, 0xf4, 0xa2,
|
||||
},
|
||||
nil,
|
||||
},
|
||||
lzmaTest{
|
||||
"hello world test with corrupt lc, lp, pb in header",
|
||||
3,
|
||||
true,
|
||||
"hello world\n",
|
||||
[]byte{
|
||||
0xfe, 0x00, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x19,
|
||||
0x49, 0xee, 0x8d, 0xe9, 0x17, 0x89, 0x3a, 0x33,
|
||||
0x5f, 0xfc, 0xac, 0xf7, 0x20, 0x00,
|
||||
},
|
||||
headerError,
|
||||
},
|
||||
}
|
||||
|
||||
type lzmaBenchmark struct {
|
||||
descr string
|
||||
level int
|
||||
raw []byte
|
||||
lzma []byte
|
||||
}
|
||||
|
||||
func readFile(filename string) []byte {
|
||||
file, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
return file
|
||||
}
|
||||
|
||||
var bench = lzmaBenchmark{
|
||||
descr: "text bench with size == -1",
|
||||
level: 3,
|
||||
raw: readFile("data/data.txt"),
|
||||
lzma: readFile("data/data.eos.l3.lzma"),
|
||||
}
|
||||
+369
@@ -0,0 +1,369 @@
|
||||
// Copyright (c) 2010, Andrei Vieru. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// The lzma package implements reading and writing of LZMA format compressed data.
|
||||
// Reference implementation is LZMA SDK version 4.65 originaly developed by Igor
|
||||
// Pavlov, available online at:
|
||||
//
|
||||
// http://www.7-zip.org/sdk.html
|
||||
//
|
||||
//
|
||||
//
|
||||
// Usage examples. Write compressed data to a buffer:
|
||||
//
|
||||
// var b bytes.Buffer
|
||||
// w := lzma.NewWriter(&b)
|
||||
// w.Write([]byte("hello, world\n"))
|
||||
// w.Close()
|
||||
//
|
||||
// read that data back:
|
||||
//
|
||||
// r := lzma.NewReader(&b)
|
||||
// io.Copy(os.Stdout, r)
|
||||
// r.Close()
|
||||
//
|
||||
//
|
||||
//
|
||||
// If the data is bigger than you'd like to hold into memory, use pipes. Write
|
||||
// compressed data to an io.PipeWriter:
|
||||
//
|
||||
// pr, pw := io.Pipe()
|
||||
// go func() {
|
||||
// defer pw.Close()
|
||||
// w := lzma.NewWriter(pw)
|
||||
// defer w.Close()
|
||||
// // the bytes.Buffer would be an io.Reader used to read uncompressed data from
|
||||
// io.Copy(w, bytes.NewBuffer([]byte("hello, world\n")))
|
||||
// }()
|
||||
//
|
||||
// and read it back:
|
||||
//
|
||||
// defer pr.Close()
|
||||
// r := lzma.NewReader(pr)
|
||||
// defer r.Close()
|
||||
// // the os.Stdout would be an io.Writer used to write uncompressed data to
|
||||
// io.Copy(os.Stdout, r)
|
||||
//
|
||||
//
|
||||
//
|
||||
package lzma
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
inBufSize = 1 << 16
|
||||
outBufSize = 1 << 16
|
||||
lzmaPropSize = 5
|
||||
lzmaHeaderSize = lzmaPropSize + 8
|
||||
lzmaMaxReqInputSize = 20
|
||||
|
||||
kNumRepDistances = 4
|
||||
kNumStates = 12
|
||||
kNumPosSlotBits = 6
|
||||
kDicLogSizeMin = 0
|
||||
kNumLenToPosStatesBits = 2
|
||||
kNumLenToPosStates = 1 << kNumLenToPosStatesBits
|
||||
kMatchMinLen = 2
|
||||
kNumAlignBits = 4
|
||||
kAlignTableSize = 1 << kNumAlignBits
|
||||
kAlignMask = kAlignTableSize - 1
|
||||
kStartPosModelIndex = 4
|
||||
kEndPosModelIndex = 14
|
||||
kNumPosModels = kEndPosModelIndex - kStartPosModelIndex
|
||||
kNumFullDistances = 1 << (kEndPosModelIndex / 2)
|
||||
kNumLitPosStatesBitsEncodingMax = 4
|
||||
kNumLitContextBitsMax = 8
|
||||
kNumPosStatesBitsMax = 4
|
||||
kNumPosStatesMax = 1 << kNumPosStatesBitsMax
|
||||
kNumLowLenBits = 3
|
||||
kNumMidLenBits = 3
|
||||
kNumHighLenBits = 8
|
||||
kNumLowLenSymbols = 1 << kNumLowLenBits
|
||||
kNumMidLenSymbols = 1 << kNumMidLenBits
|
||||
kNumLenSymbols = kNumLowLenSymbols + kNumMidLenSymbols + (1 << kNumHighLenBits)
|
||||
kMatchMaxLen = kMatchMinLen + kNumLenSymbols - 1
|
||||
)
|
||||
|
||||
// A streamError reports the presence of corrupt input stream.
|
||||
var streamError = errors.New("error in lzma encoded data stream")
|
||||
|
||||
// A headerError reports an error in the header of the lzma encoder file.
|
||||
var headerError = errors.New("error in lzma header")
|
||||
|
||||
// A nReadError reports what its message reads
|
||||
var nReadError = errors.New("number of bytes returned by Reader.Read() didn't meet expectances")
|
||||
|
||||
// A nWriteError reports what its message reads
|
||||
var nWriteError = errors.New("number of bytes returned by Writer.Write() didn't meet expectances")
|
||||
|
||||
// TODO: implement this err
|
||||
// A dataIntegrityError reports an error encountered while cheching data integrity.
|
||||
// -- from lzma.txt:
|
||||
// You can use multiple checks to test data integrity after full decompression:
|
||||
// 1) Check Result and "status" variable.
|
||||
// 2) Check that output(destLen) = uncompressedSize, if you know real uncompressedSize.
|
||||
// 3) Check that output(srcLen) = compressedSize, if you know real compressedSize.
|
||||
// You must use correct finish mode in that case.
|
||||
//
|
||||
//type dataIntegrityError struct {
|
||||
// msg string
|
||||
// // hz
|
||||
//}
|
||||
|
||||
func stateUpdateChar(index uint32) uint32 {
|
||||
if index < 4 {
|
||||
return 0
|
||||
}
|
||||
if index < 10 {
|
||||
return index - 3
|
||||
}
|
||||
return index - 6
|
||||
}
|
||||
|
||||
func stateUpdateMatch(index uint32) uint32 {
|
||||
if index < 7 {
|
||||
return 7
|
||||
}
|
||||
return 10
|
||||
}
|
||||
|
||||
func stateUpdateRep(index uint32) uint32 {
|
||||
if index < 7 {
|
||||
return 8
|
||||
}
|
||||
return 11
|
||||
}
|
||||
|
||||
func stateUpdateShortRep(index uint32) uint32 {
|
||||
if index < 7 {
|
||||
return 9
|
||||
}
|
||||
return 11
|
||||
}
|
||||
|
||||
func stateIsCharState(index uint32) bool {
|
||||
if index < 7 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getLenToPosState(length uint32) uint32 {
|
||||
length -= kMatchMinLen
|
||||
if length < kNumLenToPosStates {
|
||||
return length
|
||||
}
|
||||
return kNumLenToPosStates - 1
|
||||
}
|
||||
|
||||
// LZMA compressed file format
|
||||
// ---------------------------
|
||||
// Offset Size Description
|
||||
// 0 1 Special LZMA properties (lc,lp, pb in encoded form)
|
||||
// 1 4 Dictionary size (little endian)
|
||||
// 5 8 Uncompressed size (little endian). Size -1 stands for unknown size
|
||||
|
||||
// lzma properties
|
||||
type props struct {
|
||||
litContextBits, // lc
|
||||
litPosStateBits, // lp
|
||||
posStateBits uint8 // pb
|
||||
dictSize uint32
|
||||
}
|
||||
|
||||
func (p *props) decodeProps(buf []byte) {
|
||||
d := buf[0]
|
||||
if d > (9 * 5 * 5) {
|
||||
throw(headerError)
|
||||
}
|
||||
p.litContextBits = d % 9
|
||||
d /= 9
|
||||
p.posStateBits = d / 5
|
||||
p.litPosStateBits = d % 5
|
||||
if p.litContextBits > kNumLitContextBitsMax || p.litPosStateBits > 4 || p.posStateBits > kNumPosStatesBitsMax {
|
||||
throw(headerError)
|
||||
}
|
||||
for i := 0; i < 4; i++ {
|
||||
p.dictSize += uint32(buf[i+1]) << uint32(i*8)
|
||||
}
|
||||
}
|
||||
|
||||
type decoder struct {
|
||||
// i/o
|
||||
rd *rangeDecoder // r
|
||||
outWin *lzOutWindow // w
|
||||
|
||||
// lzma header
|
||||
prop *props
|
||||
unpackSize int64
|
||||
|
||||
// hz
|
||||
matchDecoders []uint16
|
||||
repDecoders []uint16
|
||||
repG0Decoders []uint16
|
||||
repG1Decoders []uint16
|
||||
repG2Decoders []uint16
|
||||
rep0LongDecoders []uint16
|
||||
posSlotCoders []*rangeBitTreeCoder
|
||||
posDecoders []uint16
|
||||
posAlignCoder *rangeBitTreeCoder
|
||||
lenCoder *lenCoder
|
||||
repLenCoder *lenCoder
|
||||
litCoder *litCoder
|
||||
dictSizeCheck uint32
|
||||
posStateMask uint32
|
||||
}
|
||||
|
||||
func (z *decoder) doDecode() {
|
||||
var state uint32 = 0
|
||||
var rep0 uint32 = 0
|
||||
var rep1 uint32 = 0
|
||||
var rep2 uint32 = 0
|
||||
var rep3 uint32 = 0
|
||||
var nowPos uint64 = 0
|
||||
var prevByte byte = 0
|
||||
|
||||
for z.unpackSize < 0 || int64(nowPos) < z.unpackSize {
|
||||
posState := uint32(nowPos) & z.posStateMask
|
||||
if z.rd.decodeBit(z.matchDecoders, state<<kNumPosStatesBitsMax+posState) == 0 {
|
||||
lsc := z.litCoder.getSubCoder(uint32(nowPos), prevByte)
|
||||
if !stateIsCharState(state) {
|
||||
prevByte = lsc.decodeWithMatchByte(z.rd, z.outWin.getByte(rep0))
|
||||
} else {
|
||||
prevByte = lsc.decodeNormal(z.rd)
|
||||
}
|
||||
z.outWin.putByte(prevByte)
|
||||
state = stateUpdateChar(state)
|
||||
nowPos++
|
||||
} else {
|
||||
var length uint32
|
||||
if z.rd.decodeBit(z.repDecoders, state) == 1 {
|
||||
length = 0
|
||||
if z.rd.decodeBit(z.repG0Decoders, state) == 0 {
|
||||
if z.rd.decodeBit(z.rep0LongDecoders, state<<kNumPosStatesBitsMax+posState) == 0 {
|
||||
state = stateUpdateShortRep(state)
|
||||
length = 1
|
||||
}
|
||||
} else {
|
||||
var distance uint32
|
||||
if z.rd.decodeBit(z.repG1Decoders, state) == 0 {
|
||||
distance = rep1
|
||||
} else {
|
||||
if z.rd.decodeBit(z.repG2Decoders, state) == 0 {
|
||||
distance = rep2
|
||||
} else {
|
||||
distance, rep3 = rep3, rep2
|
||||
}
|
||||
rep2 = rep1
|
||||
}
|
||||
rep1, rep0 = rep0, distance
|
||||
}
|
||||
if length == 0 {
|
||||
length = z.repLenCoder.decode(z.rd, posState) + kMatchMinLen
|
||||
state = stateUpdateRep(state)
|
||||
}
|
||||
} else {
|
||||
rep3, rep2, rep1 = rep2, rep1, rep0
|
||||
length = z.lenCoder.decode(z.rd, posState) + kMatchMinLen
|
||||
state = stateUpdateMatch(state)
|
||||
posSlot := z.posSlotCoders[getLenToPosState(length)].decode(z.rd)
|
||||
if posSlot >= kStartPosModelIndex {
|
||||
numDirectBits := posSlot>>1 - 1
|
||||
rep0 = (2 | posSlot&1) << numDirectBits
|
||||
if posSlot < kEndPosModelIndex {
|
||||
rep0 += reverseDecodeIndex(z.rd, z.posDecoders, rep0-posSlot-1, numDirectBits)
|
||||
} else {
|
||||
rep0 += z.rd.decodeDirectBits(numDirectBits-kNumAlignBits) << kNumAlignBits
|
||||
rep0 += z.posAlignCoder.reverseDecode(z.rd)
|
||||
if int32(rep0) < 0 {
|
||||
if rep0 == 0xFFFFFFFF {
|
||||
break
|
||||
}
|
||||
throw(streamError)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
rep0 = posSlot
|
||||
}
|
||||
}
|
||||
if uint64(rep0) >= nowPos || rep0 >= z.dictSizeCheck {
|
||||
throw(streamError)
|
||||
}
|
||||
z.outWin.copyBlock(rep0, length)
|
||||
nowPos += uint64(length)
|
||||
prevByte = z.outWin.getByte(0)
|
||||
}
|
||||
}
|
||||
z.outWin.flush()
|
||||
//if z.unpackSize != -1 {
|
||||
// if z.outWin.unpacked != z.unpackSize {
|
||||
// throw(&dataIntegrityError{})
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
func (z *decoder) decoder(r io.Reader, w io.Writer) (err error) {
|
||||
defer handlePanics(&err)
|
||||
|
||||
// read 13 bytes (lzma header)
|
||||
header := make([]byte, lzmaHeaderSize)
|
||||
n, err := r.Read(header)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if n != lzmaHeaderSize {
|
||||
return nReadError
|
||||
}
|
||||
z.prop = &props{}
|
||||
z.prop.decodeProps(header)
|
||||
|
||||
z.unpackSize = 0
|
||||
for i := 0; i < 8; i++ {
|
||||
b := header[lzmaPropSize+i]
|
||||
z.unpackSize = z.unpackSize | int64(b)<<uint64(8*i)
|
||||
}
|
||||
|
||||
// do not move before r.Read(header)
|
||||
z.rd = newRangeDecoder(r)
|
||||
|
||||
z.dictSizeCheck = maxUInt32(z.prop.dictSize, 1)
|
||||
z.outWin = newLzOutWindow(w, maxUInt32(z.dictSizeCheck, 1<<12))
|
||||
|
||||
z.litCoder = newLitCoder(uint32(z.prop.litPosStateBits), uint32(z.prop.litContextBits))
|
||||
z.lenCoder = newLenCoder(uint32(1 << z.prop.posStateBits))
|
||||
z.repLenCoder = newLenCoder(uint32(1 << z.prop.posStateBits))
|
||||
z.posStateMask = uint32(1<<z.prop.posStateBits - 1)
|
||||
z.matchDecoders = initBitModels(kNumStates << kNumPosStatesBitsMax)
|
||||
z.repDecoders = initBitModels(kNumStates)
|
||||
z.repG0Decoders = initBitModels(kNumStates)
|
||||
z.repG1Decoders = initBitModels(kNumStates)
|
||||
z.repG2Decoders = initBitModels(kNumStates)
|
||||
z.rep0LongDecoders = initBitModels(kNumStates << kNumPosStatesBitsMax)
|
||||
z.posDecoders = initBitModels(kNumFullDistances - kEndPosModelIndex)
|
||||
z.posSlotCoders = make([]*rangeBitTreeCoder, kNumLenToPosStates)
|
||||
for i := 0; i < kNumLenToPosStates; i++ {
|
||||
z.posSlotCoders[i] = newRangeBitTreeCoder(kNumPosSlotBits)
|
||||
}
|
||||
z.posAlignCoder = newRangeBitTreeCoder(kNumAlignBits)
|
||||
|
||||
z.doDecode()
|
||||
return
|
||||
}
|
||||
|
||||
// NewReader returns a new ReadCloser that can be used to read the uncompressed
|
||||
// version of r. It is the caller's responsibility to call Close on the ReadCloser
|
||||
// when finished reading.
|
||||
//
|
||||
func NewReader(r io.Reader) io.ReadCloser {
|
||||
var z decoder
|
||||
pr, pw := io.Pipe()
|
||||
go func() {
|
||||
err := z.decoder(r, pw)
|
||||
pw.CloseWithError(err)
|
||||
}()
|
||||
return pr
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
// Copyright (c) 2010, Andrei Vieru. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package lzma
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"log"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDecoder(t *testing.T) {
|
||||
b := new(bytes.Buffer)
|
||||
for _, tt := range lzmaTests {
|
||||
in := bytes.NewBuffer(tt.lzma)
|
||||
r := NewReader(in)
|
||||
defer r.Close()
|
||||
b.Reset()
|
||||
n, err := io.Copy(b, r)
|
||||
if err != tt.err {
|
||||
t.Errorf("%s: io.Copy: %v, want %v", tt.descr, err, tt.err)
|
||||
}
|
||||
if err == nil { // if err != nil, there is little chance that data is decoded correctly, if at all
|
||||
s := b.String()
|
||||
if s != tt.raw {
|
||||
t.Errorf("%s: got %d-byte %q, want %d-byte %q", tt.descr, n, s, len(tt.raw), tt.raw)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDecoder(b *testing.B) {
|
||||
b.StopTimer()
|
||||
buf := new(bytes.Buffer)
|
||||
for i := 0; i < b.N; i++ {
|
||||
buf.Reset()
|
||||
in := bytes.NewBuffer(bench.lzma)
|
||||
b.StartTimer()
|
||||
// timer starts before this contructor because variable "in" already
|
||||
// contains data, so the decoding start rigth away
|
||||
r := NewReader(in)
|
||||
n, err := io.Copy(buf, r)
|
||||
b.StopTimer()
|
||||
if err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
b.SetBytes(n)
|
||||
r.Close()
|
||||
}
|
||||
if bytes.Equal(buf.Bytes(), bench.raw) == false { // check only after last iteration
|
||||
log.Fatalf("%s: got %d-byte %q, want %d-byte %q", bench.descr, len(buf.Bytes()), buf.String(), len(bench.raw), bench.raw)
|
||||
}
|
||||
}
|
||||
+1150
File diff suppressed because it is too large.
Load diff
+157
@@ -0,0 +1,157 @@
|
||||
// Copyright (c) 2010, Andrei Vieru. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package lzma
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func pipe(t *testing.T, efunc func(io.WriteCloser), dfunc func(io.ReadCloser), size int64) {
|
||||
level := 3
|
||||
pr, pw := io.Pipe()
|
||||
go func() {
|
||||
defer pw.Close()
|
||||
ze := NewWriterSizeLevel(pw, size, level)
|
||||
defer ze.Close()
|
||||
efunc(ze)
|
||||
}()
|
||||
defer pr.Close()
|
||||
zd := NewReader(pr)
|
||||
defer zd.Close()
|
||||
dfunc(zd)
|
||||
}
|
||||
|
||||
func testEmpty(t *testing.T, sizeIsKnown bool) {
|
||||
size := int64(-1)
|
||||
if sizeIsKnown == true {
|
||||
size = 0
|
||||
}
|
||||
pipe(t,
|
||||
func(w io.WriteCloser) {},
|
||||
func(r io.ReadCloser) {
|
||||
b, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
if len(b) != 0 {
|
||||
t.Fatalf("did not read an empty slice")
|
||||
}
|
||||
},
|
||||
size)
|
||||
}
|
||||
|
||||
func TestEmpty1(t *testing.T) {
|
||||
testEmpty(t, true)
|
||||
}
|
||||
|
||||
func TestEmpty2(t *testing.T) {
|
||||
testEmpty(t, false)
|
||||
}
|
||||
|
||||
func testBoth(t *testing.T, sizeIsKnown bool) {
|
||||
size := int64(-1)
|
||||
payload := []byte("lzmalzmalzma")
|
||||
if sizeIsKnown == true {
|
||||
size = int64(len(payload))
|
||||
}
|
||||
pipe(t,
|
||||
func(w io.WriteCloser) {
|
||||
n, err := w.Write(payload)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
if n != len(payload) {
|
||||
t.Fatalf("wrote %d bytes, want %d bytes", n, len(payload))
|
||||
}
|
||||
},
|
||||
func(r io.ReadCloser) {
|
||||
b, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
if string(b) != string(payload) {
|
||||
t.Fatalf("payload is %s, want %s", string(b), string(payload))
|
||||
}
|
||||
},
|
||||
size)
|
||||
}
|
||||
|
||||
func TestBoth1(t *testing.T) {
|
||||
testBoth(t, true)
|
||||
}
|
||||
|
||||
func TestBoth2(t *testing.T) {
|
||||
testBoth(t, false)
|
||||
}
|
||||
|
||||
func TestEncoder(t *testing.T) {
|
||||
b := new(bytes.Buffer)
|
||||
for _, tt := range lzmaTests {
|
||||
if tt.err == nil {
|
||||
pr, pw := io.Pipe()
|
||||
defer pr.Close()
|
||||
in := bytes.NewBuffer([]byte(tt.raw))
|
||||
size := int64(-1)
|
||||
if tt.size == true {
|
||||
size = int64(len([]byte(tt.raw)))
|
||||
}
|
||||
go func() {
|
||||
defer pw.Close()
|
||||
w := NewWriterSizeLevel(pw, size, tt.level)
|
||||
defer w.Close()
|
||||
_, err := io.Copy(w, in)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
}()
|
||||
b.Reset()
|
||||
_, err := io.Copy(b, pr)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
res := b.Bytes()
|
||||
if bytes.Equal(res, tt.lzma) == false {
|
||||
t.Errorf("%s: got %d-byte %q, want %d-byte %q", tt.descr, len(res), string(res), len(tt.lzma), string(tt.lzma))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncoder(b *testing.B) {
|
||||
b.StopTimer()
|
||||
buf := new(bytes.Buffer)
|
||||
start := make(chan bool)
|
||||
for i := 0; i < b.N; i++ {
|
||||
in := bytes.NewBuffer(bench.raw)
|
||||
pr, pw := io.Pipe()
|
||||
defer pr.Close()
|
||||
go func() {
|
||||
defer pw.Close()
|
||||
w := NewWriterLevel(pw, bench.level)
|
||||
defer w.Close()
|
||||
start <- true
|
||||
n, err := io.Copy(w, in)
|
||||
if err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
b.SetBytes(n)
|
||||
}()
|
||||
buf.Reset()
|
||||
<-start // wait for encoder to start encoding before starting the timer
|
||||
b.StartTimer()
|
||||
_, err := io.Copy(buf, pr)
|
||||
b.StopTimer()
|
||||
if err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
}
|
||||
if bytes.Equal(buf.Bytes(), bench.lzma) == false { // check only once, not at every iteration
|
||||
log.Fatalf("%s: got %d-byte %q, want %d-byte %q", bench.descr, len(buf.Bytes()), buf.String(), len(bench.lzma), string(bench.lzma))
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) 2010, Andrei Vieru. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of the author nor the names of its contributors
|
||||
// may be used to endorse or promote products derived from this software
|
||||
// without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+261
@@ -0,0 +1,261 @@
|
||||
// Copyright (c) 2010, Andrei Vieru. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
//"compress/lzma"
|
||||
"github.com/smira/lzma"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
stdout = flag.Bool("c", false, "write on standard output, keep original files unchanged")
|
||||
decompress = flag.Bool("d", false, "decompress; see also -c and -k")
|
||||
force = flag.Bool("f", false, "force overwrite of output file")
|
||||
help = flag.Bool("h", false, "print this help message")
|
||||
keep = flag.Bool("k", false, "keep original files unchaned")
|
||||
suffix = flag.String("s", "lzma", "use provided suffix on compressed files")
|
||||
level = flag.Int("l", 5, "compression level [1 ... 9]")
|
||||
cores = flag.Int("cores", 1, "number of cores to use for parallelization")
|
||||
|
||||
stdin bool
|
||||
)
|
||||
|
||||
func usage() {
|
||||
fmt.Fprintf(os.Stderr, "Usage: %s [OPTION]... [FILE]\n", os.Args[0])
|
||||
fmt.Fprintf(os.Stderr, "Compress or uncompress FILE (by default, compress FILE in-place).\n\n")
|
||||
flag.PrintDefaults()
|
||||
fmt.Fprintf(os.Stderr, "\nWith no FILE, or when FILE is -, read standard input.\n")
|
||||
}
|
||||
|
||||
func exit(msg string) {
|
||||
usage()
|
||||
fmt.Fprintln(os.Stderr)
|
||||
log.Fatalf("%s: check args: %s\n\n", os.Args[0], msg)
|
||||
}
|
||||
|
||||
func setByUser(name string) (isSet bool) {
|
||||
flag.Visit(func(f *flag.Flag) {
|
||||
if f.Name == name {
|
||||
isSet = true
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
if *help == true {
|
||||
usage()
|
||||
log.Fatal(0)
|
||||
}
|
||||
//if *stdout == true && *suffix != "lzma" {
|
||||
if *stdout == true && setByUser("s") == true {
|
||||
exit("stdout set, suffix not used")
|
||||
}
|
||||
if *stdout == true && *force == true {
|
||||
exit("stdout set, force not used")
|
||||
}
|
||||
if *stdout == true && *keep == true {
|
||||
exit("stdout set, keep is redundant")
|
||||
}
|
||||
if flag.NArg() > 1 {
|
||||
exit("too many file, provide at most one file at a time or check order of flags")
|
||||
}
|
||||
//if *decompress == true && *level != 5 {
|
||||
if *decompress == true && setByUser("l") == true {
|
||||
exit("compression level is used for compression only")
|
||||
}
|
||||
if *level < 1 || *level > 9 {
|
||||
exit("compression level out of range")
|
||||
}
|
||||
if *cores < 1 || *cores > 32 {
|
||||
exit("invalid number of cores")
|
||||
}
|
||||
|
||||
runtime.GOMAXPROCS(*cores)
|
||||
|
||||
var inFilePath string
|
||||
var outFilePath string
|
||||
if flag.NArg() == 0 || flag.NArg() == 1 && flag.Args()[0] == "-" { // parse args: read from stdin
|
||||
if *stdout != true {
|
||||
exit("reading from stdin, can write only to stdout")
|
||||
}
|
||||
//if *suffix != "lzma" {
|
||||
if setByUser("s") == true {
|
||||
exit("reading from stdin, suffix not needed")
|
||||
}
|
||||
stdin = true
|
||||
|
||||
} else if flag.NArg() == 1 { // parse args: read from file
|
||||
inFilePath = flag.Args()[0]
|
||||
f, err := os.Lstat(inFilePath)
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
if f == nil {
|
||||
exit(fmt.Sprintf("file %s not found", inFilePath))
|
||||
}
|
||||
if !!f.IsDir() {
|
||||
exit(fmt.Sprintf("%s is not a regular file", inFilePath))
|
||||
}
|
||||
|
||||
if *stdout == false { // parse args: write to file
|
||||
if *suffix == "" {
|
||||
exit("suffix can't be an empty string")
|
||||
}
|
||||
|
||||
if *decompress == true {
|
||||
outFileDir, outFileName := path.Split(inFilePath)
|
||||
if strings.HasSuffix(outFileName, "."+*suffix) {
|
||||
if len(outFileName) > len("."+*suffix) {
|
||||
nstr := strings.SplitN(outFileName, ".", len(outFileName))
|
||||
estr := strings.Join(nstr[0:len(nstr)-1], ".")
|
||||
outFilePath = outFileDir + estr
|
||||
} else {
|
||||
log.Fatalf("error: can't strip suffix .%s from file %s", *suffix, inFilePath)
|
||||
}
|
||||
} else {
|
||||
exit(fmt.Sprintf("file %s doesn't have suffix .%s", inFilePath, *suffix))
|
||||
}
|
||||
|
||||
} else {
|
||||
outFilePath = inFilePath + "." + *suffix
|
||||
}
|
||||
|
||||
f, err = os.Lstat(outFilePath)
|
||||
if err != nil && f != nil { // should be: ||| if err != nil && err != "file not found" ||| but i can't find the error's id
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
if f != nil && !f.IsDir() {
|
||||
if *force == true {
|
||||
err = os.Remove(outFilePath)
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
} else {
|
||||
exit(fmt.Sprintf("outFile %s exists. use force to overwrite", outFilePath))
|
||||
}
|
||||
} else if f != nil {
|
||||
exit(fmt.Sprintf("outFile %s exists and is not a regular file", outFilePath))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pr, pw := io.Pipe()
|
||||
//defer pr.Close()
|
||||
//defer pw.Close()
|
||||
|
||||
if *decompress {
|
||||
// read from inFile into pw
|
||||
go func() {
|
||||
defer pw.Close()
|
||||
var inFile *os.File
|
||||
var err error
|
||||
if stdin == true {
|
||||
inFile = os.Stdin
|
||||
} else {
|
||||
inFile, err = os.Open(inFilePath)
|
||||
}
|
||||
defer inFile.Close()
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
|
||||
_, err = io.Copy(pw, inFile)
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
// write into outFile from z
|
||||
defer pr.Close()
|
||||
z := lzma.NewReader(pr)
|
||||
defer z.Close()
|
||||
var outFile *os.File
|
||||
var err error
|
||||
if *stdout == true {
|
||||
outFile = os.Stdout
|
||||
} else {
|
||||
outFile, err = os.Create(outFilePath)
|
||||
}
|
||||
defer outFile.Close()
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
|
||||
_, err = io.Copy(outFile, z)
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
|
||||
} else {
|
||||
// read from inFile into z
|
||||
go func() {
|
||||
defer pw.Close()
|
||||
var z io.WriteCloser
|
||||
var inFile *os.File
|
||||
var err error
|
||||
if stdin == true {
|
||||
inFile = os.Stdin
|
||||
defer inFile.Close()
|
||||
z = lzma.NewWriterLevel(pw, *level)
|
||||
defer z.Close()
|
||||
} else {
|
||||
inFile, err = os.Open(inFilePath)
|
||||
defer inFile.Close()
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
f, err := os.Lstat(inFilePath)
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
z = lzma.NewWriterSizeLevel(pw, int64(f.Size()), *level)
|
||||
defer z.Close()
|
||||
}
|
||||
|
||||
_, err = io.Copy(z, inFile)
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
// write into outFile from pr
|
||||
defer pr.Close()
|
||||
var outFile *os.File
|
||||
var err error
|
||||
if *stdout == true {
|
||||
outFile = os.Stdout
|
||||
} else {
|
||||
outFile, err = os.Create(outFilePath)
|
||||
}
|
||||
defer outFile.Close()
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
|
||||
_, err = io.Copy(outFile, pr)
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if *stdout == false && *keep == false {
|
||||
err := os.Remove(inFilePath)
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
// Copyright (c) 2010, Andrei Vieru. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package lzma
|
||||
|
||||
type lenCoder struct {
|
||||
choice []uint16
|
||||
lowCoder []*rangeBitTreeCoder
|
||||
midCoder []*rangeBitTreeCoder
|
||||
highCoder *rangeBitTreeCoder
|
||||
}
|
||||
|
||||
func newLenCoder(numPosStates /*1 << pb*/ uint32) *lenCoder {
|
||||
lc := &lenCoder{
|
||||
choice: initBitModels(2),
|
||||
lowCoder: make([]*rangeBitTreeCoder, kNumPosStatesMax),
|
||||
midCoder: make([]*rangeBitTreeCoder, kNumPosStatesMax),
|
||||
highCoder: newRangeBitTreeCoder(kNumHighLenBits),
|
||||
}
|
||||
for i := uint32(0); i < numPosStates; i++ {
|
||||
lc.lowCoder[i] = newRangeBitTreeCoder(kNumLowLenBits)
|
||||
lc.midCoder[i] = newRangeBitTreeCoder(kNumMidLenBits)
|
||||
}
|
||||
return lc
|
||||
}
|
||||
|
||||
func (lc *lenCoder) decode(rd *rangeDecoder, posState uint32) (res uint32) {
|
||||
i := rd.decodeBit(lc.choice, 0)
|
||||
if i == 0 {
|
||||
res = lc.lowCoder[posState].decode(rd)
|
||||
return
|
||||
}
|
||||
res = kNumLowLenSymbols
|
||||
j := rd.decodeBit(lc.choice, 1)
|
||||
if j == 0 {
|
||||
k := lc.midCoder[posState].decode(rd)
|
||||
res += k
|
||||
return
|
||||
} else {
|
||||
l := lc.highCoder.decode(rd)
|
||||
res = res + kNumMidLenSymbols + l
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (lc *lenCoder) encode(re *rangeEncoder, symbol, posState uint32) {
|
||||
if symbol < kNumLowLenSymbols {
|
||||
re.encode(lc.choice, 0, 0)
|
||||
lc.lowCoder[posState].encode(re, symbol)
|
||||
} else {
|
||||
symbol -= kNumLowLenSymbols
|
||||
re.encode(lc.choice, 0, 1)
|
||||
if symbol < kNumMidLenSymbols {
|
||||
re.encode(lc.choice, 1, 0)
|
||||
lc.midCoder[posState].encode(re, symbol)
|
||||
} else {
|
||||
re.encode(lc.choice, 1, 1)
|
||||
lc.highCoder.encode(re, symbol-kNumMidLenSymbols)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// write prices into prices []uint32
|
||||
func (lc *lenCoder) setPrices(prices []uint32, posState, numSymbols, st uint32) {
|
||||
a0 := getPrice0(lc.choice[0])
|
||||
a1 := getPrice1(lc.choice[0])
|
||||
b0 := a1 + getPrice0(lc.choice[1])
|
||||
b1 := a1 + getPrice1(lc.choice[1])
|
||||
|
||||
var i uint32
|
||||
for i = 0; i < kNumLowLenSymbols; i++ {
|
||||
if i >= numSymbols {
|
||||
return
|
||||
}
|
||||
prices[st+i] = a0 + lc.lowCoder[posState].getPrice(i)
|
||||
}
|
||||
for ; i < kNumLowLenSymbols+kNumMidLenSymbols; i++ {
|
||||
if i >= numSymbols {
|
||||
return
|
||||
}
|
||||
prices[st+i] = b0 + lc.midCoder[posState].getPrice(i-kNumLowLenSymbols)
|
||||
}
|
||||
for ; i < numSymbols; i++ {
|
||||
prices[st+i] = b1 + lc.highCoder.getPrice(i-kNumLowLenSymbols-kNumMidLenSymbols)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
type lenPriceTableCoder struct {
|
||||
lc *lenCoder
|
||||
prices []uint32
|
||||
counters []uint32
|
||||
tableSize uint32
|
||||
}
|
||||
|
||||
func newLenPriceTableCoder(tableSize, numPosStates uint32) *lenPriceTableCoder {
|
||||
pc := &lenPriceTableCoder{
|
||||
lc: newLenCoder(numPosStates),
|
||||
prices: make([]uint32, kNumLenSymbols<<kNumPosStatesBitsMax),
|
||||
counters: make([]uint32, kNumPosStatesMax),
|
||||
tableSize: tableSize,
|
||||
}
|
||||
for posState := uint32(0); posState < numPosStates; posState++ {
|
||||
pc.updateTable(posState)
|
||||
}
|
||||
return pc
|
||||
}
|
||||
|
||||
func (pc *lenPriceTableCoder) updateTable(posState uint32) {
|
||||
pc.lc.setPrices(pc.prices, posState, pc.tableSize, posState*kNumLenSymbols)
|
||||
pc.counters[posState] = pc.tableSize
|
||||
}
|
||||
|
||||
func (pc *lenPriceTableCoder) getPrice(symbol, posState uint32) uint32 {
|
||||
return pc.prices[posState*kNumLenSymbols+symbol]
|
||||
}
|
||||
|
||||
func (pc *lenPriceTableCoder) encode(re *rangeEncoder, symbol, posState uint32) {
|
||||
pc.lc.encode(re, symbol, posState)
|
||||
pc.counters[posState]--
|
||||
if pc.counters[posState] == 0 {
|
||||
pc.updateTable(posState)
|
||||
}
|
||||
}
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
// Copyright (c) 2010, Andrei Vieru. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package lzma
|
||||
|
||||
type litSubCoder struct {
|
||||
coders []uint16
|
||||
}
|
||||
|
||||
func newLitSubCoder() *litSubCoder {
|
||||
return &litSubCoder{
|
||||
coders: initBitModels(0x300),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func (lsc *litSubCoder) decodeNormal(rd *rangeDecoder) byte {
|
||||
symbol := uint32(1)
|
||||
for symbol < 0x100 {
|
||||
i := rd.decodeBit(lsc.coders, symbol)
|
||||
symbol = symbol<<1 | i
|
||||
}
|
||||
return byte(symbol)
|
||||
}
|
||||
|
||||
func (lsc *litSubCoder) decodeWithMatchByte(rd *rangeDecoder, matchByte byte) byte {
|
||||
uMatchByte := uint32(matchByte)
|
||||
symbol := uint32(1)
|
||||
for symbol < 0x100 {
|
||||
matchBit := (uMatchByte >> 7) & 1
|
||||
uMatchByte <<= 1
|
||||
bit := rd.decodeBit(lsc.coders, ((1+matchBit)<<8)+symbol)
|
||||
symbol = (symbol << 1) | bit
|
||||
if matchBit != bit {
|
||||
for symbol < 0x100 {
|
||||
i := rd.decodeBit(lsc.coders, symbol)
|
||||
symbol = (symbol << 1) | i
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return byte(symbol)
|
||||
}
|
||||
|
||||
|
||||
func (lsc *litSubCoder) encode(re *rangeEncoder, symbol byte) {
|
||||
uSymbol := uint32(symbol)
|
||||
context := uint32(1)
|
||||
for i := uint32(7); int32(i) >= 0; i-- {
|
||||
bit := (uSymbol >> i) & 1
|
||||
re.encode(lsc.coders, context, bit)
|
||||
context = context<<1 | bit
|
||||
}
|
||||
}
|
||||
|
||||
func (lsc *litSubCoder) encodeMatched(re *rangeEncoder, matchByte, symbol byte) {
|
||||
uMatchByte := uint32(matchByte)
|
||||
uSymbol := uint32(symbol)
|
||||
context := uint32(1)
|
||||
same := true
|
||||
for i := uint32(7); int32(i) >= 0; i-- {
|
||||
bit := (uSymbol >> i) & 1
|
||||
state := context
|
||||
if same == true {
|
||||
matchBit := (uMatchByte >> i) & 1
|
||||
state += (1 + matchBit) << 8
|
||||
same = false
|
||||
if matchBit == bit {
|
||||
same = true
|
||||
}
|
||||
}
|
||||
re.encode(lsc.coders, state, bit)
|
||||
context = context<<1 | bit
|
||||
}
|
||||
}
|
||||
|
||||
func (lsc *litSubCoder) getPrice(matchMode bool, matchByte, symbol byte) uint32 {
|
||||
uMatchByte := uint32(matchByte)
|
||||
uSymbol := uint32(symbol)
|
||||
price := uint32(0)
|
||||
context := uint32(1)
|
||||
i := uint32(7)
|
||||
if matchMode == true {
|
||||
for ; int32(i) >= 0; i-- {
|
||||
matchBit := (uMatchByte >> i) & 1
|
||||
bit := (uSymbol >> i) & 1
|
||||
price += getPrice(lsc.coders[(1+matchBit)<<8+context], bit)
|
||||
context = context<<1 | bit
|
||||
if matchBit != bit {
|
||||
i--
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
for ; int32(i) >= 0; i-- {
|
||||
bit := (uSymbol >> i) & 1
|
||||
price += getPrice(lsc.coders[context], bit)
|
||||
context = context<<1 | bit
|
||||
}
|
||||
return price
|
||||
}
|
||||
|
||||
|
||||
type litCoder struct {
|
||||
coders []*litSubCoder
|
||||
numPrevBits uint32 // literal context bits // lc
|
||||
// numPosBits uint32 // literal position state bits // lp
|
||||
posMask uint32
|
||||
}
|
||||
|
||||
func newLitCoder(numPosBits, numPrevBits uint32) *litCoder {
|
||||
numStates := uint32(1) << (numPrevBits + numPosBits)
|
||||
lc := &litCoder{
|
||||
coders: make([]*litSubCoder, numStates),
|
||||
numPrevBits: numPrevBits,
|
||||
// numPosBits: numPosBits,
|
||||
posMask: (1 << numPosBits) - 1,
|
||||
}
|
||||
for i := uint32(0); i < numStates; i++ {
|
||||
lc.coders[i] = newLitSubCoder()
|
||||
}
|
||||
return lc
|
||||
}
|
||||
|
||||
func (lc *litCoder) getSubCoder(pos uint32, prevByte byte) *litSubCoder {
|
||||
return lc.coders[((pos&lc.posMask)<<lc.numPrevBits)+uint32(prevByte>>(8-lc.numPrevBits))]
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
// Copyright (c) 2010, Andrei Vieru. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package lzma
|
||||
|
||||
type rangeBitTreeCoder struct {
|
||||
models []uint16 // length(models) is at most 1<<8
|
||||
numBitLevels uint32 // min 2; max 8
|
||||
}
|
||||
|
||||
func newRangeBitTreeCoder(numBitLevels uint32) *rangeBitTreeCoder {
|
||||
return &rangeBitTreeCoder{
|
||||
numBitLevels: numBitLevels,
|
||||
models: initBitModels(1 << numBitLevels),
|
||||
}
|
||||
}
|
||||
|
||||
func (rc *rangeBitTreeCoder) decode(rd *rangeDecoder) (res uint32) {
|
||||
res = 1
|
||||
for bitIndex := rc.numBitLevels; bitIndex != 0; bitIndex-- {
|
||||
bit := rd.decodeBit(rc.models, res)
|
||||
res = res<<1 + bit
|
||||
}
|
||||
res -= 1 << rc.numBitLevels
|
||||
return
|
||||
}
|
||||
|
||||
func (rc *rangeBitTreeCoder) reverseDecode(rd *rangeDecoder) (res uint32) {
|
||||
index := uint32(1)
|
||||
res = 0
|
||||
for bitIndex := uint32(0); bitIndex < rc.numBitLevels; bitIndex++ {
|
||||
bit := rd.decodeBit(rc.models, index)
|
||||
index <<= 1
|
||||
index += bit
|
||||
res |= bit << bitIndex
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func reverseDecodeIndex(rd *rangeDecoder, models []uint16, startIndex, numBitModels uint32) (res uint32) {
|
||||
index := uint32(1)
|
||||
res = 0
|
||||
for bitIndex := uint32(0); bitIndex < numBitModels; bitIndex++ {
|
||||
bit := rd.decodeBit(models, startIndex+index)
|
||||
index <<= 1
|
||||
index += bit
|
||||
res |= bit << bitIndex
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (rc *rangeBitTreeCoder) encode(re *rangeEncoder, symbol uint32) {
|
||||
m := uint32(1)
|
||||
for bitIndex := rc.numBitLevels; bitIndex != 0; {
|
||||
bitIndex--
|
||||
bit := (symbol >> bitIndex) & 1
|
||||
re.encode(rc.models, m, bit)
|
||||
m = m<<1 | bit
|
||||
}
|
||||
}
|
||||
|
||||
func (rc *rangeBitTreeCoder) reverseEncode(re *rangeEncoder, symbol uint32) {
|
||||
m := uint32(1)
|
||||
for i := uint32(0); i < rc.numBitLevels; i++ {
|
||||
bit := symbol & 1
|
||||
re.encode(rc.models, m, bit)
|
||||
m = m<<1 | bit
|
||||
symbol >>= 1
|
||||
}
|
||||
}
|
||||
|
||||
func (rc *rangeBitTreeCoder) getPrice(symbol uint32) (res uint32) {
|
||||
res = 0
|
||||
m := uint32(1)
|
||||
for bitIndex := rc.numBitLevels; bitIndex != 0; {
|
||||
bitIndex--
|
||||
bit := (symbol >> bitIndex) & 1
|
||||
res += getPrice(rc.models[m], bit)
|
||||
m = m<<1 + bit
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (rc *rangeBitTreeCoder) reverseGetPrice(symbol uint32) (res uint32) {
|
||||
res = 0
|
||||
m := uint32(1)
|
||||
for i := rc.numBitLevels; i != 0; i-- {
|
||||
bit := symbol & 1
|
||||
symbol >>= 1
|
||||
res += getPrice(rc.models[m], bit)
|
||||
m = m<<1 | bit
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func reverseGetPriceIndex(models []uint16, startIndex, numBitLevels, symbol uint32) (res uint32) {
|
||||
res = 0
|
||||
m := uint32(1)
|
||||
for i := numBitLevels; i != 0; i-- {
|
||||
bit := symbol & 1
|
||||
symbol >>= 1
|
||||
res += getPrice(models[startIndex+m], bit)
|
||||
m = m<<1 | bit
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func reverseEncodeIndex(re *rangeEncoder, models []uint16, startIndex, numBitLevels, symbol uint32) {
|
||||
m := uint32(1)
|
||||
for i := uint32(0); i < numBitLevels; i++ {
|
||||
bit := symbol & 1
|
||||
re.encode(models, startIndex+m, bit)
|
||||
m = m<<1 | bit
|
||||
symbol >>= 1
|
||||
}
|
||||
}
|
||||
+249
@@ -0,0 +1,249 @@
|
||||
// Copyright (c) 2010, Andrei Vieru. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package lzma
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
kTopValue = 1 << 24
|
||||
kNumBitModelTotalBits = 11
|
||||
kBitModelTotal = 1 << kNumBitModelTotalBits
|
||||
kNumMoveBits = 5
|
||||
)
|
||||
|
||||
// The actual read interface needed by NewDecoder. If the passed in io.Reader
|
||||
// does not also have ReadByte, the NewDecoder will introduce its own buffering.
|
||||
//
|
||||
type Reader interface {
|
||||
io.Reader
|
||||
ReadByte() (c byte, err error)
|
||||
}
|
||||
|
||||
type rangeDecoder struct {
|
||||
r Reader
|
||||
rrange uint32
|
||||
code uint32
|
||||
}
|
||||
|
||||
func makeReader(r io.Reader) Reader {
|
||||
if rr, ok := r.(Reader); ok {
|
||||
return rr
|
||||
}
|
||||
return bufio.NewReader(r)
|
||||
}
|
||||
|
||||
func newRangeDecoder(r io.Reader) *rangeDecoder {
|
||||
rd := &rangeDecoder{
|
||||
r: makeReader(r),
|
||||
rrange: 0xFFFFFFFF,
|
||||
code: 0,
|
||||
}
|
||||
buf := make([]byte, 5)
|
||||
n, err := rd.r.Read(buf)
|
||||
if err != nil {
|
||||
throw(err)
|
||||
}
|
||||
if n != len(buf) {
|
||||
throw(nReadError)
|
||||
}
|
||||
for i := 0; i < len(buf); i++ {
|
||||
rd.code = rd.code<<8 | uint32(buf[i])
|
||||
}
|
||||
return rd
|
||||
}
|
||||
|
||||
func (rd *rangeDecoder) decodeDirectBits(numTotalBits uint32) (res uint32) {
|
||||
for i := numTotalBits; i != 0; i-- {
|
||||
rd.rrange >>= 1
|
||||
t := (rd.code - rd.rrange) >> 31
|
||||
rd.code -= rd.rrange & (t - 1)
|
||||
res = res<<1 | (1 - t)
|
||||
if rd.rrange < kTopValue {
|
||||
c, err := rd.r.ReadByte()
|
||||
if err != nil {
|
||||
throw(err)
|
||||
}
|
||||
rd.code = rd.code<<8 | uint32(c)
|
||||
rd.rrange <<= 8
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (rd *rangeDecoder) decodeBit(probs []uint16, index uint32) (res uint32) {
|
||||
prob := probs[index]
|
||||
newBound := (rd.rrange >> kNumBitModelTotalBits) * uint32(prob)
|
||||
if rd.code < newBound {
|
||||
rd.rrange = newBound
|
||||
probs[index] = prob + (kBitModelTotal-prob)>>kNumMoveBits
|
||||
if rd.rrange < kTopValue {
|
||||
b, err := rd.r.ReadByte()
|
||||
if err != nil {
|
||||
throw(err)
|
||||
}
|
||||
rd.code = rd.code<<8 | uint32(b)
|
||||
rd.rrange <<= 8
|
||||
}
|
||||
res = 0
|
||||
} else {
|
||||
rd.rrange -= newBound
|
||||
rd.code -= newBound
|
||||
probs[index] = prob - prob>>kNumMoveBits
|
||||
if rd.rrange < kTopValue {
|
||||
b, err := rd.r.ReadByte()
|
||||
if err != nil {
|
||||
throw(err)
|
||||
}
|
||||
rd.code = rd.code<<8 | uint32(b)
|
||||
rd.rrange <<= 8
|
||||
}
|
||||
res = 1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func initBitModels(length uint32) (probs []uint16) {
|
||||
probs = make([]uint16, length)
|
||||
val := uint16(kBitModelTotal) >> 1
|
||||
for i := uint32(0); i < length; i++ {
|
||||
probs[i] = val // 1 << 10
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const (
|
||||
kNumMoveReducingBits = 2
|
||||
kNumBitPriceShiftBits = 6
|
||||
)
|
||||
|
||||
// The actual write interface needed by NewEncoder. If the passed in io.Writer
|
||||
// does not also have WriteByte and Flush, the NewEncoder will wrap it into an
|
||||
// bufio.Writer.
|
||||
//
|
||||
type Writer interface {
|
||||
io.Writer
|
||||
Flush() error
|
||||
WriteByte(c byte) error
|
||||
}
|
||||
|
||||
type rangeEncoder struct {
|
||||
w Writer
|
||||
low uint64
|
||||
pos uint64
|
||||
cacheSize uint32
|
||||
cache uint32
|
||||
rrange uint32
|
||||
}
|
||||
|
||||
func makeWriter(w io.Writer) Writer {
|
||||
if ww, ok := w.(Writer); ok {
|
||||
return ww
|
||||
}
|
||||
return bufio.NewWriter(w)
|
||||
}
|
||||
|
||||
func newRangeEncoder(w io.Writer) *rangeEncoder {
|
||||
return &rangeEncoder{
|
||||
w: makeWriter(w),
|
||||
low: 0,
|
||||
pos: 0,
|
||||
cacheSize: 1,
|
||||
cache: 0,
|
||||
rrange: 0xFFFFFFFF,
|
||||
}
|
||||
}
|
||||
|
||||
func (re *rangeEncoder) flush() {
|
||||
for i := 0; i < 5; i++ {
|
||||
re.shiftLow()
|
||||
}
|
||||
err := re.w.Flush()
|
||||
if err != nil {
|
||||
throw(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (re *rangeEncoder) shiftLow() {
|
||||
lowHi := uint32(re.low >> 32)
|
||||
if lowHi != 0 || re.low < uint64(0x00000000FF000000) {
|
||||
re.pos += uint64(re.cacheSize)
|
||||
temp := re.cache
|
||||
dwtemp := uint32(1) // execute the loop at least once (do-while)
|
||||
for ; dwtemp != 0; dwtemp = re.cacheSize {
|
||||
err := re.w.WriteByte(byte(temp + lowHi))
|
||||
if err != nil {
|
||||
throw(err)
|
||||
}
|
||||
temp = 0x000000FF
|
||||
re.cacheSize--
|
||||
}
|
||||
re.cache = uint32(re.low) >> 24
|
||||
}
|
||||
re.cacheSize++
|
||||
re.low = uint64(uint32(re.low) << 8)
|
||||
}
|
||||
|
||||
func (re *rangeEncoder) encodeDirectBits(v, numTotalBits uint32) {
|
||||
for i := numTotalBits - 1; int32(i) >= 0; i-- {
|
||||
re.rrange >>= 1
|
||||
if (v>>i)&1 == 1 {
|
||||
re.low += uint64(re.rrange)
|
||||
}
|
||||
if re.rrange < kTopValue {
|
||||
re.rrange <<= 8
|
||||
re.shiftLow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (re *rangeEncoder) processedSize() uint64 {
|
||||
return uint64(re.cacheSize) + re.pos + 4
|
||||
}
|
||||
|
||||
func (re *rangeEncoder) encode(probs []uint16, index, symbol uint32) {
|
||||
prob := probs[index]
|
||||
newBound := (re.rrange >> kNumBitModelTotalBits) * uint32(prob)
|
||||
if symbol == 0 {
|
||||
re.rrange = newBound
|
||||
probs[index] = prob + (kBitModelTotal-prob)>>kNumMoveBits
|
||||
} else {
|
||||
re.low += uint64(newBound) & uint64(0xFFFFFFFFFFFFFFFF)
|
||||
re.rrange -= newBound
|
||||
probs[index] = prob - prob>>kNumMoveBits
|
||||
}
|
||||
if re.rrange < kTopValue {
|
||||
re.rrange <<= 8
|
||||
re.shiftLow()
|
||||
}
|
||||
}
|
||||
|
||||
var probPrices []uint32 = make([]uint32, kBitModelTotal>>kNumMoveReducingBits) // len(probPrices) = 512
|
||||
|
||||
// should be called in the encoder's contructor.
|
||||
func initProbPrices() {
|
||||
kNumBits := uint32(kNumBitModelTotalBits - kNumMoveReducingBits)
|
||||
for i := kNumBits - 1; int32(i) >= 0; i-- {
|
||||
start := uint32(1) << (kNumBits - i - 1)
|
||||
end := uint32(1) << (kNumBits - i)
|
||||
for j := start; j < end; j++ {
|
||||
probPrices[j] = i<<kNumBitPriceShiftBits + ((end-j)<<kNumBitPriceShiftBits)>>(kNumBits-i-1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getPrice(prob uint16, symbol uint32) uint32 {
|
||||
return probPrices[(((uint32(prob)-symbol)^(-symbol))&(uint32(kBitModelTotal)-1))>>kNumMoveReducingBits]
|
||||
}
|
||||
|
||||
func getPrice0(prob uint16) uint32 {
|
||||
return probPrices[prob>>kNumMoveReducingBits]
|
||||
}
|
||||
|
||||
func getPrice1(prob uint16) uint32 {
|
||||
return probPrices[(kBitModelTotal-prob)>>kNumMoveReducingBits]
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) 2010, Andrei Vieru. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package lzma
|
||||
|
||||
func minInt32(left int32, right int32) int32 {
|
||||
if left < right {
|
||||
return left
|
||||
}
|
||||
return right
|
||||
}
|
||||
|
||||
func minUInt32(left uint32, right uint32) uint32 {
|
||||
if left < right {
|
||||
return left
|
||||
}
|
||||
return right
|
||||
}
|
||||
|
||||
func maxInt32(left int32, right int32) int32 {
|
||||
if left > right {
|
||||
return left
|
||||
}
|
||||
return right
|
||||
}
|
||||
|
||||
func maxUInt32(left uint32, right uint32) uint32 {
|
||||
if left > right {
|
||||
return left
|
||||
}
|
||||
return right
|
||||
}
|
||||
Reference in new issue
Block a user