<commit_before>package pointer

import (
	"github.com/bmizerany/assert"
	"github.com/github/git-media/gitmedia"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"testing"
)

func TestWriteConsistentFile(t *testing.T) {
	path, close := SetupConsistentWriter()
	defer close()

	filename := filepath.Join(path, "valid")
	file, err := newFile(filename, "e9058ab198f6908f702111b0c0fb5b36f99d00554521886c40e2891b349dc7a1")
	if err != nil {
		t.Errorf("file error: %s", err.Error())
	}
	assert.Equal(t, nil, err)

	n, err := file.Write([]byte("yo"))
	assert.Equal(t, nil, err)
	assert.Equal(t, 2, n)

	err = file.Close()
	assert.Equal(t, nil, err)

	by, err := ioutil.ReadFile(filename)
	assert.Equal(t, nil, err)
	assert.Equal(t, "yo", string(by))
}

func TestAttemptWriteToExistingFile(t *testing.T) {
	path, close := SetupConsistentWriter()
	defer close()

	filename := filepath.Join(path, "existing")
	err := ioutil.WriteFile(filename, []byte("yo"), 0777)
	assert.Equal(t, nil, err)

	_, err = newFile(filename, "sha")
	if err == nil {
		t.Fatalf("Expected error!")
	}

	if !strings.Contains(err.Error(), "File exists") {
		t.Fatalf("No problem trying to write to %s", filename)
	}
}

func TestAttemptWriteWithInvalidSHA(t *testing.T) {
	path, close := SetupConsistentWriter()
	defer close()

	filename := filepath.Join(path, "invalid-sha")
	file, err := newFile(filename, "sha")
	assert.Equal(t, nil, err)

	n, err := file.Write([]byte("yo"))
	assert.Equal(t, nil, err)
	assert.Equal(t, 2, n)

	err = file.Close()
	if !strings.Contains(err.Error(), "Unexpected SHA-256") {
		t.Fatalf("No problem trying to write to %s", filename)
	}

	stat, err := os.Stat(filename)
	if err == nil {
		t.Fatalf(".git media file should not exist: %s", filename)
	}
	assert.Equal(t, nil, stat)
}

func SetupConsistentWriter() (string, func()) {
	wd, err := os.Getwd()
	if err != nil {
		panic(err)
	}

	path := filepath.Join(wd, "test")
	gitmedia.TempDir = filepath.Join(path, "tmp")
	err = os.MkdirAll(path, 0777)
	if err != nil {
		panic(err)
	}

	return path, func() { os.RemoveAll(path); gitmedia.ResetTempDir() }
}
<commit_msg>ンンン ンンン ンン<commit_after>package pointer

import (
	"github.com/bmizerany/assert"
	"github.com/github/git-media/gitmedia"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"testing"
)

func TestWriteConsistentFile(t *testing.T) {
	path, close := SetupConsistentWriter()
	defer close()

	filename := filepath.Join(path, "valid")
	file, err := newFile(filename, "e9058ab198f6908f702111b0c0fb5b36f99d00554521886c40e2891b349dc7a1")
	if err != nil {
		t.Errorf("file error: %s", err.Error())
	}
	assert.Equal(t, nil, err)

	n, err := file.Write([]byte("yo"))
	assert.Equal(t, nil, err)
	assert.Equal(t, 2, n)

	err = file.Close()
	assert.Equal(t, nil, err)

	by, err := ioutil.ReadFile(filename)
	assert.Equal(t, nil, err)
	assert.Equal(t, "yo", string(by))
}

func TestAttemptWriteToExistingFile(t *testing.T) {
	path, close := SetupConsistentWriter()
	defer close()

	filename := filepath.Join(path, "existing")
	err := ioutil.WriteFile(filename, []byte("yo"), 0777)
	assert.Equal(t, nil, err)

	_, err = newFile(filename, "sha")
	if err == nil {
		t.Fatalf("Expected error!")
	}

	if !strings.Contains(err.Error(), "File exists") {
		t.Fatalf("No problem trying to write to %s", filename)
	}
}

func TestAttemptWriteWithInvalidSHA(t *testing.T) {
	path, close := SetupConsistentWriter()
	defer close()

	filename := filepath.Join(path, "invalid-sha")
	file, err := newFile(filename, "sha")
	assert.Equal(t, nil, err)

	n, err := file.Write([]byte("yo"))
	assert.Equal(t, nil, err)
	assert.Equal(t, 2, n)

	err = file.Close()
	if !strings.Contains(err.Error(), "Unexpected SHA-256") {
		t.Fatalf("No problem trying to write to %s", filename)
	}

	stat, err := os.Stat(filename)
	if err == nil {
		t.Fatalf(".git media file should not exist: %s", filename)
	}
	assert.Equal(t, nil, stat)
}

func SetupConsistentWriter() (string, func()) {
	wd, err := os.Getwd()
	if err != nil {
		panic(err)
	}

	path := filepath.Join(wd, "test")
	oldTempDir := gitmedia.TempDir
	gitmedia.TempDir = filepath.Join(path, "tmp")
	err = os.MkdirAll(path, 0777)
	if err != nil {
		panic(err)
	}

	return path, func() {
		os.RemoveAll(path)
		gitmedia.ResetTempDir()
		gitmedia.TempDir = oldTempDir
	}
}
<|endoftext|>


<commit_before>package commands

import (
	"fmt"
	"github.com/bmizerany/assert"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"
)

var (
	Root         string
	Bin          string
	TempDir      string
	GitEnv       []string
	JoinedGitEnv string
	configKeys   = []string{"Endpoint", "LocalWorkingDir", "LocalGitDir", "LocalMediaDir", "TempDir"}
)

func NewRepository(t *testing.T, name string) *Repository {
	path := filepath.Join(TempDir, name)
	r := &Repository{
		T:        t,
		Name:     name,
		Path:     path,
		Paths:    []string{path},
		Commands: make([]*TestCommand, 0),
	}
	r.clone()
	r.Path = expand(path)
	return r
}

func AssertIncludeString(t *testing.T, expected string, actual []string) {
	found := false
	for _, line := range actual {
		if line == expected {
			found = true
		}
	}
	assert.Tf(t, found, "%s not included.", expected)
}

func GlobalGitConfig(t *testing.T) []string {
	o := cmd(t, "git", "config", "-l", "--global")
	return strings.Split(o, "\n")
}

func SetConfigOutput(c *TestCommand, keys map[string]string) {
	pieces := make([]string, 0, len(keys))

	for _, key := range configKeys {
		if v, ok := keys[key]; ok {
			pieces = append(pieces, key+"="+v)
		}
	}

	c.Output = strings.Join(pieces, "\n")

	if len(JoinedGitEnv) > 0 {
		c.Output += "\n" + JoinedGitEnv
	}
}

type Repository struct {
	T                *testing.T
	Name             string
	Path             string
	Paths            []string
	Commands         []*TestCommand
	expandedTempPath bool
}

func (r *Repository) AddPath(paths ...string) {
	r.Paths = append(r.Paths, filepath.Join(paths...))
}

func (r *Repository) Command(args ...string) *TestCommand {
	cmd := &TestCommand{
		T:               r.T,
		Args:            args,
		BeforeCallbacks: make([]func(), 0),
		AfterCallbacks:  make([]func(), 0),
		Env:             make([]string, 0),
	}
	r.Commands = append(r.Commands, cmd)
	return cmd
}

func (r *Repository) ReadFile(paths ...string) string {
	args := make([]string, 1, len(paths)+1)
	args[0] = r.Path
	args = append(args, paths...)
	by, err := ioutil.ReadFile(filepath.Join(args...))
	assert.Equal(r.T, nil, err)
	return string(by)
}

func (r *Repository) WriteFile(filename, output string) {
	r.e(ioutil.WriteFile(filename, []byte(output), 0755))
}

func (r *Repository) MediaCmd(args ...string) string {
	return r.cmd(Bin, args...)
}

func (r *Repository) Test() {
	for _, path := range r.Paths {
		r.test(path)
	}
}

func (r *Repository) test(path string) {
	fmt.Println("Command tests for\n", path)
	for _, cmd := range r.Commands {
		r.clone()
		cmd.Run(path)
	}
}

func (r *Repository) clone() {
	clone(r.T, r.Name, r.Path)
}

func (r *Repository) e(err error) {
	e(r.T, err)
}

func (r *Repository) cmd(name string, args ...string) string {
	return cmd(r.T, name, args...)
}

type TestCommand struct {
	T               *testing.T
	Args            []string
	Env             []string
	Input           io.Reader
	Output          string
	BeforeCallbacks []func()
	AfterCallbacks  []func()
}

func (c *TestCommand) Run(path string) {
	fmt.Println("$ git media", strings.Join(c.Args, " "))

	for _, cb := range c.BeforeCallbacks {
		cb()
	}

	c.e(os.Chdir(path))

	cmd := exec.Command(Bin, c.Args...)
	cmd.Stdin = c.Input
	if c.Env != nil && len(c.Env) > 0 {
		cmd.Env = c.Env
	}
	outputBytes, err := cmd.CombinedOutput()
	c.e(err)

	if len(c.Output) > 0 {
		assert.Equal(c.T, c.Output+"\n", string(outputBytes))
	}

	for _, cb := range c.AfterCallbacks {
		cb()
	}
}

func (c *TestCommand) Before(f func()) {
	c.BeforeCallbacks = append(c.BeforeCallbacks, f)
}

func (c *TestCommand) After(f func()) {
	c.AfterCallbacks = append(c.AfterCallbacks, f)
}

func (c *TestCommand) e(err error) {
	e(c.T, err)
}

func cmd(t *testing.T, name string, args ...string) string {
	cmd := exec.Command(name, args...)
	o, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf(
			"Error running command:\n$ %s\n\n%s",
			strings.Join(cmd.Args, " "),
			string(o),
		)
	}
	return string(o)
}

func e(t *testing.T, err error) {
	if err != nil {
		t.Fatal(err.Error())
	}
}

func expand(path string) string {
	p, err := filepath.EvalSymlinks(path)
	if err != nil {
		panic(err)
	}
	return p
}

func clone(t *testing.T, name, path string) {
	e(t, os.RemoveAll(path))

	reposPath := filepath.Join(Root, "commands", "repos")
	e(t, os.Chdir(reposPath))
	cmd(t, "git", "clone", name, path)
	e(t, os.Chdir(path))
	cmd(t, "git", "remote", "remove", "origin")
	cmd(t, "git", "remote", "add", "origin", "https://example.com/git/media")
}

func init() {
	wd, err := os.Getwd()
	if err != nil {
		panic(err)
	}

	Root = filepath.Join(wd, "..")
	Bin = filepath.Join(Root, "bin", "git-media")
	TempDir = filepath.Join(os.TempDir(), "git-media-tests")

	env := os.Environ()
	GitEnv = make([]string, 0, len(env))
	for _, e := range env {
		if !strings.Contains(e, "GIT_") {
			continue
		}
		GitEnv = append(GitEnv, e)
	}
	JoinedGitEnv = strings.Join(GitEnv, "\n")
}
<commit_msg>ラララララ ラー ララララー<commit_after>package commands

import (
	"fmt"
	"github.com/bmizerany/assert"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"
)

var (
	Root         string
	Bin          string
	TempDir      string
	GitEnv       []string
	JoinedGitEnv string
	configKeys   = []string{"Endpoint", "LocalWorkingDir", "LocalGitDir", "LocalMediaDir", "TempDir"}
)

func NewRepository(t *testing.T, name string) *Repository {
	path := filepath.Join(TempDir, name)
	r := &Repository{
		T:        t,
		Name:     name,
		Path:     path,
		Paths:    []string{path},
		Commands: make([]*TestCommand, 0),
	}
	r.clone()
	r.Path = expand(path)
	return r
}

func AssertIncludeString(t *testing.T, expected string, actual []string) {
	found := false
	for _, line := range actual {
		if line == expected {
			found = true
		}
	}
	assert.Tf(t, found, "%s not included.", expected)
}

func GlobalGitConfig(t *testing.T) []string {
	o := cmd(t, "git", "config", "-l", "--global")
	return strings.Split(o, "\n")
}

func SetConfigOutput(c *TestCommand, keys map[string]string) {
	pieces := make([]string, 0, len(keys))

	for _, key := range configKeys {
		if v, ok := keys[key]; ok {
			pieces = append(pieces, key+"="+v)
		}
	}

	c.Output = strings.Join(pieces, "\n")

	if len(JoinedGitEnv) > 0 {
		c.Output += "\n" + JoinedGitEnv
	}
}

type Repository struct {
	T                *testing.T
	Name             string
	Path             string
	Paths            []string
	Commands         []*TestCommand
	expandedTempPath bool
}

func (r *Repository) AddPath(paths ...string) {
	r.Paths = append(r.Paths, filepath.Join(paths...))
}

func (r *Repository) Command(args ...string) *TestCommand {
	cmd := &TestCommand{
		T:               r.T,
		Args:            args,
		BeforeCallbacks: make([]func(), 0),
		AfterCallbacks:  make([]func(), 0),
		Env:             make([]string, 0),
	}
	r.Commands = append(r.Commands, cmd)
	return cmd
}

func (r *Repository) ReadFile(paths ...string) string {
	args := make([]string, 1, len(paths)+1)
	args[0] = r.Path
	args = append(args, paths...)
	by, err := ioutil.ReadFile(filepath.Join(args...))
	assert.Equal(r.T, nil, err)
	return string(by)
}

