Imported Upstream version 1.0.1

This commit is contained in:
Sébastien Delafond committed 2017-07-18 11:45:29 +02:00
1 parent 1ee35b841d
commit 4f12259bc0
5531 files changed
+1834599 -742844

No files matched your search

+11
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,86 @@
go-aws-auth
===========
[![GoDoc](https://godoc.org/github.com/smartystreets/go-aws-auth?status.svg)](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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1 @@
Paul Borman <borman@google.com>
+1
View File
@@ -0,0 +1 @@
defaultcc: borman@google.com
File diff suppressed because it is too large. Load diff
+27
View File
@@ -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.
Generated Vendored Executable
+84
View File
@@ -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))
}
Generated Vendored Executable
+8
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
}
+24
View File
@@ -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
View File
@@ -0,0 +1,6 @@
language: go
go:
- 1.3.3
- 1.4
- tip
+22
View File
@@ -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
View File
@@ -0,0 +1,17 @@
# go-xz
[![GoDoc](https://godoc.org/github.com/smira/go-xz?status.svg)](https://godoc.org/github.com/smira/go-xz)
[![Build Status](https://travis-ci.org/smira/go-xz.svg?branch=master)](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
View File
File diff suppressed because it is too large. Load diff
BIN
View File
Binary file not shown.
+71
View File
@@ -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
View File
@@ -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
View File
@@ -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.
Binary file not shown.
+3982
View File
File diff suppressed because it is too large. Load diff
+355
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large. Load diff
+157
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
}