func (r *Repository) WriteFile(filename, output string) {
	r.e(ioutil.WriteFile(filename, []byte(output), 0755))
}

func (r *Repository) MediaCmd(args ...string) string {
	return r.cmd(Bin, args...)
}

func (r *Repository) Test() {
	for _, path := range r.Paths {
		r.test(path)
	}
}

func (r *Repository) test(path string) {
	fmt.Println("Command tests for\n", path)
	for _, cmd := range r.Commands {
		r.clone()
		cmd.Run(path)
	}
}

func (r *Repository) clone() {
	clone(r.T, r.Name, r.Path)
}

func (r *Repository) e(err error) {
	e(r.T, err)
}

func (r *Repository) cmd(name string, args ...string) string {
	return cmd(r.T, name, args...)
}

type TestCommand struct {
	T               *testing.T
	Args            []string
	Env             []string
	Input           io.Reader
	Output          string
	BeforeCallbacks []func()
	AfterCallbacks  []func()
}

func (c *TestCommand) Run(path string) {
	fmt.Println("$ git media", strings.Join(c.Args, " "))

	for _, cb := range c.BeforeCallbacks {
		cb()
	}

	c.e(os.Chdir(path))

	cmd := exec.Command(Bin, c.Args...)
	cmd.Stdin = c.Input
	if c.Env != nil && len(c.Env) > 0 {
		cmd.Env = append(os.Environ(), c.Env...)
	}
	outputBytes, err := cmd.CombinedOutput()
	c.e(err)

	if len(c.Output) > 0 {
		assert.Equal(c.T, c.Output+"\n", string(outputBytes))
	}

	for _, cb := range c.AfterCallbacks {
		cb()
	}
}

func (c *TestCommand) Before(f func()) {
	c.BeforeCallbacks = append(c.BeforeCallbacks, f)
}

func (c *TestCommand) After(f func()) {
	c.AfterCallbacks = append(c.AfterCallbacks, f)
}

func (c *TestCommand) e(err error) {
	e(c.T, err)
}

func cmd(t *testing.T, name string, args ...string) string {
	cmd := exec.Command(name, args...)
	o, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf(
			"Error running command:\n$ %s\n\n%s",
			strings.Join(cmd.Args, " "),
			string(o),
		)
	}
	return string(o)
}

func e(t *testing.T, err error) {
	if err != nil {
		t.Fatal(err.Error())
	}
}

func expand(path string) string {
	p, err := filepath.EvalSymlinks(path)
	if err != nil {
		panic(err)
	}
	return p
}

func clone(t *testing.T, name, path string) {
	e(t, os.RemoveAll(path))

	reposPath := filepath.Join(Root, "commands", "repos")
	e(t, os.Chdir(reposPath))
	cmd(t, "git", "clone", name, path)
	e(t, os.Chdir(path))
	cmd(t, "git", "remote", "remove", "origin")
	cmd(t, "git", "remote", "add", "origin", "https://example.com/git/media")
}

func init() {
	wd, err := os.Getwd()
	if err != nil {
		panic(err)
	}

	Root = filepath.Join(wd, "..")
	Bin = filepath.Join(Root, "bin", "git-media")
	TempDir = filepath.Join(os.TempDir(), "git-media-tests")

	env := os.Environ()
	GitEnv = make([]string, 0, len(env))
	for _, e := range env {
		if !strings.Contains(e, "GIT_") {
			continue
		}
		GitEnv = append(GitEnv, e)
	}
	JoinedGitEnv = strings.Join(GitEnv, "\n")
}
<|endoftext|>


<commit_before>// Package goinline implements inlining for go identifiers.
package goinline

import (
	"fmt"
	"go/ast"
	"go/parser"
	"go/token"
	"reflect"
	"strings"

	"golang.org/x/tools/go/ast/astutil"
)

// Inline replaces each instance of identifier k with v.Ident in ast.File f,
// for k, v := range m.
// For all inlines that were triggeres it also adds imports from v.Imports to f.
// In addition, it removes top level type declarations of the form
// type k ...
// for all k in m.
//
// Every k in m should be a valid identifier.
// Every v.Ident should be a valid expression.
func Inline(fset *token.FileSet, f *ast.File, m map[string]Target) error {
	// Build the inline map.
	im := map[string]reflect.Value{}
	for k, v := range m {
		expr, err := parser.ParseExpr(k)
		if err != nil {
			return fmt.Errorf("failed to parse `%s`: %s", k, err)
		}
		if _, ok := expr.(*ast.Ident); !ok {
			return fmt.Errorf("expected identifier, got %s which is %T", k, expr)
		}
		expr, err = parser.ParseExpr(v.Ident)
		if err != nil {
			return fmt.Errorf("failed to parse `%s`: %s", v.Ident, err)
		}
		s := v.Ident
		if _, ok := expr.(*ast.StarExpr); ok {
			s = fmt.Sprintf("(%s)", s)
		}
		im[k] = reflect.ValueOf(ast.Ident{Name: s})
	}
	// Filter `type XXX ...` declarations out if we are inlining XXX.
	cmap := ast.NewCommentMap(fset, f, f.Comments)
	to := 0
	for _, d := range f.Decls {
		skip := false
		if t, ok := d.(*ast.GenDecl); ok {
			for _, s := range t.Specs {
				ts, ok := s.(*ast.TypeSpec)
				if !ok {
					continue
				}
				if _, ok = im[ts.Name.String()]; ok {
					skip = true
				}
			}
		}
		if !skip {
			f.Decls[to] = d
			to++
		}
	}
	if to != len(f.Decls) {
		f.Decls = f.Decls[:to]
		// Remove comments for the declarations that were filtered out.
		f.Comments = cmap.Filter(f).Comments()
	}
	// Add imports for the inlines that were triggered.
	for k := range inline(im, f) {
		for _, imp := range m[k].Imports {
			astutil.AddImport(fset, f, imp)
		}
	}
	return nil
}

// Target for inlining.
type Target struct {
	// Ident is a go identifier for the target.
	Ident string
	// Imports are the imporst to be added if the inline is triggered.
	Imports []string
}

// ParseTarget parses a target string.
// Expected format:
// xxx->[import1,import2,...importn::]yyy
// Examples:
// Value->int
// X->go/topen::*token.FileSet
func ParseTarget(s string) (string, Target, error) {
	ps := strings.Split(s, "->")
	if len(ps) != 2 {
		return "", Target{}, fmt.Errorf("expected xxx->yyy, got `%s`", s)
	}
	name := ps[0]
	parts := strings.Split(ps[1], "::")
	if len(parts) > 2 {
		return "", Target{}, fmt.Errorf("expected something like a,b,c::v , got `%s`", ps[1])
	}
	if len(parts) == 1 {
		return name, Target{Ident: parts[0]}, nil
	}
	return name, Target{Ident: parts[1], Imports: strings.Split(parts[0], ",")}, nil
}

func inline(im map[string]reflect.Value, node ast.Node) map[string]bool {
	i := inliner{im: im, triggered: map[string]bool{}}
	ast.Walk(i, node)
	return i.triggered
}

type inliner struct {
	im        map[string]reflect.Value
	triggered map[string]bool
}

func (x inliner) Visit(node ast.Node) ast.Visitor {
	switch t := node.(type) {
	case *ast.Ident:
		v, ok := x.im[t.Name]
		if !ok {
			break
		}
		x.triggered[t.Name] = true
		reflect.ValueOf(t).Elem().Set(v)
	}
	return x
}
<commit_msg>added NoFiltering field to Target<commit_after>// Package goinline implements inlining for go identifiers.
package goinline

import (
	"fmt"
	"go/ast"
	"go/parser"
	"go/token"
	"reflect"
	"strings"

	"golang.org/x/tools/go/ast/astutil"
)

// Inline replaces each instance of identifier k with v.Ident in ast.File f,
// for k, v := range m.
// For all inlines that were triggeres it also adds imports from v.Imports to f.
// In addition, it removes top level type declarations of the form
// type k ...
// for all k in m.
//
// Every k in m should be a valid identifier.
// Every v.Ident should be a valid expression.
func Inline(fset *token.FileSet, f *ast.File, m map[string]Target) error {
	// Build the inline map.
	im := map[string]reflect.Value{}
	for k, v := range m {
		expr, err := parser.ParseExpr(k)
		if err != nil {
			return fmt.Errorf("failed to parse `%s`: %s", k, err)
		}
		if _, ok := expr.(*ast.Ident); !ok {
			return fmt.Errorf("expected identifier, got %s which is %T", k, expr)
		}
		expr, err = parser.ParseExpr(v.Ident)
		if err != nil {
			return fmt.Errorf("failed to parse `%s`: %s", v.Ident, err)
		}
		s := v.Ident
		if _, ok := expr.(*ast.StarExpr); ok {
			s = fmt.Sprintf("(%s)", s)
		}
		im[k] = reflect.ValueOf(ast.Ident{Name: s})
	}
	// Filter `type XXX ...` declarations out if we are inlining XXX.
	cmap := ast.NewCommentMap(fset, f, f.Comments)
	to := 0
	for _, d := range f.Decls {
		skip := false
		if t, ok := d.(*ast.GenDecl); ok {
			for _, s := range t.Specs {
				ts, ok := s.(*ast.TypeSpec)
				if !ok {
					continue
				}
				if t, ok := m[ts.Name.String()]; ok {
					if !t.NoFiltering {
						skip = true
					}
				}
			}
		}
		if !skip {
			f.Decls[to] = d
			to++
		}
	}
	if to != len(f.Decls) {
		f.Decls = f.Decls[:to]
		// Remove comments for the declarations that were filtered out.
		f.Comments = cmap.Filter(f).Comments()
	}
	// Add imports for the inlines that were triggered.
	for k := range inline(im, f) {
		for _, imp := range m[k].Imports {
			astutil.AddImport(fset, f, imp)
		}
	}
	return nil
}

// Target for inlining.
type Target struct {
	// Ident is a go identifier for the target.
	Ident string
	// Imports are the imporst to be added if the inline is triggered.
	Imports []string
	// NoFiltering prevents removing type Ident when inlining.
	NoFiltering bool
}

// ParseTarget parses a target string.
// Expected format:
// xxx->[import1,import2,...importn::]yyy
// Examples:
// Value->int
// X->go/topen::*token.FileSet
func ParseTarget(s string) (string, Target, error) {
	ps := strings.Split(s, "->")
	if len(ps) != 2 {
		return "", Target{}, fmt.Errorf("expected xxx->yyy, got `%s`", s)
	}
	name := ps[0]
	parts := strings.Split(ps[1], "::")
	if len(parts) > 2 {
		return "", Target{}, fmt.Errorf("expected something like a,b,c::v , got `%s`", ps[1])
	}
	if len(parts) == 1 {
		return name, Target{Ident: parts[0]}, nil
	}
	return name, Target{Ident: parts[1], Imports: strings.Split(parts[0], ",")}, nil
}

func inline(im map[string]reflect.Value, node ast.Node) map[string]bool {
	i := inliner{im: im, triggered: map[string]bool{}}
	ast.Walk(i, node)
	return i.triggered
}

type inliner struct {
	im        map[string]reflect.Value
	triggered map[string]bool
}

func (x inliner) Visit(node ast.Node) ast.Visitor {
	switch t := node.(type) {
	case *ast.Ident:
		v, ok := x.im[t.Name]
		if !ok {
			break
		}
		x.triggered[t.Name] = true
		reflect.ValueOf(t).Elem().Set(v)
	}
	return x
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2014, The Tor Project, Inc.
 * See LICENSE for licensing information
 */

package upnp

import (
	"encoding/xml"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
	"path"
	"strconv"
	"strings"
	"time"

	"github.com/yawning/go-fw-helper/natclient/upnp/httpu"
)

const (
	mSearchMethod = "M-SEARCH"
	mSearchURL    = "*"
	mSearchHost   = "239.255.255.250:1900"
	mSearchMan    = "\"ssdp:discover\""
	mSearchMx     = "2"
	mSearchStRoot = "upnp:rootdevice"

	internetGatewayDevice = "InternetGatewayDevice"
	wanDevice             = "WANDevice"
	wanConnectionDevice   = "WANConnectionDevice"
	wanIPConnection       = "WANIPConnection"
	wanPPPConnection      = "WANPPPConnection"

	maxRetries     = 3
	requestTimeout = 2 * time.Second // Match mSearchMx
)

type controlPoint struct {
	url *url.URL
	urn *upnpURN
}

type upnpURN struct {
	domainName string
	kind       string
	kindType   string
	version    int
}

func (u *upnpURN) String() string {
	return fmt.Sprintf("urn:%s:%s:%s:%d", u.domainName, u.kind, u.kindType, u.version)
}

func parseURN(s string) (*upnpURN, error) {
	split := strings.Split(s, ":")
	if len(split) != 5 {
		return nil, fmt.Errorf("urn: malformed %d elements", len(split))
	}
	if split[0] != "urn" {
		return nil, fmt.Errorf("urn: invalid prefix")
	}
	v, err := strconv.ParseInt(split[4], 10, 8)
	if err != nil {
		return nil, fmt.Errorf("urn: malformed version: %s", err)
	}
	return &upnpURN{split[1], split[2], split[3], int(v)}, nil
}

type upnpRoot struct {
	SpecVersion struct {
		Major int `xml:"major"`
		Minor int `xml:"minor"`
	} `xml:"specVersion"`
	URLBase string     `xml:"URLBase"`
	Device  upnpDevice `xml:"device"`
}

type upnpDevice struct {
	DeviceType   string          `xml:"deviceType"`
	FriendlyName string          `xml:"friendlyName"`
	Manufacturer string          `xml:"manufacturer"`
	ModelName    string          `xml:"modelName"`
	UDN          string          `xml:"UDN"`
	DeviceList   upnpDeviceList  `xml:"deviceList"`
	ServiceList  upnpServiceList `xml:"serviceList"`
}

type upnpService struct {
	ServiceType string `xml:"serviceType"`
	ServiceID   string `xml:"serviceId"`
	SCPDURL     string `xml:"SCPDURL"`
	ControlURL  string `xml:"controlURL"`
	EventSubURL string `xml:"eventSubURL"`
}

type upnpDeviceList struct {
	Device []upnpDevice `xml:"device"`
}

type upnpServiceList struct {
	Service []upnpService `xml:"service"`
}

func (d *upnpDevice) is(k string) bool {
	urn, err := parseURN(d.DeviceType)
	if err != nil {
		return false
	}
	return urn.kind == "device" && urn.kindType == k
}

func (s *upnpService) is(k string) bool {
	urn, err := parseURN(s.ServiceType)
	if err != nil {
		return false
	}
	return urn.kind == "service" && urn.kindType == k
}

func (d *upnpDevice) findChild(k string) *upnpDevice {
	for _, dd := range d.DeviceList.Device {
		if dd.is(k) {
			return &dd
		}
	}
	return nil
}

func (d *upnpDevice) findService(k string) *upnpService {
	for _, s := range d.ServiceList.Service {
		if s.is(k) {
			return &s
		}
	}
	return nil
}

func (c *Client) discover() (cp *controlPoint, localAddr net.IP, err error) {
	// The uPNP discovery process is 3 steps.
	//  1. Figure out where the relevant device is via M-SEARCH over UDP
	//     multicast.
	//  2. Pull down the "Device Description" XML document to figure out the
	//     controlURL and SCPDURL for the desired services.
	//  3. Pull down the "Service Description" document for each of the
	//     services, to figure out the details.
	//
	// This implementation skips step 3 because all of the desired services are
	// so basic that only the most shady fly-by-night of uPNP implementors will
	// screw them up to the point where our calls don't "work" (Note: At least
	// historically, most shady fly-by-night uPNP implementors like Broadcom
	// have screwed up UPnP to the point where "work" is loosely defined.)

	// 1. Find the target devices.
	c.Vlogf("probing for UPNP root devices via M-SEARCH\n")
	rootXMLLocs, err := discoverRootDevices()
	if err != nil {
		return nil, nil, err
	}

	c.Vlogf("received %d potential root devices\n", len(rootXMLLocs))

	for _, rootLoc := range rootXMLLocs {
		// 2. Pull down the "Device Description" document.
		c.Vlogf("downloading 'Device Description' from %s\n", rootLoc)
		rootXML, localAddr, err := retreiveDeviceDescription(rootLoc)
		if err != nil {
			c.Vlogf("download failed: %s\n", err)
			continue
		}

		// Figure out the controlURL (and SCPDURL).
		//
		//  -+- InternetGatewayDevice
		//       |
		//       +- WANDevice
		//       |   |
		//       |   +- WANConnectionDevice
		//       |   |   |
		//       |   |   +- WANIPConnection (Service)
		//       |   |   |
		//       |   |   +- WANPPPConnection (Service)
		//
		// Ugh.  Technically things under the InternetGatewayDevice can be
		// duplicated, but if anyone has a multihomed home router with more
		// than one uplink connection, it's probably ok to assume that they
		// can setup port forwarding themselves, or can pay someone to do so.
		cp = &controlPoint{}
		var urlBase *url.URL
		if rootXML.SpecVersion.Major == 1 && rootXML.SpecVersion.Minor == 0 {
			// uPNP 1.0 has an optional URLBase that is used as the base for
			// all of the relative URLs.  uPNP 1.1 and later do the sensible
			// thing and just use absolute URLs everywhere.
			if rootXML.URLBase != "" {
				urlBase, err = url.Parse(rootXML.URLBase)
				if err != nil {
					c.Vlogf("malformed URLBase: %s\n", err)
					continue
				}
			} else {
				// Per the spec: "If URLBase is empty or not given, the base
				// URL is the URL from which the device description was
				// retreived.
				urlBase = &url.URL{Scheme: rootLoc.Scheme, Host: rootLoc.Host}
			}
		}
		rootD := rootXML.Device // InternetGatewayDevice
		c.Vlogf("device: %s - %s\n", rootD.Manufacturer, rootD.ModelName)
		if !rootD.is(internetGatewayDevice) {
			c.Vlogf("root device is not a %s\n", internetGatewayDevice)
			continue
		}
		wanD := rootD.findChild(wanDevice) // WANDevice
		if wanD == nil {
			c.Vlogf("device does not have a %s\n", wanDevice)
			continue
		}
		wanConnD := wanD.findChild(wanConnectionDevice) // WANConnectionDevice
		if wanConnD == nil {
			c.Vlogf("device does not have a %s\n", wanConnectionDevice)
			continue
		}

		// WANIPConnection is the prefered service to use, though a lot of
		// routers export both, and really old DSL modems only export one.
		// Check both, with preference towards the new hotness, what we want to
		// do works with either.
		okServices := []string{wanIPConnection, wanPPPConnection}
		for _, svc := range okServices {
			s := wanConnD.findService(svc)
			if s != nil {
				if urlBase != nil {
					// ControlURL is relative, so build it using urlBase.
					// This assumes that none of the routers use a BaseURL or
					// ControlURL that contains querys or fragments, which may
					// be incorrect.
					cp.url = urlBase
					cp.url.Path = path.Join(cp.url.Path, s.ControlURL)
				} else {
					// ControlURL is absolute.
					cp.url, err = url.Parse(s.ControlURL)
					if err != nil {
						c.Vlogf("malformed ControlURL: %s\n", err)
						continue
					}
				}
				cp.urn, _ = parseURN(s.ServiceType)

				// 3. Pull down the "Service Description" document. (Skipped)
				c.Vlogf("found a %s at %s\n", cp.urn.kindType, cp.url)
				c.Vlogf("local IP is %s\n", localAddr)

				return cp, localAddr, nil
			}
		}

		c.Vlogf("device has no compatible upstream services\n")
	}
	return nil, nil, fmt.Errorf("failed to find a compatible service")
}

func discoverRootDevices() ([]*url.URL, error) {
	// 1.3.2 Search request with M-SEARCH
	//
	// This is done via a HTTPMU request.  The response is unicasted back.
	//
	// The request is formatted as thus:
	//  M-SEARCH * HTTP/1.1
	//  HOST: 239.255.255.250:1900
	//  MAN: "ssdp:discover"
	//  MX: seconds to delay response
	//  ST: search target
	//  USER-AGENT: OS/version UPnP/1.1 product/version
	req, err := http.NewRequest(mSearchMethod, "", nil)
	if err != nil {
		return nil, err
	}
	req.Host = mSearchHost
	req.URL.Opaque = mSearchURL // NewRequest escapes the path, use Opaque.
	req.Header.Set("MAN", mSearchMan)
	req.Header.Set("MX", mSearchMx)
	req.Header.Set("ST", mSearchStRoot)
	req.Header.Set("User-Agent", userAgent)

	hc, err := httpu.New(outgoingPort)
	if err != nil {
		return nil, err
	}
	resps, err := hc.Do(req, requestTimeout, maxRetries)
	if err != nil {
		return nil, err
	}
	locs := make([]*url.URL, 0, len(resps))
	for _, resp := range resps {
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			continue
		}
		if resp.Header.Get("ST") != req.Header.Get("ST") {
			continue
		}
		xmlLoc, err := url.Parse(resp.Header.Get("Location"))
		if err != nil {
			continue
		}
		locs = append(locs, xmlLoc)
	}
	if len(locs) > 0 {
		return locs, nil
	}
	return nil, fmt.Errorf("ssdp: failed to discover any root devices")
}

func retreiveDeviceDescription(xmlLoc *url.URL) (*upnpRoot, net.IP, error) {
	c, err := net.Dial("tcp", xmlLoc.Host)
	if err != nil {
		return nil, nil, err
	}
	conn := httputil.NewClientConn(c, nil)
	defer conn.Close()

	// At this point we have the local address of the http socket, that can
	// apparently talk to the UPnP device, so save that off as the local
	// address.
	localAddr := c.LocalAddr()

	req, err := http.NewRequest("GET", xmlLoc.String(), nil)
	if err != nil {
		return nil, nil, err
	}
	req.Header.Set("User-Agent", userAgent)
	resp, err := conn.Do(req)
	if err != nil {
		return nil, nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return nil, nil, fmt.Errorf("XML fetch failed with status: %s", resp.Status)
	}
	xmlDoc, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, nil, err
	}
	rewt := &upnpRoot{}
	if err = xml.Unmarshal(xmlDoc, rewt); err != nil {
		return nil, nil, err
	}

	// This should always be true, but be paranoid.
	if tcpAddr, ok := localAddr.(*net.TCPAddr); ok {
		return rewt, tcpAddr.IP, nil
	}

	return nil, nil, fmt.Errorf("failed to determine local address")
}
<commit_msg>ErrPersistEOF is allowed when using httputil.<commit_after>/*
 * Copyright (c) 2014, The Tor Project, Inc.
 * See LICENSE for licensing information
 */

package upnp

import (
	"encoding/xml"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
	"path"
	"strconv"
	"strings"
	"time"

	"github.com/yawning/go-fw-helper/natclient/upnp/httpu"
)

const (
	mSearchMethod = "M-SEARCH"
	mSearchURL    = "*"
	mSearchHost   = "239.255.255.250:1900"
	mSearchMan    = "\"ssdp:discover\""
	mSearchMx     = "2"
	mSearchStRoot = "upnp:rootdevice"

	internetGatewayDevice = "InternetGatewayDevice"
	wanDevice             = "WANDevice"
	wanConnectionDevice   = "WANConnectionDevice"
	wanIPConnection       = "WANIPConnection"
	wanPPPConnection      = "WANPPPConnection"

	maxRetries     = 3
	requestTimeout = 2 * time.Second // Match mSearchMx
)

type controlPoint struct {
	url *url.URL
	urn *upnpURN
}

type upnpURN struct {
	domainName string
	kind       string
	kindType   string
	version    int
}

func (u *upnpURN) String() string {
	return fmt.Sprintf("urn:%s:%s:%s:%d", u.domainName, u.kind, u.kindType, u.version)
}

func parseURN(s string) (*upnpURN, error) {
	split := strings.Split(s, ":")
	if len(split) != 5 {
		return nil, fmt.Errorf("urn: malformed %d elements", len(split))
	}
	if split[0] != "urn" {
		return nil, fmt.Errorf("urn: invalid prefix")
	}
	v, err := strconv.ParseInt(split[4], 10, 8)
	if err != nil {
		return nil, fmt.Errorf("urn: malformed version: %s", err)
	}
	return &upnpURN{split[1], split[2], split[3], int(v)}, nil
}

type upnpRoot struct {
	SpecVersion struct {
		Major int `xml:"major"`
		Minor int `xml:"minor"`
	} `xml:"specVersion"`
	URLBase string     `xml:"URLBase"`
	Device  upnpDevice `xml:"device"`
}

type upnpDevice struct {
	DeviceType   string          `xml:"deviceType"`
	FriendlyName string          `xml:"friendlyName"`
	Manufacturer string          `xml:"manufacturer"`
	ModelName    string          `xml:"modelName"`
	UDN          string          `xml:"UDN"`
	DeviceList   upnpDeviceList  `xml:"deviceList"`
	ServiceList  upnpServiceList `xml:"serviceList"`
}

type upnpService struct {
	ServiceType string `xml:"serviceType"`
	ServiceID   string `xml:"serviceId"`
	SCPDURL     string `xml:"SCPDURL"`
	ControlURL  string `xml:"controlURL"`
	EventSubURL string `xml:"eventSubURL"`
}

type upnpDeviceList struct {
	Device []upnpDevice `xml:"device"`
}

type upnpServiceList struct {
	Service []upnpService `xml:"service"`
}

func (d *upnpDevice) is(k string) bool {
	urn, err := parseURN(d.DeviceType)
	if err != nil {
		return false
	}
	return urn.kind == "device" && urn.kindType == k
}

func (s *upnpService) is(k string) bool {
	urn, err := parseURN(s.ServiceType)
	if err != nil {
		return false
	}
	return urn.kind == "service" && urn.kindType == k
}

func (d *upnpDevice) findChild(k string) *upnpDevice {
	for _, dd := range d.DeviceList.Device {
		if dd.is(k) {
			return &dd
		}
	}
	return nil
}

func (d *upnpDevice) findService(k string) *upnpService {
	for _, s := range d.ServiceList.Service {
		if s.is(k) {
			return &s
		}
	}
	return nil
}

func (c *Client) discover() (cp *controlPoint, localAddr net.IP, err error) {
	// The uPNP discovery process is 3 steps.
	//  1. Figure out where the relevant device is via M-SEARCH over UDP
	//     multicast.
	//  2. Pull down the "Device Description" XML document to figure out the
	//     controlURL and SCPDURL for the desired services.
	//  3. Pull down the "Service Description" document for each of the
	//     services, to figure out the details.
	//
	// This implementation skips step 3 because all of the desired services are
	// so basic that only the most shady fly-by-night of uPNP implementors will
	// screw them up to the point where our calls don't "work" (Note: At least
	// historically, most shady fly-by-night uPNP implementors like Broadcom
	// have screwed up UPnP to the point where "work" is loosely defined.)

	// 1. Find the target devices.
	c.Vlogf("probing for UPNP root devices via M-SEARCH\n")
	rootXMLLocs, err := discoverRootDevices()
	if err != nil {
		return nil, nil, err
	}

	c.Vlogf("received %d potential root devices\n", len(rootXMLLocs))

	for _, rootLoc := range rootXMLLocs {
		// 2. Pull down the "Device Description" document.
		c.Vlogf("downloading 'Device Description' from %s\n", rootLoc)
		rootXML, localAddr, err := retreiveDeviceDescription(rootLoc)
		if err != nil {
			c.Vlogf("download failed: %s\n", err)
			continue
		}

		// Figure out the controlURL (and SCPDURL).
		//
		//  -+- InternetGatewayDevice
		//       |
		//       +- WANDevice
		//       |   |
		//       |   +- WANConnectionDevice
		//       |   |   |
		//       |   |   +- WANIPConnection (Service)
		//       |   |   |
		//       |   |   +- WANPPPConnection (Service)
		//
		// Ugh.  Technically things under the InternetGatewayDevice can be
		// duplicated, but if anyone has a multihomed home router with more
		// than one uplink connection, it's probably ok to assume that they
		// can setup port forwarding themselves, or can pay someone to do so.
		cp = &controlPoint{}
		var urlBase *url.URL
		if rootXML.SpecVersion.Major == 1 && rootXML.SpecVersion.Minor == 0 {
			// uPNP 1.0 has an optional URLBase that is used as the base for
			// all of the relative URLs.  uPNP 1.1 and later do the sensible
			// thing and just use absolute URLs everywhere.
			if rootXML.URLBase != "" {
				urlBase, err = url.Parse(rootXML.URLBase)
				if err != nil {
					c.Vlogf("malformed URLBase: %s\n", err)
					continue
				}
			} else {
				// Per the spec: "If URLBase is empty or not given, the base
				// URL is the URL from which the device description was
				// retreived.
				urlBase = &url.URL{Scheme: rootLoc.Scheme, Host: rootLoc.Host}
			}
		}
		rootD := rootXML.Device // InternetGatewayDevice
		c.Vlogf("device: %s - %s\n", rootD.Manufacturer, rootD.ModelName)
		if !rootD.is(internetGatewayDevice) {
			c.Vlogf("root device is not a %s\n", internetGatewayDevice)
			continue
		}
		wanD := rootD.findChild(wanDevice) // WANDevice
		if wanD == nil {
			c.Vlogf("device does not have a %s\n", wanDevice)
			continue
		}
		wanConnD := wanD.findChild(wanConnectionDevice) // WANConnectionDevice
		if wanConnD == nil {
			c.Vlogf("device does not have a %s\n", wanConnectionDevice)
			continue
		}

		// WANIPConnection is the prefered service to use, though a lot of
		// routers export both, and really old DSL modems only export one.
		// Check both, with preference towards the new hotness, what we want to
		// do works with either.
		okServices := []string{wanIPConnection, wanPPPConnection}
		for _, svc := range okServices {
			s := wanConnD.findService(svc)
			if s != nil {
				if urlBase != nil {
					// ControlURL is relative, so build it using urlBase.
					// This assumes that none of the routers use a BaseURL or
					// ControlURL that contains querys or fragments, which may
					// be incorrect.
					cp.url = urlBase
					cp.url.Path = path.Join(cp.url.Path, s.ControlURL)
				} else {
					// ControlURL is absolute.
					cp.url, err = url.Parse(s.ControlURL)
					if err != nil {
						c.Vlogf("malformed ControlURL: %s\n", err)
						continue
					}
				}
				cp.urn, _ = parseURN(s.ServiceType)

				// 3. Pull down the "Service Description" document. (Skipped)
				c.Vlogf("found a %s at %s\n", cp.urn.kindType, cp.url)
				c.Vlogf("local IP is %s\n", localAddr)

				return cp, localAddr, nil
			}
		}

		c.Vlogf("device has no compatible upstream services\n")
	}
	return nil, nil, fmt.Errorf("failed to find a compatible service")
}

func discoverRootDevices() ([]*url.URL, error) {
	// 1.3.2 Search request with M-SEARCH
	//
	// This is done via a HTTPMU request.  The response is unicasted back.
	//
	// The request is formatted as thus:
	//  M-SEARCH * HTTP/1.1
	//  HOST: 239.255.255.250:1900
	//  MAN: "ssdp:discover"
	//  MX: seconds to delay response
	//  ST: search target
	//  USER-AGENT: OS/version UPnP/1.1 product/version
	req, err := http.NewRequest(mSearchMethod, "", nil)
	if err != nil {
		return nil, err
	}
	req.Host = mSearchHost
	req.URL.Opaque = mSearchURL // NewRequest escapes the path, use Opaque.
	req.Header.Set("MAN", mSearchMan)
	req.Header.Set("MX", mSearchMx)
	req.Header.Set("ST", mSearchStRoot)
	req.Header.Set("User-Agent", userAgent)

	hc, err := httpu.New(outgoingPort)
	if err != nil {
		return nil, err
	}
	resps, err := hc.Do(req, requestTimeout, maxRetries)
	if err != nil {
		return nil, err
	}
	locs := make([]*url.URL, 0, len(resps))
	for _, resp := range resps {
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			continue
		}
		if resp.Header.Get("ST") != req.Header.Get("ST") {
			continue
		}
		xmlLoc, err := url.Parse(resp.Header.Get("Location"))
		if err != nil {
			continue
		}
		locs = append(locs, xmlLoc)
	}
	if len(locs) > 0 {
		return locs, nil
	}
	return nil, fmt.Errorf("ssdp: failed to discover any root devices")
}

func retreiveDeviceDescription(xmlLoc *url.URL) (*upnpRoot, net.IP, error) {
	c, err := net.Dial("tcp", xmlLoc.Host)
	if err != nil {
		return nil, nil, err
	}
	conn := httputil.NewClientConn(c, nil)
	defer conn.Close()

	// At this point we have the local address of the http socket, that can
	// apparently talk to the UPnP device, so save that off as the local
	// address.
	localAddr := c.LocalAddr()

	req, err := http.NewRequest("GET", xmlLoc.String(), nil)
	if err != nil {
		return nil, nil, err
	}
	req.Header.Set("User-Agent", userAgent)
	resp, err := conn.Do(req)
	if err != nil && err != httputil.ErrPersistEOF {
		return nil, nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return nil, nil, fmt.Errorf("XML fetch failed with status: %s", resp.Status)
	}
	xmlDoc, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, nil, err
	}
	rewt := &upnpRoot{}
	if err = xml.Unmarshal(xmlDoc, rewt); err != nil {
		return nil, nil, err
	}

	// This should always be true, but be paranoid.
	if tcpAddr, ok := localAddr.(*net.TCPAddr); ok {
		return rewt, tcpAddr.IP, nil
	}

	return nil, nil, fmt.Errorf("failed to determine local address")
}
<|endoftext|>


<commit_before>// Package xmlsec is a wrapper around the xmlsec1 command
// https://www.aleksey.com/xmlsec/index.html
package xmlsec

import (
	"bufio"
	"encoding/xml"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"strings"
)

// ErrSelfSignedCertificate is a typed error returned when xmlsec1 detects a
// self-signed certificate.
type ErrSelfSignedCertificate struct {
	err error
}

// Error returns the underlying error reported by xmlsec1.
func (e ErrSelfSignedCertificate) Error() string {
	return e.err.Error()
}

// ErrUnknownIssuer is a typed error returned when xmlsec1 detects a
// "unknown issuer" error.
type ErrUnknownIssuer struct {
	err error
}

// Error returns the underlying error reported by xmlsec1.
func (e ErrUnknownIssuer) Error() string {
	return e.err.Error()
}

// Encrypt encrypts a byte sequence into an EncryptedData template using the
// given certificate and encryption method.
func Encrypt(template *EncryptedData, in []byte, publicCertPath string, method string) ([]byte, error) {
	// Writing template.
	fp, err := ioutil.TempFile("/tmp", "xmlsec")
	if err != nil {
		return nil, err
	}
	defer os.Remove(fp.Name())

	out, err := xml.MarshalIndent(template, "", "\t")
	if err != nil {
		return nil, err
	}
	_, err = fp.Write(out)
	if err != nil {
		return nil, err
	}
	if err := fp.Close(); err != nil {
		return nil, err
	}

	// Executing command.
	cmd := exec.Command("xmlsec1", "--encrypt",
		"--session-key", method,
		"--pubkey-cert-pem", publicCertPath,
		"--output", "/dev/stdout",
		"--xml-data", "/dev/stdin",
		fp.Name(),
	)

	stdin, err := cmd.StdinPipe()
	if err != nil {
		return nil, err
	}

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return nil, err
	}

	stderr, err := cmd.StderrPipe()
	if err != nil {
		return nil, err
	}

	outbr := bufio.NewReader(stdout)
	errbr := bufio.NewReader(stderr)

	if err := cmd.Start(); err != nil {
		return nil, err
	}

	if _, err := stdin.Write(in); err != nil {
		return nil, err
	}

	if err := stdin.Close(); err != nil {
		return nil, err
	}

	res, err := ioutil.ReadAll(outbr)
	if err != nil {
		return nil, err
	}

	resErr, err := ioutil.ReadAll(errbr)
	if err != nil {
		return nil, err
	}

	if err := cmd.Wait(); err != nil {
		if len(resErr) > 0 {
			return res, xmlsecErr(string(resErr))
		}
		return nil, err
	}

	return res, nil
}

// Decrypt takes an encrypted XML document and decrypts it using the given
// private key.
func Decrypt(in []byte, privateKeyPath string) ([]byte, error) {
	// Executing command.
	cmd := exec.Command("xmlsec1", "--decrypt",
		"--privkey-pem", privateKeyPath,
		"--output", "/dev/stdout",
		"/dev/stdin",
	)

	stdin, err := cmd.StdinPipe()
	if err != nil {
		return nil, err
	}

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return nil, err
	}

	stderr, err := cmd.StderrPipe()
	if err != nil {
		return nil, err
	}

	outbr := bufio.NewReader(stdout)
	errbr := bufio.NewReader(stderr)

	if err := cmd.Start(); err != nil {
		return nil, err
	}

	if _, err := stdin.Write(in); err != nil {
		return nil, err
	}

	if err := stdin.Close(); err != nil {
		return nil, err
	}

	res, err := ioutil.ReadAll(outbr)
	if err != nil {
		return nil, err
	}

	resErr, err := ioutil.ReadAll(errbr)
	if err != nil {
		return nil, err
	}

	if err := cmd.Wait(); err != nil {
		if len(resErr) > 0 {
			return res, xmlsecErr(string(resErr))
		}
		return nil, err
	}

	return res, nil
}

// Verify takes a signed XML document and validates its signature.
func Verify(in []byte, publicCertPath string, id string) error {
	cmd := exec.Command("xmlsec1", "--verify",
		"--pubkey-cert-pem", publicCertPath,
		// Security: Don't ever use --enabled-reference-uris "local" value,
		// since it'd allow potential attackers to read local files using
		// <Reference URI="file:///etc/passwd"> hack!
		"--enabled-reference-uris", "empty,same-doc",
		"--id-attr:ID", id,
		"/dev/stdin",
	)

	stdin, err := cmd.StdinPipe()
	if err != nil {
		return err
	}

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return err
	}

	stderr, err := cmd.StderrPipe()
	if err != nil {
		return err
	}

	outbr := bufio.NewReader(stdout)
	errbr := bufio.NewReader(stderr)

	if err := cmd.Start(); err != nil {
		return err
	}

	if _, err := stdin.Write(in); err != nil {
		return err
	}

	if err := stdin.Close(); err != nil {
		return err
	}

	res, err := ioutil.ReadAll(outbr)
	if err != nil {
		return err
	}

	resErr, err := ioutil.ReadAll(errbr)
	if err != nil {
		return err
	}

	if err := cmd.Wait(); err != nil {

		if len(resErr) > 0 {
			return xmlsecErr(string(res) + "\n" + string(resErr))
		}
		return err
	}

	return nil
}

// Sign takes a XML document and produces a signature.
func Sign(in []byte, privateKeyPath string, id string) (out []byte, err error) {
	cmd := exec.Command("xmlsec1",
		"--sign",
		"--privkey-pem", privateKeyPath,
		"--enabled-reference-uris", "empty,same-doc",
		"--id-attr:ID", id,
		"--output", "/dev/stdout",
		"/dev/stdin",
	)

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return nil, err
	}

	stderr, err := cmd.StderrPipe()
	if err != nil {
		return nil, err
	}

	stdin, err := cmd.StdinPipe()
	if err != nil {
		return nil, err
	}

	outbr := bufio.NewReader(stdout)
	errbr := bufio.NewReader(stderr)

	if err := cmd.Start(); err != nil {
		return nil, err
	}

	if _, err := stdin.Write(in); err != nil {
		return nil, err
	}

	if err := stdin.Close(); err != nil {
		return nil, err
	}

	res, err := ioutil.ReadAll(outbr)
	if err != nil {
		return nil, err
	}

	resErr, err := ioutil.ReadAll(errbr)
	if err != nil {
		return nil, err
	}

	if err := cmd.Wait(); err != nil {
		if len(resErr) > 0 {
			return res, xmlsecErr(string(resErr))
		}

		return nil, err
	}

	return res, nil
}

func xmlsecErr(s string) error {
	err := fmt.Errorf("xmlsec: %s", strings.TrimSpace(s))
	if strings.HasPrefix(s, "OK") {
		return nil
	}
	if strings.Contains(err.Error(), "msg=self signed certificate") {
		return ErrSelfSignedCertificate{err}
	}
	if strings.Contains(err.Error(), "msg=unable to get local issuer certificate") {
		return ErrUnknownIssuer{err}
	}
	return err
}
<commit_msg>do not recover from signature failed<commit_after>// Package xmlsec is a wrapper around the xmlsec1 command
// https://www.aleksey.com/xmlsec/index.html
package xmlsec

import (
	"bufio"
	"encoding/xml"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"strings"
)

// ErrSelfSignedCertificate is a typed error returned when xmlsec1 detects a
// self-signed certificate.
type ErrSelfSignedCertificate struct {
	err error
}

// Error returns the underlying error reported by xmlsec1.
func (e ErrSelfSignedCertificate) Error() string {
	return e.err.Error()
}

// ErrUnknownIssuer is a typed error returned when xmlsec1 detects a
// "unknown issuer" error.
type ErrUnknownIssuer struct {
	err error
}

// Error returns the underlying error reported by xmlsec1.
func (e ErrUnknownIssuer) Error() string {
	return e.err.Error()
}

// Encrypt encrypts a byte sequence into an EncryptedData template using the
// given certificate and encryption method.
func Encrypt(template *EncryptedData, in []byte, publicCertPath string, method string) ([]byte, error) {
	// Writing template.
	fp, err := ioutil.TempFile("/tmp", "xmlsec")
	if err != nil {
		return nil, err
	}
	defer os.Remove(fp.Name())

	out, err := xml.MarshalIndent(template, "", "\t")
	if err != nil {
		return nil, err
	}
	_, err = fp.Write(out)
	if err != nil {
		return nil, err
	}
	if err := fp.Close(); err != nil {
		return nil, err
	}

	// Executing command.
	cmd := exec.Command("xmlsec1", "--encrypt",
		"--session-key", method,
		"--pubkey-cert-pem", publicCertPath,
		"--output", "/dev/stdout",
		"--xml-data", "/dev/stdin",
		fp.Name(),
	)

	stdin, err := cmd.StdinPipe()
	if err != nil {
		return nil, err
	}

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return nil, err
	}

	stderr, err := cmd.StderrPipe()
	if err != nil {
		return nil, err
	}

	outbr := bufio.NewReader(stdout)
	errbr := bufio.NewReader(stderr)

	if err := cmd.Start(); err != nil {
		return nil, err
	}

	if _, err := stdin.Write(in); err != nil {
		return nil, err
	}

	if err := stdin.Close(); err != nil {
		return nil, err
	}

	res, err := ioutil.ReadAll(outbr)
	if err != nil {
		return nil, err
	}

	resErr, err := ioutil.ReadAll(errbr)
	if err != nil {
		return nil, err
	}

	if err := cmd.Wait(); err != nil {
		if len(resErr) > 0 {
			return res, xmlsecErr(string(resErr))
		}
		return nil, err
	}

	return res, nil
}

// Decrypt takes an encrypted XML document and decrypts it using the given
// private key.
func Decrypt(in []byte, privateKeyPath string) ([]byte, error) {
	// Executing command.
	cmd := exec.Command("xmlsec1", "--decrypt",
		"--privkey-pem", privateKeyPath,
		"--output", "/dev/stdout",
		"/dev/stdin",
	)

	stdin, err := cmd.StdinPipe()
	if err != nil {
		return nil, err
	}

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return nil, err
	}

	stderr, err := cmd.StderrPipe()
	if err != nil {
		return nil, err
	}

	outbr := bufio.NewReader(stdout)
	errbr := bufio.NewReader(stderr)

	if err := cmd.Start(); err != nil {
		return nil, err
	}

	if _, err := stdin.Write(in); err != nil {
		return nil, err
	}

	if err := stdin.Close(); err != nil {
		return nil, err
	}

	res, err := ioutil.ReadAll(outbr)
	if err != nil {
		return nil, err
	}

	resErr, err := ioutil.ReadAll(errbr)
	if err != nil {
		return nil, err
	}

	if err := cmd.Wait(); err != nil {
		if len(resErr) > 0 {
			return res, xmlsecErr(string(resErr))
		}
		return nil, err
	}

	return res, nil
}

// Verify takes a signed XML document and validates its signature.
func Verify(in []byte, publicCertPath string, id string) error {
	cmd := exec.Command("xmlsec1", "--verify",
		"--pubkey-cert-pem", publicCertPath,
		// Security: Don't ever use --enabled-reference-uris "local" value,
		// since it'd allow potential attackers to read local files using
		// <Reference URI="file:///etc/passwd"> hack!
		"--enabled-reference-uris", "empty,same-doc",
		"--id-attr:ID", id,
		"/dev/stdin",
	)

	stdin, err := cmd.StdinPipe()
	if err != nil {
		return err
	}

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return err
	}

	stderr, err := cmd.StderrPipe()
	if err != nil {
		return err
	}

	outbr := bufio.NewReader(stdout)
	errbr := bufio.NewReader(stderr)

	if err := cmd.Start(); err != nil {
		return err
	}

	if _, err := stdin.Write(in); err != nil {
		return err
	}

	if err := stdin.Close(); err != nil {
		return err
	}

	res, err := ioutil.ReadAll(outbr)
	if err != nil {
		return err
	}

	resErr, err := ioutil.ReadAll(errbr)
	if err != nil {
		return err
	}

	if err := cmd.Wait(); err != nil {

		if len(resErr) > 0 {
			return xmlsecErr(string(res) + "\n" + string(resErr))
		}
		return err
	}

	return nil
}

// Sign takes a XML document and produces a signature.
func Sign(in []byte, privateKeyPath string, id string) (out []byte, err error) {
	cmd := exec.Command("xmlsec1",
		"--sign",
		"--privkey-pem", privateKeyPath,
		"--enabled-reference-uris", "empty,same-doc",
		"--id-attr:ID", id,
		"--output", "/dev/stdout",
		"/dev/stdin",
	)

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return nil, err
	}

	stderr, err := cmd.StderrPipe()
	if err != nil {
		return nil, err
	}

	stdin, err := cmd.StdinPipe()
	if err != nil {
		return nil, err
	}

	outbr := bufio.NewReader(stdout)
	errbr := bufio.NewReader(stderr)

	if err := cmd.Start(); err != nil {
		return nil, err
	}

	if _, err := stdin.Write(in); err != nil {
		return nil, err
	}

	if err := stdin.Close(); err != nil {
		return nil, err
	}

	res, err := ioutil.ReadAll(outbr)
	if err != nil {
		return nil, err
	}

	resErr, err := ioutil.ReadAll(errbr)
	if err != nil {
		return nil, err
	}

	if err := cmd.Wait(); err != nil {
		if len(resErr) > 0 {
			return res, xmlsecErr(string(resErr))
		}

		return nil, err
	}

	return res, nil
}

func xmlsecErr(s string) error {
	err := fmt.Errorf("xmlsec: %s", strings.TrimSpace(s))
	if strings.HasPrefix(s, "OK") {
		return nil
	}
	if strings.Contains(err.Error(), "signature failed") {
		return err
	}
	if strings.Contains(err.Error(), "msg=self signed certificate") {
		return ErrSelfSignedCertificate{err}
	}
	if strings.Contains(err.Error(), "msg=unable to get local issuer certificate") {
		return ErrUnknownIssuer{err}
	}
	return err
}
<|endoftext|>


<commit_before>package gerrit

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"

	"golang.org/x/net/context/ctxhttp"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	"go.chromium.org/luci/common/errors"
	"go.chromium.org/luci/common/logging"
	gerritpb "go.chromium.org/luci/common/proto/gerrit"
)

// OAuthScope is the OAuth 2.0 scope that must be included when acquiring an
// access token for Gerrit RPCs.
const OAuthScope = "https://www.googleapis.com/auth/gerritcodereview"

// This file implements Gerrit proto service client
// on top of Gerrit REST API.

// NewRESTClient creates a new Gerrit client based on Gerrit's REST API.
//
// The host must be a full Gerrit host, e.g. "chromium-review.googlesource.com".
//
// If auth is true, indicates that the given HTTP client sends authenticated
// requests. If so, the requests to Gerrit will include "/a/" URL path
// prefix.
//
// RPC methods of the returned client return an error if a grpc.CallOption is
// passed.
func NewRESTClient(httpClient *http.Client, host string, auth bool) (gerritpb.GerritClient, error) {
	switch {
	case strings.Contains(host, "/"):
		return nil, errors.Reason("invalid host %q", host).Err()
	case !strings.HasSuffix(host, "-review.googlesource.com"):
		return nil, errors.New("Gerrit at googlesource subdomains end with '-review'")
	}

	baseURL := "https://" + host
	if auth {
		baseURL += "/a"
	}
	return &client{Client: httpClient, BaseURL: baseURL}, nil
}

// Implementation.

var jsonPrefix = []byte(")]}'")

// client implements gerritpb.GerritClient.
type client struct {
	Client *http.Client
	// BaseURL is the base URL for all API requests,
	// for example "https://chromium-review.googlesource.com/a".
	BaseURL string
}

// changeInfo is JSON representation of gerritpb.ChangeInfo on the wire.
type changeInfo struct {
	Number  int64                 `json:"_number"`
	Owner   *gerritpb.AccountInfo `json:"owner"`
	Project string                `json:"project"`
}

func (c *client) GetChange(ctx context.Context, req *gerritpb.GetChangeRequest, opts ...grpc.CallOption) (
	*gerritpb.ChangeInfo, error) {

	if err := checkArgs(opts, req); err != nil {
		return nil, err
	}

	var resp changeInfo
	path := fmt.Sprintf("/changes/%d", req.Number)

	params := url.Values{}
	for _, o := range req.Options {
		params.Add("o", o.String())
	}
	if _, err := c.call(ctx, "GET", path, params, nil, &resp); err != nil {
		return nil, err
	}

	return &gerritpb.ChangeInfo{
		Number:  resp.Number,
		Owner:   resp.Owner,
		Project: resp.Project,
	}, nil
}

// call executes a request to Gerrit REST API with JSON input/output.
//
// call returns HTTP status code and gRPC error.
// If error happens before HTTP status code was determined, HTTP status code
// will be -1.
func (c *client) call(ctx context.Context, method, urlPath string, params url.Values, data, dest interface{}, expectedHTTPCodes ...int) (int, error) {
	url := c.BaseURL + urlPath
	if len(params) > 0 {
		url += "?" + params.Encode()
	}

	var buffer bytes.Buffer
	req, err := http.NewRequest(method, url, &buffer)
	if err != nil {
		return 0, status.Errorf(codes.Internal, "failed to create an HTTP request: %s", err)
	}
	if data != nil {
		req.Header.Set("Content-Type", contentType)
		if err := json.NewEncoder(&buffer).Encode(data); err != nil {
			return -1, status.Errorf(codes.Internal, "failed to serialize request message: %s", err)
		}
	}

	res, err := ctxhttp.Do(ctx, c.Client, req)
	if err != nil {
		return -1, status.Errorf(codes.Internal, "failed to execute Post HTTP request: %s", err)
	}
	defer res.Body.Close()

	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return res.StatusCode, status.Errorf(codes.Internal, "failed to read response: %s", err)
	}

	expectedHTTPCodes = append(expectedHTTPCodes, http.StatusOK)
	for _, s := range expectedHTTPCodes {
		if res.StatusCode == s {
			body = bytes.TrimPrefix(body, jsonPrefix)
			if err = json.Unmarshal(body, dest); err != nil {
				return res.StatusCode, status.Errorf(codes.Internal, "failed to desirealize response: %s", err)
			}
			return res.StatusCode, nil
		}
	}

	switch res.StatusCode {
	case http.StatusTooManyRequests:
		logging.Errorf(ctx, "Gerrit quota error.\nResponse headers: %v\nResponse body: %s",
			res.Header, body)
		return res.StatusCode, status.Errorf(codes.ResourceExhausted, "insufficient Gerrit quota")

	case http.StatusForbidden:
		return res.StatusCode, status.Errorf(codes.PermissionDenied, "permission denied")

	case http.StatusNotFound:
		return res.StatusCode, status.Errorf(codes.NotFound, "not found")

	default:
		logging.Errorf(ctx, "gerrit: unexpected HTTP %d response.\nResponse headers: %v\nResponse body: %s",
			res.StatusCode,
			res.Header, body)
		return res.StatusCode, status.Errorf(codes.Internal, "unexpected HTTP %d from Gerrit", res.StatusCode)
	}
}

type validatable interface {
	Validate() error
}

func checkArgs(opts []grpc.CallOption, req validatable) error {
	if len(opts) > 0 {
		return errors.New("gerrit.client does not support grpc options")
	}
	if err := req.Validate(); err != nil {
		return errors.Annotate(err, "request is invalid").Err()
	}
	return nil
}
<commit_msg>gerrit: Add license header<commit_after>// Copyright 2018 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package gerrit

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"

	"golang.org/x/net/context/ctxhttp"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	"go.chromium.org/luci/common/errors"
	"go.chromium.org/luci/common/logging"
	gerritpb "go.chromium.org/luci/common/proto/gerrit"
)

// OAuthScope is the OAuth 2.0 scope that must be included when acquiring an
// access token for Gerrit RPCs.
const OAuthScope = "https://www.googleapis.com/auth/gerritcodereview"

// This file implements Gerrit proto service client
// on top of Gerrit REST API.

// NewRESTClient creates a new Gerrit client based on Gerrit's REST API.
//
// The host must be a full Gerrit host, e.g. "chromium-review.googlesource.com".
//
// If auth is true, indicates that the given HTTP client sends authenticated
// requests. If so, the requests to Gerrit will include "/a/" URL path
// prefix.
//
// RPC methods of the returned client return an error if a grpc.CallOption is
// passed.
func NewRESTClient(httpClient *http.Client, host string, auth bool) (gerritpb.GerritClient, error) {
	switch {
	case strings.Contains(host, "/"):
		return nil, errors.Reason("invalid host %q", host).Err()
	case !strings.HasSuffix(host, "-review.googlesource.com"):
		return nil, errors.New("Gerrit at googlesource subdomains end with '-review'")
	}

	baseURL := "https://" + host
	if auth {
		baseURL += "/a"
	}
	return &client{Client: httpClient, BaseURL: baseURL}, nil
}

// Implementation.

var jsonPrefix = []byte(")]}'")

// client implements gerritpb.GerritClient.
type client struct {
	Client *http.Client
	// BaseURL is the base URL for all API requests,
	// for example "https://chromium-review.googlesource.com/a".
	BaseURL string
}

// changeInfo is JSON representation of gerritpb.ChangeInfo on the wire.
type changeInfo struct {
	Number  int64                 `json:"_number"`
	Owner   *gerritpb.AccountInfo `json:"owner"`
	Project string                `json:"project"`
}

func (c *client) GetChange(ctx context.Context, req *gerritpb.GetChangeRequest, opts ...grpc.CallOption) (
	*gerritpb.ChangeInfo, error) {

	if err := checkArgs(opts, req); err != nil {
		return nil, err
	}

	var resp changeInfo
	path := fmt.Sprintf("/changes/%d", req.Number)

	params := url.Values{}
	for _, o := range req.Options {
		params.Add("o", o.String())
	}
	if _, err := c.call(ctx, "GET", path, params, nil, &resp); err != nil {
		return nil, err
	}

	return &gerritpb.ChangeInfo{
		Number:  resp.Number,
		Owner:   resp.Owner,
		Project: resp.Project,
	}, nil
}

// call executes a request to Gerrit REST API with JSON input/output.
//
// call returns HTTP status code and gRPC error.
// If error happens before HTTP status code was determined, HTTP status code
// will be -1.
func (c *client) call(ctx context.Context, method, urlPath string, params url.Values, data, dest interface{}, expectedHTTPCodes ...int) (int, error) {
	url := c.BaseURL + urlPath
	if len(params) > 0 {
		url += "?" + params.Encode()
	}

	var buffer bytes.Buffer
	req, err := http.NewRequest(method, url, &buffer)
	if err != nil {
		return 0, status.Errorf(codes.Internal, "failed to create an HTTP request: %s", err)
	}
	if data != nil {
		req.Header.Set("Content-Type", contentType)
		if err := json.NewEncoder(&buffer).Encode(data); err != nil {
			return -1, status.Errorf(codes.Internal, "failed to serialize request message: %s", err)
		}
	}

	res, err := ctxhttp.Do(ctx, c.Client, req)
	if err != nil {
		return -1, status.Errorf(codes.Internal, "failed to execute Post HTTP request: %s", err)
	}
	defer res.Body.Close()

	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return res.StatusCode, status.Errorf(codes.Internal, "failed to read response: %s", err)
	}

	expectedHTTPCodes = append(expectedHTTPCodes, http.StatusOK)
	for _, s := range expectedHTTPCodes {
		if res.StatusCode == s {
			body = bytes.TrimPrefix(body, jsonPrefix)
			if err = json.Unmarshal(body, dest); err != nil {
				return res.StatusCode, status.Errorf(codes.Internal, "failed to desirealize response: %s", err)
			}
			return res.StatusCode, nil
		}
	}

	switch res.StatusCode {
	case http.StatusTooManyRequests:
		logging.Errorf(ctx, "Gerrit quota error.\nResponse headers: %v\nResponse body: %s",
			res.Header, body)
		return res.StatusCode, status.Errorf(codes.ResourceExhausted, "insufficient Gerrit quota")

	case http.StatusForbidden:
		return res.StatusCode, status.Errorf(codes.PermissionDenied, "permission denied")

	case http.StatusNotFound:
		return res.StatusCode, status.Errorf(codes.NotFound, "not found")

	default:
		logging.Errorf(ctx, "gerrit: unexpected HTTP %d response.\nResponse headers: %v\nResponse body: %s",
			res.StatusCode,
			res.Header, body)
		return res.StatusCode, status.Errorf(codes.Internal, "unexpected HTTP %d from Gerrit", res.StatusCode)
	}
}

type validatable interface {
	Validate() error
}

func checkArgs(opts []grpc.CallOption, req validatable) error {
	if len(opts) > 0 {
		return errors.New("gerrit.client does not support grpc options")
	}
	if err := req.Validate(); err != nil {
		return errors.Annotate(err, "request is invalid").Err()
	}
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2014, Truveris Inc. All Rights Reserved.
// Use of this source code is governed by the ISC license in the LICENSE file.
//
// The io_irc_http is a temporary hack to expose ygor functions to the
// interwebs. This is bound to be replaced by an external process communicating
// with ygord via the SQS "API".
//

package main

import (
	"encoding/base64"
	"errors"
	"fmt"
	"log"
	"net/http"
	"strings"
	"regexp"
)

func aliasesHandler(w http.ResponseWriter, r *http.Request) {
	user, err := auth(r)
	if err != nil {
		log.Printf("Authentication failed: %s", err.Error())
		errorHandler(w, "Authentication failed")
		return
	}

	aliases, err := Aliases.All()
	if err != nil {
		http.Error(w, "error: "+err.Error(), 500)
		return
	}

	fmt.Fprintf(w, `

			<title>ygor - aliases</title>

%s@ygor/aliases

						Name
						Value

	`, user)

	re := regexp.MustCompile("(https?://(?:(?:[:&=+$,a-zA-Z0-9_-]+@)?[a-zA-Z0-9.-]+)(?:/[,:!+=~%/.a-zA-Z0-9_-]*)?\\??(?:[,:!+=&%@.a-zA-Z0-9_-]*))")

	for _, alias := range aliases {

		value := re.ReplaceAll([]byte(alias.Value), []byte("$1"))

		fmt.Fprintf(w, `

			%s
			%s

`, alias.Name, value)
	}

	fmt.Fprintf(w, `

	`)
}

func minionsHandler(w http.ResponseWriter, r *http.Request) {
	user, err := auth(r)
	if err != nil {
		log.Printf("Authentication failed: %s", err.Error())
		errorHandler(w, "Authentication failed")
		return
	}

	minions, err := Minions.All()
	if err != nil {
		http.Error(w, "error: "+err.Error(), 500)
		return
	}

	fmt.Fprintf(w, `

			<title>ygor - minions</title>

%s@ygor/minions

						Name
						Last registration

	`, user)

	for _, minion := range minions {
		fmt.Fprintf(w, `

			%s
			%s

`, minion.Name, minion.LastSeen)
	}

	fmt.Fprintf(w, `

	`)
}

// Given a Basic Authorization header value, return the user and password.
func parseBasicAuth(value string) (string, string, error) {
	log.Printf("parseBasicAuth: %s", value)
	authorization, err := base64.StdEncoding.DecodeString(value)
	if err != nil {
		return "", "", err
	}

	tokens := strings.SplitN(string(authorization), ":", 2)
	if len(tokens) != 2 {
		return "", "", errors.New("Unable to split Basic Auth.")
	}

	return tokens[0], tokens[1], nil
}

// Makes sure we have a Basic auth user. We don't check the password, we assume
// this HTTP server sits behind a proxy which enforces that aspect.
func auth(r *http.Request) (string, error) {
	var user string
	var err error

	auth, ok := r.Header["Authorization"]
	if ok {
		if len(auth) > 0 {
			if ! strings.HasPrefix(auth[0], "Basic ") {
				return "", errors.New("Unsupported auth type")
			}
			value := strings.TrimPrefix(auth[0], "Basic ")
			user, _, err = parseBasicAuth(value)
			if err != nil {
				return "", err
			}
		}
	}

	return user, nil
}

func errorHandler(w http.ResponseWriter, msg string) {
	fmt.Fprintf(w, `

	<title>ygor: Error</title>

Error: %s

	`, msg)
}

func homeHandler(w http.ResponseWriter, r *http.Request) {
	user, err := auth(r)
	if err != nil {
		log.Printf("Authentication failed: %s", err.Error())
		errorHandler(w, "Authentication failed")
		return
	}

	fmt.Fprintf(w, `

		<title>ygor</title>

%s@ygor

aliases

minions

	`, user)
}

func HTTPServer(address string) {
	log.Printf("starting http server on %s", address)
	http.HandleFunc("/", homeHandler)
	http.HandleFunc("/aliases", aliasesHandler)
	http.HandleFunc("/minions", minionsHandler)
	http.ListenAndServe(address, nil)
}

func StartHTTPAdapter() error {
	if cfg.HTTPServerAddress != "" {
		go HTTPServer(cfg.HTTPServerAddress)
	}
	return nil
}
<commit_msg>Ports and parens now supported for URL matching<commit_after>// Copyright 2014, Truveris Inc. All Rights Reserved.
// Use of this source code is governed by the ISC license in the LICENSE file.
//
// The io_irc_http is a temporary hack to expose ygor functions to the
// interwebs. This is bound to be replaced by an external process communicating
// with ygord via the SQS "API".
//

package main

import (
	"encoding/base64"
	"errors"
	"fmt"
	"log"
	"net/http"
	"strings"
	"regexp"
)

func aliasesHandler(w http.ResponseWriter, r *http.Request) {
	user, err := auth(r)
	if err != nil {
		log.Printf("Authentication failed: %s", err.Error())
		errorHandler(w, "Authentication failed")
		return
	}

	aliases, err := Aliases.All()
	if err != nil {
		http.Error(w, "error: "+err.Error(), 500)
		return
	}

	fmt.Fprintf(w, `

			<title>ygor - aliases</title>

%s@ygor/aliases

						Name
						Value

	`, user)

	re := regexp.MustCompile("(https?://(?:(?:[:&=+$,a-zA-Z0-9_-]+@)?[a-zA-Z0-9.-]+(?::[0-9])?)(?:/[,:!+=~%/.a-zA-Z0-9_()-]*)?\\??(?:[,:!+=&%@.a-zA-Z0-9_()-]*))")

	for _, alias := range aliases {

		value := re.ReplaceAll([]byte(alias.Value), []byte("$1"))

		fmt.Fprintf(w, `

			%s
			%s

`, alias.Name, value)
	}

	fmt.Fprintf(w, `

	`)
}

func minionsHandler(w http.ResponseWriter, r *http.Request) {
	user, err := auth(r)
	if err != nil {
		log.Printf("Authentication failed: %s", err.Error())
		errorHandler(w, "Authentication failed")
		return
	}

	minions, err := Minions.All()
	if err != nil {
		http.Error(w, "error: "+err.Error(), 500)
		return
	}

	fmt.Fprintf(w, `

			<title>ygor - minions</title>

%s@ygor/minions

						Name
						Last registration

	`, user)

	for _, minion := range minions {
		fmt.Fprintf(w, `

			%s
			%s

`, minion.Name, minion.LastSeen)
	}

	fmt.Fprintf(w, `

	`)
}

// Given a Basic Authorization header value, return the user and password.
func parseBasicAuth(value string) (string, string, error) {
	log.Printf("parseBasicAuth: %s", value)
	authorization, err := base64.StdEncoding.DecodeString(value)
	if err != nil {
		return "", "", err
	}

	tokens := strings.SplitN(string(authorization), ":", 2)
	if len(tokens) != 2 {
		return "", "", errors.New("Unable to split Basic Auth.")
	}

	return tokens[0], tokens[1], nil
}

// Makes sure we have a Basic auth user. We don't check the password, we assume
// this HTTP server sits behind a proxy which enforces that aspect.
func auth(r *http.Request) (string, error) {
	var user string
	var err error

	auth, ok := r.Header["Authorization"]
	if ok {
		if len(auth) > 0 {
			if ! strings.HasPrefix(auth[0], "Basic ") {
				return "", errors.New("Unsupported auth type")
			}
			value := strings.TrimPrefix(auth[0], "Basic ")
			user, _, err = parseBasicAuth(value)
			if err != nil {
				return "", err
			}
		}
	}

	return user, nil
}

func errorHandler(w http.ResponseWriter, msg string) {
	fmt.Fprintf(w, `

	<title>ygor: Error</title>

Error: %s

	`, msg)
}

func homeHandler(w http.ResponseWriter, r *http.Request) {
	user, err := auth(r)
	if err != nil {
		log.Printf("Authentication failed: %s", err.Error())
		errorHandler(w, "Authentication failed")
		return
	}

	fmt.Fprintf(w, `

		<title>ygor</title>

%s@ygor

aliases

minions

	`, user)
}

func HTTPServer(address string) {
	log.Printf("starting http server on %s", address)
	http.HandleFunc("/", homeHandler)
	http.HandleFunc("/aliases", aliasesHandler)
	http.HandleFunc("/minions", minionsHandler)
	http.ListenAndServe(address, nil)
}

func StartHTTPAdapter() error {
	if cfg.HTTPServerAddress != "" {
		go HTTPServer(cfg.HTTPServerAddress)
	}
	return nil
}
<|endoftext|>


<commit_before>package gps

import (
	"bytes"
	"fmt"
	"io"
	"strings"
	"testing"
)

func AssertScanner_ReadFrom_N(got, want int64) error {
	var (
		format string = "Scanner.RedFrom, n output: Got: n='%d', Want: n='%d'"
	)
	if want != got {
		return fmt.Errorf(format, got, want)
	}
	return nil
}

func AssertScanner_ReadFrom_Error(got, want error) error {
	var (
		gStr   string
		wStr   string
		format string = "Scanner.RedFrom, error output: Got err='%s', Want: err='%s'"
	)
	gStr = fmt.Sprintf("%s", got)
	wStr = fmt.Sprintf("%s", want)
	if strings.EqualFold(wStr, gStr) == false {
		return fmt.Errorf(format, got, want)
	}
	return nil
}

func AssertScanner_ReadFrom_StructFieldIoReader(got, want io.Reader) error {
	format := "%s '%#v', Want struct field: err='%#v'"
	s1 := "Scanner.RedFrom, comparing io.Readers: Got struct field '"

	gStr := fmt.Sprintf("%#v", got)
	wStr := fmt.Sprintf("%#v", want)

	if strings.Compare(wStr, gStr) != 0 {
		return fmt.Errorf(format, s1, got, want)
	}
	return nil
}

func AssertScanner_ReadFrom_StructFieldC(got, want rune) error {
	panic("FIXME Llaves færdig")
	return nil
}

func TestScanner_ReadFromNilIoReader(t *testing.T) {
	var (
		s      *Scanner = NewScanner(&gst_testing)
		gotN   int64
		gotErr error
		r      io.Reader = io.Reader(nil)
		err    error
	)
	gotN, gotErr = s.ReadFrom(r)
	if err = AssertScanner_ReadFrom_N(gotN, 0); err != nil {
		t.Error(err.Error())
	}
	if err = AssertScanner_ReadFrom_Error(gotErr, ErrIoNilReader); err != nil {
		t.Error(err.Error())
	}
}

func TestScanner_ReadFromStructFields(t *testing.T) {
	var (
		s                *Scanner
		buf_struct_field bytes.Buffer
		buf_ioReader_arg bytes.Buffer
		r                io.Reader
		err              error
	)

	s = NewScanner(&gst_testing)

	buf_struct_field.WriteString(" ")
	s.ioReader = io.Reader(&buf_struct_field)

	buf_ioReader_arg.WriteString("\"class\":\"TPV\"}")
	r = io.Reader(&buf_ioReader_arg)

	_, _ = s.ReadFrom(r)

	if err = AssertScanner_ReadFrom_StructFieldIoReader(r, r); err != nil {
		t.Error(err.Error())
	}

	if err = AssertScanner_ReadFrom_StructFieldC(s.c, s.input[0]); err != nil {
		t.Error(err.Error())
	}
}
<commit_msg>golint-ed readfrom_test.go in package github.com/larsth/rmsgradiolinkctrld/gps<commit_after>package gps

import (
	"bytes"
	"fmt"
	"io"
	"strings"
	"testing"
)

func AssertScannerReadFromN(got, want int64) error {
	var (
		format = "Scanner.RedFrom, n output: Got: n='%d', Want: n='%d'"
	)
	if want != got {
		return fmt.Errorf(format, got, want)
	}
	return nil
}

func AssertScannerReadFromError(got, want error) error {
	var (
		gStr   string
		wStr   string
		format = "Scanner.RedFrom, error output: Got err='%s', Want: err='%s'"
	)
	gStr = fmt.Sprintf("%s", got)
	wStr = fmt.Sprintf("%s", want)
	if strings.EqualFold(wStr, gStr) == false {
		return fmt.Errorf(format, got, want)
	}
	return nil
}

func AssertScannerReadFromStructFieldIoReader(got, want io.Reader) error {
	format := "%s '%#v', Want struct field: err='%#v'"
	s1 := "Scanner.RedFrom, comparing io.Readers: Got struct field '"

	gStr := fmt.Sprintf("%#v", got)
	wStr := fmt.Sprintf("%#v", want)

	if strings.Compare(wStr, gStr) != 0 {
		return fmt.Errorf(format, s1, got, want)
	}
	return nil
}

func AssertScannerReadFromStructFieldC(got, want rune) error {
	panic("FIXME Llaves færdig")
	return nil
}

func TestScannerReadFromNilIoReader(t *testing.T) {
	var (
		s      = NewScanner(&gst_testing)
		gotN   int64
		gotErr error
		r      = io.Reader(nil)
		err    error
	)
	gotN, gotErr = s.ReadFrom(r)
	if err = AssertScannerReadFromN(gotN, 0); err != nil {
		t.Error(err.Error())
	}
	if err = AssertScannerReadFromError(gotErr, ErrIoNilReader); err != nil {
		t.Error(err.Error())
	}
}

func TestScannerReadFromStructFields(t *testing.T) {
	var (
		s              *Scanner
		bufStructField bytes.Buffer
		bufIoReaderArg bytes.Buffer
		r              io.Reader
		err            error
	)

	s = NewScanner(&gst_testing)

	bufStructField.WriteString(" ")
	s.ioReader = io.Reader(&bufStructField)

	bufIoReaderArg.WriteString("\"class\":\"TPV\"}")
	r = io.Reader(&bufIoReaderArg)

	_, _ = s.ReadFrom(r)

	if err = AssertScannerReadFromStructFieldIoReader(r, r); err != nil {
		t.Error(err.Error())
	}

	if err = AssertScannerReadFromStructFieldC(s.c, s.input[0]); err != nil {
		t.Error(err.Error())
	}
}
<|endoftext|>


<commit_before>package client

import (
	"context"
	"encoding/json"
	"fmt"

	"github.com/docker/cli/cli/manifest/types"
	"github.com/docker/distribution"
	"github.com/docker/distribution/manifest/manifestlist"
	"github.com/docker/distribution/manifest/schema2"
	"github.com/docker/distribution/reference"
	"github.com/docker/distribution/registry/api/errcode"
	"github.com/docker/distribution/registry/api/v2"
	distclient "github.com/docker/distribution/registry/client"
	"github.com/docker/docker/registry"
	digest "github.com/opencontainers/go-digest"
	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

// fetchManifest pulls a manifest from a registry and returns it. An error
// is returned if no manifest is found matching namedRef.
func fetchManifest(ctx context.Context, repo distribution.Repository, ref reference.Named) (types.ImageManifest, error) {
	manifest, err := getManifest(ctx, repo, ref)
	if err != nil {
		return types.ImageManifest{}, err
	}

	switch v := manifest.(type) {
	// Removed Schema 1 support
	case *schema2.DeserializedManifest:
		imageManifest, err := pullManifestSchemaV2(ctx, ref, repo, *v)
		if err != nil {
			return types.ImageManifest{}, err
		}
		return imageManifest, nil
	case *manifestlist.DeserializedManifestList:
		return types.ImageManifest{}, errors.Errorf("%s is a manifest list", ref)
	}
	return types.ImageManifest{}, errors.Errorf("%s is not a manifest", ref)
}

func fetchList(ctx context.Context, repo distribution.Repository, ref reference.Named) ([]types.ImageManifest, error) {
	manifest, err := getManifest(ctx, repo, ref)
	if err != nil {
		return nil, err
	}

	switch v := manifest.(type) {
	case *manifestlist.DeserializedManifestList:
		imageManifests, err := pullManifestList(ctx, ref, repo, *v)
		if err != nil {
			return nil, err
		}
		return imageManifests, nil
	default:
		return nil, errors.Errorf("unsupported manifest format: %v", v)
	}
}

func getManifest(ctx context.Context, repo distribution.Repository, ref reference.Named) (distribution.Manifest, error) {
	manSvc, err := repo.Manifests(ctx)
	if err != nil {
		return nil, err
	}

	dgst, opts, err := getManifestOptionsFromReference(ref)
	if err != nil {
		return nil, errors.Errorf("image manifest for %q does not exist", ref)
	}
	return manSvc.Get(ctx, dgst, opts...)
}

func pullManifestSchemaV2(ctx context.Context, ref reference.Named, repo distribution.Repository, mfst schema2.DeserializedManifest) (types.ImageManifest, error) {
	manifestDesc, err := validateManifestDigest(ref, mfst)
	if err != nil {
		return types.ImageManifest{}, err
	}
	configJSON, err := pullManifestSchemaV2ImageConfig(ctx, mfst.Target().Digest, repo)
	if err != nil {
		return types.ImageManifest{}, err
	}

	if manifestDesc.Platform == nil {
		manifestDesc.Platform = &ocispec.Platform{}
	}

	// Fill in os and architecture fields from config JSON
	if err := json.Unmarshal(configJSON, manifestDesc.Platform); err != nil {
		return types.ImageManifest{}, err
	}

	return types.NewImageManifest(ref, manifestDesc, &mfst), nil
}

func pullManifestSchemaV2ImageConfig(ctx context.Context, dgst digest.Digest, repo distribution.Repository) ([]byte, error) {
	blobs := repo.Blobs(ctx)
	configJSON, err := blobs.Get(ctx, dgst)
	if err != nil {
		return nil, err
	}

	verifier := dgst.Verifier()
	if err != nil {
		return nil, err
	}
	if _, err := verifier.Write(configJSON); err != nil {
		return nil, err
	}
	if !verifier.Verified() {
		return nil, errors.Errorf("image config verification failed for digest %s", dgst)
	}
	return configJSON, nil
}

// validateManifestDigest computes the manifest digest, and, if pulling by
// digest, ensures that it matches the requested digest.
func validateManifestDigest(ref reference.Named, mfst distribution.Manifest) (ocispec.Descriptor, error) {
	mediaType, canonical, err := mfst.Payload()
	if err != nil {
		return ocispec.Descriptor{}, err
	}
	desc := ocispec.Descriptor{
		Digest:    digest.FromBytes(canonical),
		Size:      int64(len(canonical)),
		MediaType: mediaType,
	}

	// If pull by digest, then verify the manifest digest.
	if digested, isDigested := ref.(reference.Canonical); isDigested {
		if digested.Digest() != desc.Digest {
			err := fmt.Errorf("manifest verification failed for digest %s", digested.Digest())
			return ocispec.Descriptor{}, err
		}
	}

	return desc, nil
}

// pullManifestList handles "manifest lists" which point to various
// platform-specific manifests.
func pullManifestList(ctx context.Context, ref reference.Named, repo distribution.Repository, mfstList manifestlist.DeserializedManifestList) ([]types.ImageManifest, error) {
	infos := []types.ImageManifest{}

	if _, err := validateManifestDigest(ref, mfstList); err != nil {
		return nil, err
	}

	for _, manifestDescriptor := range mfstList.Manifests {
		manSvc, err := repo.Manifests(ctx)
		if err != nil {
			return nil, err
		}
		manifest, err := manSvc.Get(ctx, manifestDescriptor.Digest)
		if err != nil {
			return nil, err
		}
		v, ok := manifest.(*schema2.DeserializedManifest)
		if !ok {
			return nil, fmt.Errorf("unsupported manifest format: %v", v)
		}

		manifestRef, err := reference.WithDigest(ref, manifestDescriptor.Digest)
		if err != nil {
			return nil, err
		}
		imageManifest, err := pullManifestSchemaV2(ctx, manifestRef, repo, *v)
		if err != nil {
			return nil, err
		}

		// Replace platform from config
		imageManifest.Descriptor.Platform = types.OCIPlatform(&manifestDescriptor.Platform)

		infos = append(infos, imageManifest)
	}
	return infos, nil
}

func continueOnError(err error) bool {
	switch v := err.(type) {
	case errcode.Errors:
		if len(v) == 0 {
			return true
		}
		return continueOnError(v[0])
	case errcode.Error:
		e := err.(errcode.Error)
		switch e.Code {
		case errcode.ErrorCodeUnauthorized, v2.ErrorCodeManifestUnknown, v2.ErrorCodeNameUnknown:
			return true
		}
		return false
	case *distclient.UnexpectedHTTPResponseError:
		return true
	}
	return false
}

func (c *client) iterateEndpoints(ctx context.Context, namedRef reference.Named, each func(context.Context, distribution.Repository, reference.Named) (bool, error)) error {
	endpoints, err := allEndpoints(namedRef, c.insecureRegistry)
	if err != nil {
		return err
	}

	repoInfo, err := registry.ParseRepositoryInfo(namedRef)
	if err != nil {
		return err
	}

	confirmedTLSRegistries := make(map[string]bool)
	for _, endpoint := range endpoints {

		if endpoint.Version == registry.APIVersion1 {
			logrus.Debugf("skipping v1 endpoint %s", endpoint.URL)
			continue
		}

		if endpoint.URL.Scheme != "https" {
			if _, confirmedTLS := confirmedTLSRegistries[endpoint.URL.Host]; confirmedTLS {
				logrus.Debugf("skipping non-TLS endpoint %s for host/port that appears to use TLS", endpoint.URL)
				continue
			}
		}

		if c.insecureRegistry {
			endpoint.TLSConfig.InsecureSkipVerify = true
		}
		repoEndpoint := repositoryEndpoint{endpoint: endpoint, info: repoInfo}
		repo, err := c.getRepositoryForReference(ctx, namedRef, repoEndpoint)
		if err != nil {
			logrus.Debugf("error %s with repo endpoint %+v", err, repoEndpoint)
			if _, ok := err.(ErrHTTPProto); ok {
				continue
			}
			return err
		}

		if endpoint.URL.Scheme == "http" && !c.insecureRegistry {
			logrus.Debugf("skipping non-tls registry endpoint: %s", endpoint.URL)
			continue
		}
		done, err := each(ctx, repo, namedRef)
		if err != nil {
			if continueOnError(err) {
				if endpoint.URL.Scheme == "https" {
					confirmedTLSRegistries[endpoint.URL.Host] = true
				}
				logrus.Debugf("continuing on error (%T) %s", err, err)
				continue
			}
			logrus.Debugf("not continuing on error (%T) %s", err, err)
			return err
		}
		if done {
			return nil
		}
	}
	return newNotFoundError(namedRef.String())
}

// allEndpoints returns a list of endpoints ordered by priority (v2, https, v1).
func allEndpoints(namedRef reference.Named, insecure bool) ([]registry.APIEndpoint, error) {
	repoInfo, err := registry.ParseRepositoryInfo(namedRef)
	if err != nil {
		return nil, err
	}

	var serviceOpts registry.ServiceOptions
	if insecure {
		logrus.Debugf("allowing insecure registry for: %s", reference.Domain(namedRef))
		serviceOpts.InsecureRegistries = []string{reference.Domain(namedRef)}
	}
	registryService, err := registry.NewService(serviceOpts)
	if err != nil {
		return []registry.APIEndpoint{}, err
	}
	endpoints, err := registryService.LookupPullEndpoints(reference.Domain(repoInfo.Name))
	logrus.Debugf("endpoints for %s: %v", namedRef, endpoints)
	return endpoints, err
}

type notFoundError struct {
	object string
}

func newNotFoundError(ref string) *notFoundError {
	return &notFoundError{object: ref}
}

func (n *notFoundError) Error() string {
	return fmt.Sprintf("no such manifest: %s", n.object)
}

// NotFound interface
func (n *notFoundError) NotFound() {}

// IsNotFound returns true if the error is a not found error
func IsNotFound(err error) bool {
	_, ok := err.(notFound)
	return ok
}

type notFound interface {
	NotFound()
}
<commit_msg>cli/registry/client/fetcher.go:106:9: nilness: impossible condition: nil != nil (govet)<commit_after>package client

import (
	"context"
	"encoding/json"
	"fmt"

	"github.com/docker/cli/cli/manifest/types"
	"github.com/docker/distribution"
	"github.com/docker/distribution/manifest/manifestlist"
	"github.com/docker/distribution/manifest/schema2"
	"github.com/docker/distribution/reference"
	"github.com/docker/distribution/registry/api/errcode"
	v2 "github.com/docker/distribution/registry/api/v2"
	distclient "github.com/docker/distribution/registry/client"
	"github.com/docker/docker/registry"
	digest "github.com/opencontainers/go-digest"
	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

// fetchManifest pulls a manifest from a registry and returns it. An error
// is returned if no manifest is found matching namedRef.
func fetchManifest(ctx context.Context, repo distribution.Repository, ref reference.Named) (types.ImageManifest, error) {
	manifest, err := getManifest(ctx, repo, ref)
	if err != nil {
		return types.ImageManifest{}, err
	}

	switch v := manifest.(type) {
	// Removed Schema 1 support
	case *schema2.DeserializedManifest:
		imageManifest, err := pullManifestSchemaV2(ctx, ref, repo, *v)
		if err != nil {
			return types.ImageManifest{}, err
		}
		return imageManifest, nil
	case *manifestlist.DeserializedManifestList:
		return types.ImageManifest{}, errors.Errorf("%s is a manifest list", ref)
	}
	return types.ImageManifest{}, errors.Errorf("%s is not a manifest", ref)
}

func fetchList(ctx context.Context, repo distribution.Repository, ref reference.Named) ([]types.ImageManifest, error) {
	manifest, err := getManifest(ctx, repo, ref)
	if err != nil {
		return nil, err
	}

	switch v := manifest.(type) {
	case *manifestlist.DeserializedManifestList:
		imageManifests, err := pullManifestList(ctx, ref, repo, *v)
		if err != nil {
			return nil, err
		}
		return imageManifests, nil
	default:
		return nil, errors.Errorf("unsupported manifest format: %v", v)
	}
}

func getManifest(ctx context.Context, repo distribution.Repository, ref reference.Named) (distribution.Manifest, error) {
	manSvc, err := repo.Manifests(ctx)
	if err != nil {
		return nil, err
	}

	dgst, opts, err := getManifestOptionsFromReference(ref)
	if err != nil {
		return nil, errors.Errorf("image manifest for %q does not exist", ref)
	}
	return manSvc.Get(ctx, dgst, opts...)
}

func pullManifestSchemaV2(ctx context.Context, ref reference.Named, repo distribution.Repository, mfst schema2.DeserializedManifest) (types.ImageManifest, error) {
	manifestDesc, err := validateManifestDigest(ref, mfst)
	if err != nil {
		return types.ImageManifest{}, err
	}
	configJSON, err := pullManifestSchemaV2ImageConfig(ctx, mfst.Target().Digest, repo)
	if err != nil {
		return types.ImageManifest{}, err
	}

	if manifestDesc.Platform == nil {
		manifestDesc.Platform = &ocispec.Platform{}
	}

	// Fill in os and architecture fields from config JSON
	if err := json.Unmarshal(configJSON, manifestDesc.Platform); err != nil {
		return types.ImageManifest{}, err
	}

	return types.NewImageManifest(ref, manifestDesc, &mfst), nil
}

func pullManifestSchemaV2ImageConfig(ctx context.Context, dgst digest.Digest, repo distribution.Repository) ([]byte, error) {
	blobs := repo.Blobs(ctx)
	configJSON, err := blobs.Get(ctx, dgst)
	if err != nil {
		return nil, err
	}

	verifier := dgst.Verifier()
	if _, err := verifier.Write(configJSON); err != nil {
		return nil, err
	}
	if !verifier.Verified() {
		return nil, errors.Errorf("image config verification failed for digest %s", dgst)
	}
	return configJSON, nil
}

// validateManifestDigest computes the manifest digest, and, if pulling by
// digest, ensures that it matches the requested digest.
func validateManifestDigest(ref reference.Named, mfst distribution.Manifest) (ocispec.Descriptor, error) {
	mediaType, canonical, err := mfst.Payload()
	if err != nil {
		return ocispec.Descriptor{}, err
	}
	desc := ocispec.Descriptor{
		Digest:    digest.FromBytes(canonical),
		Size:      int64(len(canonical)),
		MediaType: mediaType,
	}

	// If pull by digest, then verify the manifest digest.
	if digested, isDigested := ref.(reference.Canonical); isDigested {
		if digested.Digest() != desc.Digest {
			err := fmt.Errorf("manifest verification failed for digest %s", digested.Digest())
			return ocispec.Descriptor{}, err
		}
	}

	return desc, nil
}

// pullManifestList handles "manifest lists" which point to various
// platform-specific manifests.
func pullManifestList(ctx context.Context, ref reference.Named, repo distribution.Repository, mfstList manifestlist.DeserializedManifestList) ([]types.ImageManifest, error) {
	infos := []types.ImageManifest{}

	if _, err := validateManifestDigest(ref, mfstList); err != nil {
		return nil, err
	}

	for _, manifestDescriptor := range mfstList.Manifests {
		manSvc, err := repo.Manifests(ctx)
		if err != nil {
			return nil, err
		}
		manifest, err := manSvc.Get(ctx, manifestDescriptor.Digest)
		if err != nil {
			return nil, err
		}
		v, ok := manifest.(*schema2.DeserializedManifest)
		if !ok {
			return nil, fmt.Errorf("unsupported manifest format: %v", v)
		}

		manifestRef, err := reference.WithDigest(ref, manifestDescriptor.Digest)
		if err != nil {
			return nil, err
		}
		imageManifest, err := pullManifestSchemaV2(ctx, manifestRef, repo, *v)
		if err != nil {
			return nil, err
		}

		// Replace platform from config
		imageManifest.Descriptor.Platform = types.OCIPlatform(&manifestDescriptor.Platform)

		infos = append(infos, imageManifest)
	}
	return infos, nil
}

func continueOnError(err error) bool {
	switch v := err.(type) {
	case errcode.Errors:
		if len(v) == 0 {
			return true
		}
		return continueOnError(v[0])
	case errcode.Error:
		e := err.(errcode.Error)
		switch e.Code {
		case errcode.ErrorCodeUnauthorized, v2.ErrorCodeManifestUnknown, v2.ErrorCodeNameUnknown:
			return true
		}
		return false
	case *distclient.UnexpectedHTTPResponseError:
		return true
	}
	return false
}

func (c *client) iterateEndpoints(ctx context.Context, namedRef reference.Named, each func(context.Context, distribution.Repository, reference.Named) (bool, error)) error {
	endpoints, err := allEndpoints(namedRef, c.insecureRegistry)
	if err != nil {
		return err
	}

	repoInfo, err := registry.ParseRepositoryInfo(namedRef)
	if err != nil {
		return err
	}

	confirmedTLSRegistries := make(map[string]bool)
	for _, endpoint := range endpoints {

		if endpoint.Version == registry.APIVersion1 {
			logrus.Debugf("skipping v1 endpoint %s", endpoint.URL)
			continue
		}

		if endpoint.URL.Scheme != "https" {
			if _, confirmedTLS := confirmedTLSRegistries[endpoint.URL.Host]; confirmedTLS {
				logrus.Debugf("skipping non-TLS endpoint %s for host/port that appears to use TLS", endpoint.URL)
				continue
			}
		}

		if c.insecureRegistry {
			endpoint.TLSConfig.InsecureSkipVerify = true
		}
		repoEndpoint := repositoryEndpoint{endpoint: endpoint, info: repoInfo}
		repo, err := c.getRepositoryForReference(ctx, namedRef, repoEndpoint)
		if err != nil {
			logrus.Debugf("error %s with repo endpoint %+v", err, repoEndpoint)
			if _, ok := err.(ErrHTTPProto); ok {
				continue
			}
			return err
		}

		if endpoint.URL.Scheme == "http" && !c.insecureRegistry {
			logrus.Debugf("skipping non-tls registry endpoint: %s", endpoint.URL)
			continue
		}
		done, err := each(ctx, repo, namedRef)
		if err != nil {
			if continueOnError(err) {
				if endpoint.URL.Scheme == "https" {
					confirmedTLSRegistries[endpoint.URL.Host] = true
				}
				logrus.Debugf("continuing on error (%T) %s", err, err)
				continue
			}
			logrus.Debugf("not continuing on error (%T) %s", err, err)
			return err
		}
		if done {
			return nil
		}
	}
	return newNotFoundError(namedRef.String())
}

// allEndpoints returns a list of endpoints ordered by priority (v2, https, v1).
func allEndpoints(namedRef reference.Named, insecure bool) ([]registry.APIEndpoint, error) {
	repoInfo, err := registry.ParseRepositoryInfo(namedRef)
	if err != nil {
		return nil, err
	}

	var serviceOpts registry.ServiceOptions
	if insecure {
		logrus.Debugf("allowing insecure registry for: %s", reference.Domain(namedRef))
		serviceOpts.InsecureRegistries = []string{reference.Domain(namedRef)}
	}
	registryService, err := registry.NewService(serviceOpts)
	if err != nil {
		return []registry.APIEndpoint{}, err
	}
	endpoints, err := registryService.LookupPullEndpoints(reference.Domain(repoInfo.Name))
	logrus.Debugf("endpoints for %s: %v", namedRef, endpoints)
	return endpoints, err
}

type notFoundError struct {
	object string
}

func newNotFoundError(ref string) *notFoundError {
	return &notFoundError{object: ref}
}

func (n *notFoundError) Error() string {
	return fmt.Sprintf("no such manifest: %s", n.object)
}

// NotFound interface
func (n *notFoundError) NotFound() {}

// IsNotFound returns true if the error is a not found error
func IsNotFound(err error) bool {
	_, ok := err.(notFound)
	return ok
}

type notFound interface {
	NotFound()
}
<|endoftext|>