<commit_before>package filemanager

import (
	"errors"
	"log"
	"net/http"
	"os"
	"os/exec"
	"regexp"
	"strings"

	rice "github.com/GeertJohan/go.rice"
	"github.com/asdine/storm"
	"github.com/mholt/caddy"
	"golang.org/x/net/webdav"
)

var (
	// ErrDuplicated occurs when you try to create a user that already exists.
	ErrDuplicated = errors.New("Duplicated user")
)

// FileManager is a file manager instance. It should be creating using the
// 'New' function and not directly.
type FileManager struct {
	// The BoltDB database for this instance.
	db *storm.DB

	// The key used to sign the JWT tokens.
	key []byte

	// The static assets.
	assets *rice.Box

	// PrefixURL is a part of the URL that is already trimmed from the request URL before it
	// arrives to our handlers. It may be useful when using File Manager as a middleware
	// such as in caddy-filemanager plugin. It is only useful in certain situations.
	PrefixURL string

	// BaseURL is the path where the GUI will be accessible. It musn't end with
	// a trailing slash and mustn't contain PrefixURL, if set. It shouldn't be
	// edited directly. Use SetBaseURL.
	BaseURL string

	// The Default User needed to build the New User page.
	DefaultUser *User

	// Users is a map with the different configurations for each user.
	Users map[string]*User

	// A map of events to a slice of commands.
	Commands map[string][]string

	// The plugins that have been plugged in.
	Plugins map[string]Plugin
}

// Command is a command function.
type Command func(r *http.Request, m *FileManager, u *User) error

// User contains the configuration for each user. It should be created
// using NewUser on a File Manager instance.
type User struct {
	// ID is the required primary key with auto increment0
	ID int `storm:"id,increment"`

	// Username is the user username used to login.
	Username string `json:"username" storm:"index,unique"`

	// The hashed password. This never reaches the front-end because it's temporarily
	// emptied during JSON marshall.
	Password string `json:"password"`

	// Tells if this user is an admin.
	Admin bool `json:"admin"`

	// FileSystem is the virtual file system the user has access.
	FileSystem webdav.Dir `json:"filesystem"`

	// Rules is an array of access and deny rules.
	Rules []*Rule `json:"rules"`

	// Custom styles for this user.
	CSS string `json:"css"`

	// These indicate if the user can perform certain actions.
	AllowNew      bool            `json:"allowNew"`      // Create files and folders
	AllowEdit     bool            `json:"allowEdit"`     // Edit/rename files
	AllowCommands bool            `json:"allowCommands"` // Execute commands
	Permissions   map[string]bool `json:"permissions"`   // Permissions added by plugins

	// Commands is the list of commands the user can execute.
	Commands []string `json:"commands"`
}

// Rule is a dissalow/allow rule.
type Rule struct {
	// Regex indicates if this rule uses Regular Expressions or not.
	Regex bool `json:"regex"`

	// Allow indicates if this is an allow rule. Set 'false' to be a disallow rule.
	Allow bool `json:"allow"`

	// Path is the corresponding URL path for this rule.
	Path string `json:"path"`

	// Regexp is the regular expression. Only use this when 'Regex' was set to true.
	Regexp *Regexp `json:"regexp"`
}

// Regexp is a regular expression wrapper around native regexp.
type Regexp struct {
	Raw    string `json:"raw"`
	regexp *regexp.Regexp
}

// Plugin is a File Manager plugin.
type Plugin interface {
	// The JavaScript that will be injected into the main page.
	JavaScript() string

	// If the Plugin returns (0, nil), the executation of File Manager will procced as usual.
	// Otherwise it will stop.
	BeforeAPI(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error)
	AfterAPI(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error)
}

// DefaultUser is used on New, when no 'base' user is provided.
var DefaultUser = User{
	AllowCommands: true,
	AllowEdit:     true,
	AllowNew:      true,
	Permissions:   map[string]bool{},
	Commands:      []string{},
	Rules:         []*Rule{},
	CSS:           "",
	Admin:         true,
	FileSystem:    webdav.Dir("."),
}

// New creates a new File Manager instance. If 'database' file already
// exists, it will load the users from there. Otherwise, a new user
// will be created using the 'base' variable. The 'base' User should
// not have the Password field hashed.
func New(database string, base User) (*FileManager, error) {
	// Creates a new File Manager instance with the Users
	// map and Assets box.
	m := &FileManager{
		Users:   map[string]*User{},
		assets:  rice.MustFindBox("./assets/dist"),
		Plugins: map[string]Plugin{},
	}

	// Tries to open a database on the location provided. This
	// function will automatically create a new one if it doesn't
	// exist.
	db, err := storm.Open(database)
	if err != nil {
		return nil, err
	}

	// Tries to get the encryption key from the database.
	// If it doesn't exist, create a new one of 256 bits.
	err = db.Get("config", "key", &m.key)
	if err != nil && err == storm.ErrNotFound {
		m.key = []byte(randomString(64))
		err = db.Set("config", "key", m.key)
	}

	if err != nil {
		return nil, err
	}

	// Tries to get the event commands from the database.
	// If they don't exist, initialize them.
	err = db.Get("config", "commands", &m.Commands)
	if err != nil && err == storm.ErrNotFound {
		m.Commands = map[string][]string{
			"before_save": {},
			"after_save":  {},
		}
		err = db.Set("config", "commands", m.Commands)
	}

	if err != nil {
		return nil, err
	}

	// Tries to fetch the users from the database and if there are
	// any, add them to the current File Manager instance.
	var users []User
	err = db.All(&users)
	if err != nil {
		return nil, err
	}

	for i := range users {
		m.Users[users[i].Username] = &users[i]
	}

	// If there are no users in the database, it creates a new one
	// based on 'base' User that must be provided by the function caller.
	if len(users) == 0 {
		u := base
		u.Username = "admin"

		// Hashes the password.
		u.Password, err = hashPassword("admin")
		if err != nil {
			return nil, err
		}

		// The first user must be an administrator.
		u.Admin = true
		u.AllowCommands = true
		u.AllowNew = true
		u.AllowEdit = true

		// Saves the user to the database.
		if err := db.Save(&u); err != nil {
			return nil, err
		}

		m.Users[u.Username] = &u
	}

	// Attaches db to this File Manager instance.
	m.db = db

	// Create the default user, making a copy of the base.
	m.DefaultUser = &base
	return m, nil
}

// RootURL returns the actual URL where
// File Manager interface can be accessed.
func (m FileManager) RootURL() string {
	return m.PrefixURL + m.BaseURL
}

// SetPrefixURL updates the prefixURL of a File
// Manager object.
func (m *FileManager) SetPrefixURL(url string) {
	url = strings.TrimPrefix(url, "/")
	url = strings.TrimSuffix(url, "/")
	url = "/" + url
	m.PrefixURL = strings.TrimSuffix(url, "/")
}

// SetBaseURL updates the baseURL of a File Manager
// object.
func (m *FileManager) SetBaseURL(url string) {
	url = strings.TrimPrefix(url, "/")
	url = strings.TrimSuffix(url, "/")
	url = "/" + url
	m.BaseURL = strings.TrimSuffix(url, "/")
}

// RegisterPlugin registers a plugin to a File Manager instance and
// loads its options from the database.
func (m *FileManager) RegisterPlugin(name string, plugin Plugin) error {
	if _, ok := m.Plugins[name]; ok {
		return errors.New("Plugin already registred")
	}

	err := m.db.Get("plugins", name, &plugin)
	if err != nil && err == storm.ErrNotFound {
		err = m.db.Set("plugins", name, plugin)
	}

	if err != nil {
		return err
	}

	m.Plugins[name] = plugin
	return nil
}

// RegisterEventType registers a new event type which can be triggered using Runner
// function.
func (m *FileManager) RegisterEventType(name string) error {
	if _, ok := m.Commands[name]; ok {
		return nil
	}

	m.Commands[name] = []string{}
	return m.db.Set("config", "commands", m.Commands)
}

// RegisterPermission registers a new user permission and adds it to every
// user with it default's 'value'. If the user is an admin, it will
// be true.
func (m *FileManager) RegisterPermission(name string, value bool) error {
	if _, ok := m.DefaultUser.Permissions[name]; ok {
		return nil
	}

	// Add the default value for this permission on the default user.
	m.DefaultUser.Permissions[name] = value

	for _, u := range m.Users {
		// Bypass the user if it is already defined.
		if _, ok := u.Permissions[name]; ok {
			continue
		}

		if u.Permissions == nil {
			u.Permissions = m.DefaultUser.Permissions
		}

		if u.Admin {
			u.Permissions[name] = true
		}

		err := m.db.Save(u)
		if err != nil {
			return err
		}
	}

	return nil
}

// ServeHTTP determines if the request is for this plugin, and if all prerequisites are met.
// Compatible with http.Handler.
func (m *FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	code, err := serveHTTP(&RequestContext{
		FM:   m,
		User: nil,
		FI:   nil,
	}, w, r)

	if code != 0 {
		w.WriteHeader(code)

		if err != nil {
			w.Write([]byte(err.Error()))
		} else {
			w.Write([]byte(http.StatusText(code)))
		}
	}
}

// ServeWithErrorHTTP returns the code and error of the request.
func (m *FileManager) ServeWithErrorHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
	return serveHTTP(&RequestContext{
		FM:   m,
		User: nil,
		FI:   nil,
	}, w, r)
}

// Allowed checks if the user has permission to access a directory/file.
func (u User) Allowed(url string) bool {
	var rule *Rule
	i := len(u.Rules) - 1

	for i >= 0 {
		rule = u.Rules[i]

		if rule.Regex {
			if rule.Regexp.MatchString(url) {
				return rule.Allow
			}
		} else if strings.HasPrefix(url, rule.Path) {
			return rule.Allow
		}

		i--
	}

	return true
}

// MatchString checks if this string matches the regular expression.
func (r *Regexp) MatchString(s string) bool {
	if r.regexp == nil {
		r.regexp = regexp.MustCompile(r.Raw)
	}

	return r.regexp.MatchString(s)
}

// Runner runs the commands for a certain event type.
func (m FileManager) Runner(event string, path string) error {
	commands := []string{}

	// Get the commands from the File Manager instance itself.
	if val, ok := m.Commands[event]; ok {
		commands = append(commands, val...)
	}

	// Execute the commands.
	for _, command := range commands {
		args := strings.Split(command, " ")
		nonblock := false

		if len(args) > 1 && args[len(args)-1] == "&" {
			// Run command in background; non-blocking
			nonblock = true
			args = args[:len(args)-1]
		}

		command, args, err := caddy.SplitCommandAndArgs(strings.Join(args, " "))
		if err != nil {
			return err
		}

		cmd := exec.Command(command, args...)
		cmd.Env = append(os.Environ(), "file="+path)
		cmd.Stdin = os.Stdin
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr

		if nonblock {
			log.Printf("[INFO] Nonblocking Command:\"%s %s\"", command, strings.Join(args, " "))
			if err := cmd.Start(); err != nil {
				return err
			}

			continue
		}

		log.Printf("[INFO] Blocking Command:\"%s %s\"", command, strings.Join(args, " "))
		if err := cmd.Run(); err != nil {
			return err
		}
	}

	return nil
}
<commit_msg>Make sure  default user has no username nor password<commit_after>package filemanager

import (
	"errors"
	"log"
	"net/http"
	"os"
	"os/exec"
	"regexp"
	"strings"

	rice "github.com/GeertJohan/go.rice"
	"github.com/asdine/storm"
	"github.com/mholt/caddy"
	"golang.org/x/net/webdav"
)

var (
	// ErrDuplicated occurs when you try to create a user that already exists.
	ErrDuplicated = errors.New("Duplicated user")
)

// FileManager is a file manager instance. It should be creating using the
// 'New' function and not directly.
type FileManager struct {
	// The BoltDB database for this instance.
	db *storm.DB

	// The key used to sign the JWT tokens.
	key []byte

	// The static assets.
	assets *rice.Box

	// PrefixURL is a part of the URL that is already trimmed from the request URL before it
	// arrives to our handlers. It may be useful when using File Manager as a middleware
	// such as in caddy-filemanager plugin. It is only useful in certain situations.
	PrefixURL string

	// BaseURL is the path where the GUI will be accessible. It musn't end with
	// a trailing slash and mustn't contain PrefixURL, if set. It shouldn't be
	// edited directly. Use SetBaseURL.
	BaseURL string

	// The Default User needed to build the New User page.
	DefaultUser *User

	// Users is a map with the different configurations for each user.
	Users map[string]*User

	// A map of events to a slice of commands.
	Commands map[string][]string

	// The plugins that have been plugged in.
	Plugins map[string]Plugin
}

// Command is a command function.
type Command func(r *http.Request, m *FileManager, u *User) error

// User contains the configuration for each user. It should be created
// using NewUser on a File Manager instance.
type User struct {
	// ID is the required primary key with auto increment0
	ID int `storm:"id,increment"`

	// Username is the user username used to login.
	Username string `json:"username" storm:"index,unique"`

	// The hashed password. This never reaches the front-end because it's temporarily
	// emptied during JSON marshall.
	Password string `json:"password"`

	// Tells if this user is an admin.
	Admin bool `json:"admin"`

	// FileSystem is the virtual file system the user has access.
	FileSystem webdav.Dir `json:"filesystem"`

	// Rules is an array of access and deny rules.
	Rules []*Rule `json:"rules"`

	// Custom styles for this user.
	CSS string `json:"css"`

	// These indicate if the user can perform certain actions.
	AllowNew      bool            `json:"allowNew"`      // Create files and folders
	AllowEdit     bool            `json:"allowEdit"`     // Edit/rename files
	AllowCommands bool            `json:"allowCommands"` // Execute commands
	Permissions   map[string]bool `json:"permissions"`   // Permissions added by plugins

	// Commands is the list of commands the user can execute.
	Commands []string `json:"commands"`
}

// Rule is a dissalow/allow rule.
type Rule struct {
	// Regex indicates if this rule uses Regular Expressions or not.
	Regex bool `json:"regex"`

	// Allow indicates if this is an allow rule. Set 'false' to be a disallow rule.
	Allow bool `json:"allow"`

	// Path is the corresponding URL path for this rule.
	Path string `json:"path"`

	// Regexp is the regular expression. Only use this when 'Regex' was set to true.
	Regexp *Regexp `json:"regexp"`
}

// Regexp is a regular expression wrapper around native regexp.
type Regexp struct {
	Raw    string `json:"raw"`
	regexp *regexp.Regexp
}

// Plugin is a File Manager plugin.
type Plugin interface {
	// The JavaScript that will be injected into the main page.
	JavaScript() string

	// If the Plugin returns (0, nil), the executation of File Manager will procced as usual.
	// Otherwise it will stop.
	BeforeAPI(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error)
	AfterAPI(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error)
}

// DefaultUser is used on New, when no 'base' user is provided.
var DefaultUser = User{
	AllowCommands: true,
	AllowEdit:     true,
	AllowNew:      true,
	Permissions:   map[string]bool{},
	Commands:      []string{},
	Rules:         []*Rule{},
	CSS:           "",
	Admin:         true,
	FileSystem:    webdav.Dir("."),
}

// New creates a new File Manager instance. If 'database' file already
// exists, it will load the users from there. Otherwise, a new user
// will be created using the 'base' variable. The 'base' User should
// not have the Password field hashed.
func New(database string, base User) (*FileManager, error) {
	// Creates a new File Manager instance with the Users
	// map and Assets box.
	m := &FileManager{
		Users:   map[string]*User{},
		assets:  rice.MustFindBox("./assets/dist"),
		Plugins: map[string]Plugin{},
	}

	// Tries to open a database on the location provided. This
	// function will automatically create a new one if it doesn't
	// exist.
	db, err := storm.Open(database)
	if err != nil {
		return nil, err
	}

	// Tries to get the encryption key from the database.
	// If it doesn't exist, create a new one of 256 bits.
	err = db.Get("config", "key", &m.key)
	if err != nil && err == storm.ErrNotFound {
		m.key = []byte(randomString(64))
		err = db.Set("config", "key", m.key)
	}

	if err != nil {
		return nil, err
	}

	// Tries to get the event commands from the database.
	// If they don't exist, initialize them.
	err = db.Get("config", "commands", &m.Commands)
	if err != nil && err == storm.ErrNotFound {
		m.Commands = map[string][]string{
			"before_save": {},
			"after_save":  {},
		}
		err = db.Set("config", "commands", m.Commands)
	}

	if err != nil {
		return nil, err
	}

	// Tries to fetch the users from the database and if there are
	// any, add them to the current File Manager instance.
	var users []User
	err = db.All(&users)
	if err != nil {
		return nil, err
	}

	for i := range users {
		m.Users[users[i].Username] = &users[i]
	}

	// If there are no users in the database, it creates a new one
	// based on 'base' User that must be provided by the function caller.
	if len(users) == 0 {
		u := base
		u.Username = "admin"

		// Hashes the password.
		u.Password, err = hashPassword("admin")
		if err != nil {
			return nil, err
		}

		// The first user must be an administrator.
		u.Admin = true
		u.AllowCommands = true
		u.AllowNew = true
		u.AllowEdit = true

		// Saves the user to the database.
		if err := db.Save(&u); err != nil {
			return nil, err
		}

		m.Users[u.Username] = &u
	}

	// Attaches db to this File Manager instance.
	m.db = db

	// Create the default user, making a copy of the base.
	base.Username = ""
	base.Password = ""
	m.DefaultUser = &base
	return m, nil
}

// RootURL returns the actual URL where
// File Manager interface can be accessed.
func (m FileManager) RootURL() string {
	return m.PrefixURL + m.BaseURL
}

// SetPrefixURL updates the prefixURL of a File
// Manager object.
func (m *FileManager) SetPrefixURL(url string) {
	url = strings.TrimPrefix(url, "/")
	url = strings.TrimSuffix(url, "/")
	url = "/" + url
	m.PrefixURL = strings.TrimSuffix(url, "/")
}

// SetBaseURL updates the baseURL of a File Manager
// object.
func (m *FileManager) SetBaseURL(url string) {
	url = strings.TrimPrefix(url, "/")
	url = strings.TrimSuffix(url, "/")
	url = "/" + url
	m.BaseURL = strings.TrimSuffix(url, "/")
}

// RegisterPlugin registers a plugin to a File Manager instance and
// loads its options from the database.
func (m *FileManager) RegisterPlugin(name string, plugin Plugin) error {
	if _, ok := m.Plugins[name]; ok {
		return errors.New("Plugin already registred")
	}

	err := m.db.Get("plugins", name, &plugin)
	if err != nil && err == storm.ErrNotFound {
		err = m.db.Set("plugins", name, plugin)
	}

	if err != nil {
		return err
	}

	m.Plugins[name] = plugin
	return nil
}

// RegisterEventType registers a new event type which can be triggered using Runner
// function.
func (m *FileManager) RegisterEventType(name string) error {
	if _, ok := m.Commands[name]; ok {
		return nil
	}

	m.Commands[name] = []string{}
	return m.db.Set("config", "commands", m.Commands)
}

// RegisterPermission registers a new user permission and adds it to every
// user with it default's 'value'. If the user is an admin, it will
// be true.
func (m *FileManager) RegisterPermission(name string, value bool) error {
	if _, ok := m.DefaultUser.Permissions[name]; ok {
		return nil
	}

	// Add the default value for this permission on the default user.
	m.DefaultUser.Permissions[name] = value

	for _, u := range m.Users {
		// Bypass the user if it is already defined.
		if _, ok := u.Permissions[name]; ok {
			continue
		}

		if u.Permissions == nil {
			u.Permissions = m.DefaultUser.Permissions
		}

		if u.Admin {
			u.Permissions[name] = true
		}

		err := m.db.Save(u)
		if err != nil {
			return err
		}
	}

	return nil
}

// ServeHTTP determines if the request is for this plugin, and if all prerequisites are met.
// Compatible with http.Handler.
func (m *FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	code, err := serveHTTP(&RequestContext{
		FM:   m,
		User: nil,
		FI:   nil,
	}, w, r)

	if code != 0 {
		w.WriteHeader(code)

		if err != nil {
			w.Write([]byte(err.Error()))
		} else {
			w.Write([]byte(http.StatusText(code)))
		}
	}
}

// ServeWithErrorHTTP returns the code and error of the request.
func (m *FileManager) ServeWithErrorHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
	return serveHTTP(&RequestContext{
		FM:   m,
		User: nil,
		FI:   nil,
	}, w, r)
}

// Allowed checks if the user has permission to access a directory/file.
func (u User) Allowed(url string) bool {
	var rule *Rule
	i := len(u.Rules) - 1

	for i >= 0 {
		rule = u.Rules[i]

		if rule.Regex {
			if rule.Regexp.MatchString(url) {
				return rule.Allow
			}
		} else if strings.HasPrefix(url, rule.Path) {
			return rule.Allow
		}

		i--
	}

	return true
}

// MatchString checks if this string matches the regular expression.
func (r *Regexp) MatchString(s string) bool {
	if r.regexp == nil {
		r.regexp = regexp.MustCompile(r.Raw)
	}

	return r.regexp.MatchString(s)
}

// Runner runs the commands for a certain event type.
func (m FileManager) Runner(event string, path string) error {
	commands := []string{}

	// Get the commands from the File Manager instance itself.
	if val, ok := m.Commands[event]; ok {
		commands = append(commands, val...)
	}

	// Execute the commands.
	for _, command := range commands {
		args := strings.Split(command, " ")
		nonblock := false

		if len(args) > 1 && args[len(args)-1] == "&" {
			// Run command in background; non-blocking
			nonblock = true
			args = args[:len(args)-1]
		}

		command, args, err := caddy.SplitCommandAndArgs(strings.Join(args, " "))
		if err != nil {
			return err
		}

		cmd := exec.Command(command, args...)
		cmd.Env = append(os.Environ(), "file="+path)
		cmd.Stdin = os.Stdin
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr

		if nonblock {
			log.Printf("[INFO] Nonblocking Command:\"%s %s\"", command, strings.Join(args, " "))
			if err := cmd.Start(); err != nil {
				return err
			}

			continue
		}

		log.Printf("[INFO] Blocking Command:\"%s %s\"", command, strings.Join(args, " "))
		if err := cmd.Run(); err != nil {
			return err
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package radius

import (
	"context"
	"net"
	"sync/atomic"
	"testing"
	"time"
)

func TestClient_Exchange_expired(t *testing.T) {
	handler := HandlerFunc(func(w ResponseWriter, r *Request) {
		// ignore
	})
	server := newTestServer(handler, StaticSecretSource([]byte(`12345`)))
	defer server.Close()

	ctx, cancel := context.WithTimeout(context.Background(), -time.Hour)
	defer cancel()

	req := New(CodeAccessRequest, []byte(`secret`))

	client := Client{}
	resp, err := client.Exchange(ctx, req, server.Addr)
	if resp != nil {
		t.Fatalf("got non-nil response (%v); expected nil", resp)
	}
	if err == nil {
		t.Fatal("got nil error; expected one")
	}
	if netErr, ok := err.(net.Error); !ok {
		t.Fatal("err is not a net.Error")
	} else if !netErr.Timeout() {
		t.Fatal("got netErr.Timeout() = false; expected true")
	}
}

func TestClient_Exchange_retry(t *testing.T) {
	secret := []byte(`12345`)

	var attempts int32

	handler := HandlerFunc(func(w ResponseWriter, r *Request) {
		if atomic.AddInt32(&attempts, 1) == 4 {
			w.Write(r.Response(CodeAccessAccept))
		}
	})
	server := newTestServer(handler, StaticSecretSource(secret))
	defer server.Close()

	req := New(CodeAccessRequest, secret)

	client := Client{
		Retry: time.Millisecond * 5,
	}
	resp, err := client.Exchange(context.Background(), req, server.Addr)
	if err != nil {
		t.Fatalf("got err %s; expected nil", err)
	}
	if resp.Code != CodeAccessAccept {
		t.Fatalf("got code %s; expected %s", resp.Code, CodeAccessAccept)
	}
	if attempts := atomic.LoadInt32(&attempts); attempts != 4 {
		t.Fatalf("response received in %d attemps; expecting 4", attempts)
	}
}

func TestClient_Exchange_cancelled(t *testing.T) {
	secret := []byte(`12345`)
	handler := HandlerFunc(func(w ResponseWriter, r *Request) {
		// ignore
	})
	server := newTestServer(handler, StaticSecretSource(secret))
	defer server.Close()

	ctx, cancel := context.WithCancel(context.Background())
	go func() {
		time.Sleep(time.Millisecond * 50)
		cancel()
	}()

	req := New(CodeAccessRequest, secret)

	client := Client{
		Retry: time.Millisecond * 5,
	}
	resp, err := client.Exchange(ctx, req, server.Addr)
	if resp != nil {
		t.Fatalf("got non-nil response (%v); expected nil", resp)
	}
	if err != context.Canceled {
		t.Fatalf("got error = %v; expecting context.Canceled", err)
	}
}
<commit_msg>add tests for Client.Exchange invalid, non-authentic packets<commit_after>package radius

import (
	"context"
	"net"
	"strings"
	"sync/atomic"
	"testing"
	"time"
)

func TestClient_Exchange_expired(t *testing.T) {
	handler := HandlerFunc(func(w ResponseWriter, r *Request) {
		// ignore
	})
	server := newTestServer(handler, StaticSecretSource([]byte(`12345`)))
	defer server.Close()

	ctx, cancel := context.WithTimeout(context.Background(), -time.Hour)
	defer cancel()

	req := New(CodeAccessRequest, []byte(`secret`))

	client := Client{}
	resp, err := client.Exchange(ctx, req, server.Addr)
	if resp != nil {
		t.Fatalf("got non-nil response (%v); expected nil", resp)
	}
	if err == nil {
		t.Fatal("got nil error; expected one")
	}
	if netErr, ok := err.(net.Error); !ok {
		t.Fatal("err is not a net.Error")
	} else if !netErr.Timeout() {
		t.Fatal("got netErr.Timeout() = false; expected true")
	}
}

func TestClient_Exchange_retry(t *testing.T) {
	secret := []byte(`12345`)

	var attempts int32

	handler := HandlerFunc(func(w ResponseWriter, r *Request) {
		if atomic.AddInt32(&attempts, 1) == 4 {
			w.Write(r.Response(CodeAccessAccept))
		}
	})
	server := newTestServer(handler, StaticSecretSource(secret))
	defer server.Close()

	req := New(CodeAccessRequest, secret)

	client := Client{
		Retry: time.Millisecond * 5,
	}
	resp, err := client.Exchange(context.Background(), req, server.Addr)
	if err != nil {
		t.Fatalf("got err %s; expected nil", err)
	}
	if resp.Code != CodeAccessAccept {
		t.Fatalf("got code %s; expected %s", resp.Code, CodeAccessAccept)
	}
	if attempts := atomic.LoadInt32(&attempts); attempts != 4 {
		t.Fatalf("response received in %d attemps; expecting 4", attempts)
	}
}

func TestClient_Exchange_cancelled(t *testing.T) {
	secret := []byte(`12345`)
	handler := HandlerFunc(func(w ResponseWriter, r *Request) {
		// ignore
	})
	server := newTestServer(handler, StaticSecretSource(secret))
	defer server.Close()

	ctx, cancel := context.WithCancel(context.Background())
	go func() {
		time.Sleep(time.Millisecond * 50)
		cancel()
	}()

	req := New(CodeAccessRequest, secret)

	client := Client{
		Retry: time.Millisecond * 5,
	}
	resp, err := client.Exchange(ctx, req, server.Addr)
	if resp != nil {
		t.Fatalf("got non-nil response (%v); expected nil", resp)
	}
	if err != context.Canceled {
		t.Fatalf("got error = %v; expecting context.Canceled", err)
	}
}

func TestClient_Exchange_invalidPacket(t *testing.T) {
	secret := []byte(`12345`)

	var server *TestServer
	handler := HandlerFunc(func(w ResponseWriter, r *Request) {
		// write bad data to client
		server.l.WriteTo([]byte(`AAAA`), r.RemoteAddr)
	})
	server = newTestServer(handler, StaticSecretSource(secret))
	defer server.Close()

	req := New(CodeAccessRequest, secret)

	client := Client{
		Retry:           time.Millisecond * 5,
		MaxPacketErrors: 2,
	}
	resp, err := client.Exchange(context.Background(), req, server.Addr)
	if resp != nil {
		t.Fatalf("got non-nil response (%v); expected nil", resp)
	}
	if expecting := `packet not at least 20 bytes long`; !strings.Contains(err.Error(), expecting) {
		t.Fatalf("got error = %v; expecting %s", err, expecting)
	}
}

func TestClient_Exchange_nonauthenticPacket(t *testing.T) {
	secret := []byte(`12345`)

	var server *TestServer
	handler := HandlerFunc(func(w ResponseWriter, r *Request) {
		resp := r.Response(CodeAccessAccept)
		resp.Authenticator = [16]byte{}
		w.Write(resp)
	})
	server = newTestServer(handler, StaticSecretSource(secret))
	defer server.Close()

	req := New(CodeAccessRequest, secret)

	client := Client{
		Retry:           time.Millisecond * 5,
		MaxPacketErrors: 2,
	}
	resp, err := client.Exchange(context.Background(), req, server.Addr)
	if resp != nil {
		t.Fatalf("got non-nil response (%v); expected nil", resp)
	}
	if _, ok := err.(*NonAuthenticResponseError); !ok {
		t.Fatalf("got error %T; expecting NonAuthenticResponseError", err)
	}
}
<|endoftext|>


<commit_before>package alidayu_test

import (
	"fmt"
	"io/ioutil"
	"os"

	"github.com/northbright/alidayu"
)

// Example of Client.Post()
func ExampleClient_Post() {
	// Create a new client(创建一个新的Client实例).
	c := &alidayu.Client{AppKey: "", AppSecret: "", UseHTTPS: false}

	// ---------------------------------------
	// Send Verification Code in SMS(发送短信验证码).
	// ---------------------------------------
	// Set Parameters.
	params := map[string]string{}
	// It'll use default common parameters if you don't set them.
	// 如果这些公共参数不设置，那么会自动使用默认值.
	// params["format"] = "json"
	// params["v"] = "2.0"
	// params["sign_method"] = "md5"
	params["method"] = "alibaba.aliqin.fc.sms.num.send"           // Set method to send SMS(API接口名称).
	params["sms_type"] = "normal"                                 // Set SMS type(短信类型).
	params["sms_free_sign_name"] = ""                             // Set SMS signature(短信签名).
	params["sms_param"] = `{"code":"123456", "product":"My App"}` // Set variable for SMS template(短信模板变量).
	params["sms_template_code"] = ""                              // Set SMS template code(短信模板ID).
	params["rec_num"] = ""                                        // Set phone number to send SMS(短信接收号码).

	// Call Post() to post the request.
	resp, err := c.Post(params)
	if err != nil {
		fmt.Fprintf(os.Stderr, "c.Post() error: %v\n", err)
		return
	}

	// Read HTTP Response.
	defer resp.Body.Close()
	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Fprintf(os.Stderr, "ioutil.ReadAll() error:%v\n", err)
		return
	}

	fmt.Fprintf(os.Stderr, "c.Post() successfully\n%v\n", string(data))

	// ------------------------------------------
	// Send Verification Code in Single Call(发送文本转语音通知验证码).
	// ------------------------------------------
	// Set Parameters.
	params = map[string]string{}
	params["method"] = "alibaba.aliqin.fc.tts.num.singlecall"     // Set method to make single call(API接口名称).
	params["tts_param"] = `{"code":"123456", "product":"My App"}` // Set variable for TTS template(文本转语音（TTS）模板变量).
	params["called_num"] = ""                                     // Set phone number to make single call(被叫号码).
	params["called_show_num"] = ""                                // Set show number(被叫号显).
	params["tts_code"] = ""                                       // Set TTS code(TTS模板ID).

	// Call Post() to post the request.
	resp2, err := c.Post(params)
	if err != nil {
		fmt.Fprintf(os.Stderr, "c.Post() error: %v\n", err)
		return
	}

	// Read HTTP Response.
	defer resp2.Body.Close()
	data2, err := ioutil.ReadAll(resp2.Body)
	if err != nil {
		fmt.Fprintf(os.Stderr, "ioutil.ReadAll() error:%v\n", err)
		return
	}

	fmt.Fprintf(os.Stderr, "c.Post() successfully\n%v\n", string(data2))

	// Output:
}

// Example of Client.Exec()
func ExampleClient_Exec() {
	// Create a new client(创建一个新的Client实例).
	c := &alidayu.Client{AppKey: "", AppSecret: "", UseHTTPS: false}

	// ---------------------------------------
	// Send Verification Code in SMS(发送短信验证码).
	// ---------------------------------------
	// Set Parameters.
	params := map[string]string{}
	// It'll use default common parameters if you don't set them.
	// 如果这些公共参数不设置，那么会自动使用默认值.
	// params["format"] = "json"
	// params["v"] = "2.0"
	// params["sign_method"] = "md5"
	params["method"] = "alibaba.aliqin.fc.sms.num.send"           // Set method to send SMS(API接口名称).
	params["sms_type"] = "normal"                                 // Set SMS type(短信类型).
	params["sms_free_sign_name"] = ""                             // Set SMS signature(短信签名).
	params["sms_param"] = `{"code":"123456", "product":"My App"}` // Set variable for SMS template(短信模板变量).
	params["sms_template_code"] = ""                              // Set SMS template code(短信模板ID).
	params["rec_num"] = ""                                        // Set phone number to send SMS(短信接收号码).

	// Call Exec() to post the request.
	success, result, err := c.Exec(params)
	if err != nil {
		fmt.Fprintf(os.Stderr, "c.Exec() error: %v\nsuccess: %v\nresult: %v\n", err, success, result)
		return
	}

	fmt.Fprintf(os.Stderr, "c.Exec() successfully\nsuccess: %v\nresult: %s\n", success, result)

	// ------------------------------------------
	// Send Verification Code in Single Call(发送文本转语音通知验证码).
	// ------------------------------------------
	// Set Parameters.
	params = map[string]string{}
	params["method"] = "alibaba.aliqin.fc.tts.num.singlecall"     // Set method to make single call(API接口名称).
	params["tts_param"] = `{"code":"123456", "product":"My App"}` // Set variable for TTS template(文本转语音（TTS）模板变量).
	params["called_num"] = ""                                     // Set phone number to make single call(被叫号码).
	params["called_show_num"] = ""                                // Set show number(被叫号显).
	params["tts_code"] = ""                                       // Set TTS code(TTS模板ID).

	// Call Exec() to post the request.
	success, result, err = c.Exec(params)
	if err != nil {
		fmt.Fprintf(os.Stderr, "c.Exec() error: %v\nsuccess: %v\nresult: %v\n", err, success, result)
		return
	}

	fmt.Fprintf(os.Stderr, "c.Exec() successfully\nsuccess: %v\nresult: %s\n", success, result)

	// Output:
}
<commit_msg>Make it easier to read comments.<commit_after>package alidayu_test

import (
	"fmt"
	"io/ioutil"
	"os"

	"github.com/northbright/alidayu"
)

// Example of Client.Post()
func ExampleClient_Post() {
	// Create a new client(创建一个新的Client实例).
	c := &alidayu.Client{AppKey: "", AppSecret: "", UseHTTPS: false}

	// ---------------------------------------
	// Send Verification Code in SMS(发送短信验证码).
	// ---------------------------------------
	// Set parameters(设置API所需的所有参数，包括公共参数和方法相关参数).
	params := map[string]string{}

	// Common parameters will be filled with default values automatically.
	// You may also set them to override default ones.
	// 公共参数会自动使用默认值填充，不需要用户设置.
	// 当然，用户可以使用自己的设置来覆盖默认值.
	//   params["format"] = "json"      // "json" or "xml"
	//   params["v"] = "2.0"            // "2.0" by default
	//   params["sign_method"] = "md5"  // "md5" or "hmac"

	// No need to set "timestamp"(不需要用户设置"timestamp"参数).
	//   "timestamp" will be calculated and filled automatically("timestamp"会被自动计算和设置).

	// Set method specified parameters(设置方法相关特定参数).
	params["method"] = "alibaba.aliqin.fc.sms.num.send"           // Set method to send SMS(API接口名称).
	params["sms_type"] = "normal"                                 // Set SMS type(短信类型).
	params["sms_free_sign_name"] = ""                             // Set SMS signature(短信签名).
	params["sms_param"] = `{"code":"123456", "product":"My App"}` // Set variable for SMS template(短信模板变量).
	params["sms_template_code"] = ""                              // Set SMS template code(短信模板ID).
	params["rec_num"] = ""                                        // Set phone number to send SMS(短信接收号码).

	// Call Post() to post the request.
	resp, err := c.Post(params)
	if err != nil {
		fmt.Fprintf(os.Stderr, "c.Post() error: %v\n", err)
		return
	}

	// Read HTTP Response.
	defer resp.Body.Close()
	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Fprintf(os.Stderr, "ioutil.ReadAll() error:%v\n", err)
		return
	}

	fmt.Fprintf(os.Stderr, "c.Post() successfully\n%v\n", string(data))

	// ------------------------------------------
	// Send Verification Code in Single Call(发送文本转语音通知验证码).
	// ------------------------------------------
	// Set Parameters.
	params = map[string]string{}
	params["method"] = "alibaba.aliqin.fc.tts.num.singlecall"     // Set method to make single call(API接口名称).
	params["tts_param"] = `{"code":"123456", "product":"My App"}` // Set variable for TTS template(文本转语音（TTS）模板变量).
	params["called_num"] = ""                                     // Set phone number to make single call(被叫号码).
	params["called_show_num"] = ""                                // Set show number(被叫号显).
	params["tts_code"] = ""                                       // Set TTS code(TTS模板ID).

	// Call Post() to post the request.
	resp2, err := c.Post(params)
	if err != nil {
		fmt.Fprintf(os.Stderr, "c.Post() error: %v\n", err)
		return
	}

	// Read HTTP Response.
	defer resp2.Body.Close()
	data2, err := ioutil.ReadAll(resp2.Body)
	if err != nil {
		fmt.Fprintf(os.Stderr, "ioutil.ReadAll() error:%v\n", err)
		return
	}

	fmt.Fprintf(os.Stderr, "c.Post() successfully\n%v\n", string(data2))

	// Output:
}

// Example of Client.Exec()
func ExampleClient_Exec() {
	// Create a new client(创建一个新的Client实例).
	c := &alidayu.Client{AppKey: "", AppSecret: "", UseHTTPS: false}

	// ---------------------------------------
	// Send Verification Code in SMS(发送短信验证码).
	// ---------------------------------------
	// Set parameters(设置API所需的所有参数，包括公共参数和方法相关参数).
	params := map[string]string{}

	// Common parameters will be filled with default values automatically.
	// You may also set them to override default ones.
	// 公共参数会自动使用默认值填充，不需要用户设置.
	// 当然，用户可以使用自己的设置来覆盖默认值.
	//   params["format"] = "json"      // "json" or "xml"
	//   params["v"] = "2.0"            // "2.0" by default
	//   params["sign_method"] = "md5"  // "md5" or "hmac"

	// No need to set "timestamp"(不需要用户设置"timestamp"参数).
	//   "timestamp" will be calculated and filled automatically("timestamp"会被自动计算和设置).

	// Set method specified parameters(设置方法相关特定参数).
	params["method"] = "alibaba.aliqin.fc.sms.num.send"           // Set method to send SMS(API接口名称).
	params["sms_type"] = "normal"                                 // Set SMS type(短信类型).
	params["sms_free_sign_name"] = ""                             // Set SMS signature(短信签名).
	params["sms_param"] = `{"code":"123456", "product":"My App"}` // Set variable for SMS template(短信模板变量).
	params["sms_template_code"] = ""                              // Set SMS template code(短信模板ID).
	params["rec_num"] = ""                                        // Set phone number to send SMS(短信接收号码).

	// Call Exec() to post the request.
	success, result, err := c.Exec(params)
	if err != nil {
		fmt.Fprintf(os.Stderr, "c.Exec() error: %v\nsuccess: %v\nresult: %v\n", err, success, result)
		return
	}

	fmt.Fprintf(os.Stderr, "c.Exec() successfully\nsuccess: %v\nresult: %s\n", success, result)

	// ------------------------------------------
	// Send Verification Code in Single Call(发送文本转语音通知验证码).
	// ------------------------------------------
	// Set Parameters.
	params = map[string]string{}
	params["method"] = "alibaba.aliqin.fc.tts.num.singlecall"     // Set method to make single call(API接口名称).
	params["tts_param"] = `{"code":"123456", "product":"My App"}` // Set variable for TTS template(文本转语音（TTS）模板变量).
	params["called_num"] = ""                                     // Set phone number to make single call(被叫号码).
	params["called_show_num"] = ""                                // Set show number(被叫号显).
	params["tts_code"] = ""                                       // Set TTS code(TTS模板ID).

	// Call Exec() to post the request.
	success, result, err = c.Exec(params)
	if err != nil {
		fmt.Fprintf(os.Stderr, "c.Exec() error: %v\nsuccess: %v\nresult: %v\n", err, success, result)
		return
	}

	fmt.Fprintf(os.Stderr, "c.Exec() successfully\nsuccess: %v\nresult: %s\n", success, result)

	// Output:
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/coreos/go-etcd/etcd"
	"gopkg.in/BlueDragonX/go-settings.v0"
	"io/ioutil"
	"net/http"
	"reflect"
	"strings"
	"testing"
	"time"
)

const (
	validURI   = "http://127.0.0.1:4001"
	invalidURI = "http://127.0.0.1:9999"
)

type MockClient struct {
	WaitFor  time.Duration
	GetValue map[string]interface{}
	GetError error
	Changes  chan string
}

func (mc *MockClient) Wait(stop chan bool) bool {
	select {
	case <-time.After(mc.WaitFor):
	case <-stop:
		return false
	}
	return true
}

func (mc *MockClient) Get(keys []string) (map[string]interface{}, error) {
	return mc.GetValue, mc.GetError
}

func (mc *MockClient) Watch(prefixes []string, changes chan string, stop chan bool) {
	mc.Changes = changes
	<-stop
}
func getEtcdClient(t *testing.T, uri string) *EtcdClient {
	config := settings.Settings{}
	if uri != "" {
		config.Set("uris", []string{uri})
	}
	config.Set("prefix", "test")
	client, err := NewEtcdClient(&config)
	if err != nil {
		t.Fatal(err)
	}
	return client
}

func etcdClientSetUp(t *testing.T, rawClient *etcd.Client) map[string]interface{} {
	var err error
	if _, err = rawClient.SetDir("test", 0); err != nil {
		t.Fatal(err)
	}
	if _, err = rawClient.Set("test/index", "1", 0); err != nil {
		t.Fatal(err)
	}
	if _, err = rawClient.SetDir("test/values", 0); err != nil {
		t.Fatal(err)
	}
	if _, err = rawClient.Set("test/values/a", "aye", 0); err != nil {
		t.Fatal(err)
	}
	if _, err = rawClient.Set("test/values/b", "bee", 0); err != nil {
		t.Fatal(err)
	}

	want := make(map[string]interface{})
	wantValues := make(map[string]interface{})
	wantValues["a"] = "aye"
	wantValues["b"] = "bee"
	want["values"] = wantValues
	want["index"] = "1"
	return want
}

func etcdClientTearDown(t *testing.T, rawClient *etcd.Client) {
	if _, err := rawClient.Delete("test", true); err != nil {
		t.Fatal(err)
	}
}

// Ensure the etcd server does something when we knock.
func TestEtcdClientRaw(t *testing.T) {
	if resp, err := http.Get(validURI + "/v2/keys/"); err == nil {
		defer resp.Body.Close()
		if body, err := ioutil.ReadAll(resp.Body); err == nil {
			t.Log(string(body))
		} else {
			t.Error(err)
		}
	} else {
		t.Error(err)
	}

	if _, err := http.Get(invalidURI + "/v2/keys/"); err == nil {
		t.Errorf("no error raised on invalid URI")
	} else {
		t.Logf("%v: %v", reflect.TypeOf(err), err)
	}
}

// Ensure Get returns the correct values.
func TestEtcdClientGet(t *testing.T) {
	client := getEtcdClient(t, validURI)
	rawClient := client.client

	// set some keys to play with
	data := etcdClientSetUp(t, rawClient)
	defer etcdClientTearDown(t, rawClient)

	type getCheck struct {
		keys []string
		want interface{}
	}

	getChecks := []getCheck{
		{
			[]string{"test/values"},
			map[string]interface{}{
				"test": map[string]interface{}{
					"values": data["values"],
				},
			},
		},
		{
			[]string{"test/values", "test/index"},
			map[string]interface{}{
				"test": map[string]interface{}{
					"values": data["values"],
					"index":  data["index"],
				},
			},
		},
		{
			[]string{"test/values", "test/index", "test/missing"},
			map[string]interface{}{
				"test": map[string]interface{}{
					"values": data["values"],
					"index":  data["index"],
				},
			},
		},
		{
			[]string{"/test/values"},
			map[string]interface{}{
				"test": map[string]interface{}{
					"values": data["values"],
				},
			},
		},
		{
			[]string{"/test/values/"},
			map[string]interface{}{
				"test": map[string]interface{}{
					"values": data["values"],
				},
			},
		},
	}

	for _, check := range getChecks {
		if have, err := client.Get(check.keys); err == nil {
			if !reflect.DeepEqual(check.want, have) {
				t.Errorf("keys %v are invalid: %v != %v", check.keys, check.want, have)
			}
		} else {
			t.Errorf("keys %v not retrieved: %s\n", check.keys, err)
		}
	}
}

// Ensure the client returns properly when get fails.
func TestEtcdClientGetFailure(t *testing.T) {
	// server down should return an error
	client := getEtcdClient(t, invalidURI)
	if value, err := client.Get([]string{"test/values"}); err == nil {
		t.Errorf("received a value from etcd: %v", value)
	} else {
		t.Logf("received %v: %v", reflect.TypeOf(err), err)
	}

	// missing key should return an empty map
	client = getEtcdClient(t, validURI)
	if value, err := client.Get([]string{"test/values"}); err == nil {
		t.Logf("received a value from etcd: %v", value)
	} else {
		t.Errorf("received %v: %v", reflect.TypeOf(err), err)
	}
}

// Ensure the client watches properly.
func TestEtcdClientWatch(t *testing.T) {
	client := getEtcdClient(t, validURI)
	rawClient := client.client
	etcdClientSetUp(t, rawClient)
	defer etcdClientTearDown(t, rawClient)

	// server down should return an error
	join := make(chan bool)
	changes := make(chan string)
	stop := make(chan bool)
	go func() {
		client.Watch([]string{"test/index"}, changes, stop)
		close(join)
	}()

	time.Sleep(50 * time.Millisecond)
	go func() {
		rawClient.Set("/test/index", "2", 0)
	}()

	if key := <-changes; key != "test/index" {
		t.Errorf("changed key is '%s' not 'test/index'", key)
	} else {
		t.Log("changed key is '%s'\n", key)
	}
	stop <- true
	<-join
}

// Ensure we URIs with and without trailing slashes work.
func TestEtcdClientTrailingSlash(t *testing.T) {
	client := getEtcdClient(t, validURI)
	etcdClientSetUp(t, client.client)
	defer etcdClientTearDown(t, client.client)

	// test without a trailing slash
	uri := strings.TrimRight(validURI, "/")
	client = getEtcdClient(t, uri)
	if _, err := client.Get([]string{"test/values"}); err != nil {
		t.Errorf("URI without trailing slash failed: %s", err)
	}

	// test with a trailing slash
	uri = uri + "/"
	client = getEtcdClient(t, uri)
	if _, err := client.Get([]string{"test/values"}); err != nil {
		t.Errorf("URI with trailing slash failed: %s", err)
	}
}
<commit_msg>Adjust TestEtcdClientGetFailure test.<commit_after>package main

import (
	"github.com/coreos/go-etcd/etcd"
	"gopkg.in/BlueDragonX/go-settings.v0"
	"io/ioutil"
	"net/http"
	"reflect"
	"strings"
	"testing"
	"time"
)

const (
	validURI   = "http://127.0.0.1:4001"
	invalidURI = "http://127.0.0.1:9999"
)

type MockClient struct {
	WaitFor  time.Duration
	GetValue map[string]interface{}
	GetError error
	Changes  chan string
}

func (mc *MockClient) Wait(stop chan bool) bool {
	select {
	case <-time.After(mc.WaitFor):
	case <-stop:
		return false
	}
	return true
}

func (mc *MockClient) Get(keys []string) (map[string]interface{}, error) {
	return mc.GetValue, mc.GetError
}

func (mc *MockClient) Watch(prefixes []string, changes chan string, stop chan bool) {
	mc.Changes = changes
	<-stop
}
func getEtcdClient(t *testing.T, uri string) *EtcdClient {
	config := settings.Settings{}
	if uri != "" {
		config.Set("uris", []string{uri})
	}
	config.Set("prefix", "test")
	client, err := NewEtcdClient(&config)
	if err != nil {
		t.Fatal(err)
	}
	return client
}

func etcdClientSetUp(t *testing.T, rawClient *etcd.Client) map[string]interface{} {
	var err error
	if _, err = rawClient.SetDir("test", 0); err != nil {
		t.Fatal(err)
	}
	if _, err = rawClient.Set("test/index", "1", 0); err != nil {
		t.Fatal(err)
	}
	if _, err = rawClient.SetDir("test/values", 0); err != nil {
		t.Fatal(err)
	}
	if _, err = rawClient.Set("test/values/a", "aye", 0); err != nil {
		t.Fatal(err)
	}
	if _, err = rawClient.Set("test/values/b", "bee", 0); err != nil {
		t.Fatal(err)
	}

	want := make(map[string]interface{})
	wantValues := make(map[string]interface{})
	wantValues["a"] = "aye"
	wantValues["b"] = "bee"
	want["values"] = wantValues
	want["index"] = "1"
	return want
}

func etcdClientTearDown(t *testing.T, rawClient *etcd.Client) {
	if _, err := rawClient.Delete("test", true); err != nil {
		t.Fatal(err)
	}
}

// Ensure the etcd server does something when we knock.
func TestEtcdClientRaw(t *testing.T) {
	if resp, err := http.Get(validURI + "/v2/keys/"); err == nil {
		defer resp.Body.Close()
		if body, err := ioutil.ReadAll(resp.Body); err == nil {
			t.Log(string(body))
		} else {
			t.Error(err)
		}
	} else {
		t.Error(err)
	}

	if _, err := http.Get(invalidURI + "/v2/keys/"); err == nil {
		t.Errorf("no error raised on invalid URI")
	} else {
		t.Logf("%v: %v", reflect.TypeOf(err), err)
	}
}

// Ensure Get returns the correct values.
func TestEtcdClientGet(t *testing.T) {
	client := getEtcdClient(t, validURI)
	rawClient := client.client

	// set some keys to play with
	data := etcdClientSetUp(t, rawClient)
	defer etcdClientTearDown(t, rawClient)

	type getCheck struct {
		keys []string
		want interface{}
	}

	getChecks := []getCheck{
		{
			[]string{"test/values"},
			map[string]interface{}{
				"test": map[string]interface{}{
					"values": data["values"],
				},
			},
		},
		{
			[]string{"test/values", "test/index"},
			map[string]interface{}{
				"test": map[string]interface{}{
					"values": data["values"],
					"index":  data["index"],
				},
			},
		},
		{
			[]string{"test/values", "test/index", "test/missing"},
			map[string]interface{}{
				"test": map[string]interface{}{
					"values": data["values"],
					"index":  data["index"],
				},
			},
		},
		{
			[]string{"/test/values"},
			map[string]interface{}{
				"test": map[string]interface{}{
					"values": data["values"],
				},
			},
		},
		{
			[]string{"/test/values/"},
			map[string]interface{}{
				"test": map[string]interface{}{
					"values": data["values"],
				},
			},
		},
	}

	for _, check := range getChecks {
		if have, err := client.Get(check.keys); err == nil {
			if !reflect.DeepEqual(check.want, have) {
				t.Errorf("keys %v are invalid: %v != %v", check.keys, check.want, have)
			}
		} else {
			t.Errorf("keys %v not retrieved: %s\n", check.keys, err)
		}
	}
}

// Ensure the client returns properly when get fails.
func TestEtcdClientGetFailure(t *testing.T) {
	// missing key should return an empty map
	client := getEtcdClient(t, validURI)
	if value, err := client.Get([]string{"test/values"}); err == nil {
		t.Logf("received a value from etcd: %v", value)
	} else {
		t.Errorf("received %v: %v", reflect.TypeOf(err), err)
	}
}

// Ensure the client watches properly.
func TestEtcdClientWatch(t *testing.T) {
	client := getEtcdClient(t, validURI)
	rawClient := client.client
	etcdClientSetUp(t, rawClient)
	defer etcdClientTearDown(t, rawClient)

	// server down should return an error
	join := make(chan bool)
	changes := make(chan string)
	stop := make(chan bool)
	go func() {
		client.Watch([]string{"test/index"}, changes, stop)
		close(join)
	}()

	time.Sleep(50 * time.Millisecond)
	go func() {
		rawClient.Set("/test/index", "2", 0)
	}()

	if key := <-changes; key != "test/index" {
		t.Errorf("changed key is '%s' not 'test/index'", key)
	} else {
		t.Log("changed key is '%s'\n", key)
	}
	stop <- true
	<-join
}

// Ensure we URIs with and without trailing slashes work.
func TestEtcdClientTrailingSlash(t *testing.T) {
	client := getEtcdClient(t, validURI)
	etcdClientSetUp(t, client.client)
	defer etcdClientTearDown(t, client.client)

	// test without a trailing slash
	uri := strings.TrimRight(validURI, "/")
	client = getEtcdClient(t, uri)
	if _, err := client.Get([]string{"test/values"}); err != nil {
		t.Errorf("URI without trailing slash failed: %s", err)
	}

	// test with a trailing slash
	uri = uri + "/"
	client = getEtcdClient(t, uri)
	if _, err := client.Get([]string{"test/values"}); err != nil {
		t.Errorf("URI with trailing slash failed: %s", err)
	}
}
<|endoftext|>


<commit_before>// +build !windows
// Copyright 2016 go-dockerclient authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package docker

import (
	"context"
	"net"
	"net/http"

	"github.com/hashicorp/go-cleanhttp"
)

// initializeNativeClient initializes the native Unix domain socket client on
// Unix-style operating systems
func (c *Client) initializeNativeClient() {
	if c.endpointURL.Scheme != unixProtocol {
		return
	}
	socketPath := c.endpointURL.Path
	tr := cleanhttp.DefaultTransport()
	tr.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
		return c.Dialer.Dial(unixProtocol, socketPath)
	}
	c.nativeHTTPClient = &http.Client{Transport: tr}
}
<commit_msg>client_unix: support Dial and DialContext<commit_after>// +build !windows
// Copyright 2016 go-dockerclient authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package docker

import (
	"context"
	"net"
	"net/http"

	"github.com/hashicorp/go-cleanhttp"
)

// initializeNativeClient initializes the native Unix domain socket client on
// Unix-style operating systems
func (c *Client) initializeNativeClient() {
	if c.endpointURL.Scheme != unixProtocol {
		return
	}
	socketPath := c.endpointURL.Path
	tr := cleanhttp.DefaultTransport()
	tr.Dial = func(network, addr string) (net.Conn, error) {
		return c.Dialer.Dial(network, addr)
	}
	tr.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
		return c.Dialer.Dial(unixProtocol, socketPath)
	}
	c.nativeHTTPClient = &http.Client{Transport: tr}
}
<|endoftext|>


<commit_before>package cmd

import (
	"database/sql"
	"encoding/json"

	"context"
	"fmt"
	"github.com/OpenBazaar/jsonpb"
	"github.com/OpenBazaar/openbazaar-go/pb"
	"os"
	"path"

	"github.com/OpenBazaar/openbazaar-go/ipfs"

	"github.com/ipfs/go-ipfs/commands"
	ipfscore "github.com/ipfs/go-ipfs/core"
	"github.com/ipfs/go-ipfs/repo/config"
	"io/ioutil"
	"strings"

	"bufio"
	"errors"
	"github.com/OpenBazaar/openbazaar-go/repo"
	"github.com/OpenBazaar/openbazaar-go/repo/db"

	"github.com/OpenBazaar/openbazaar-go/core"
	"github.com/golang/protobuf/proto"
	"github.com/ipfs/go-ipfs/repo/fsrepo"
	"golang.org/x/crypto/ssh/terminal"
	"syscall"
	"time"
)

type Convert struct {
	Password string `short:"p" long:"password" description:"the encryption password if the database is encrypted"`
	DataDir  string `short:"d" long:"datadir" description:"specify the data directory to be used"`
	Testnet  bool   `short:"t" long:"testnet" description:"use the test network"`
}

func (x *Convert) Execute(args []string) error {
	if len(args) == 0 {
		fmt.Fprintf(os.Stderr, "Please specify the cryptocurrency you wish to convert to. Examples:\nopenbazaar-go convert bitcoin\nopenbazaar-go convert bitcoincash\nopenbazaar-go convert zcash /path/to/zcashd\n")
		return nil
	}
	if strings.ToLower(args[0]) == "zcash" && len(args) == 1 {
		fmt.Fprintf(os.Stderr, "When converting to zcash please specify the path to the zcash binary. Example:\nopenbazaar-go convert zcash /path/to/zcashd\n")
		return nil
	}
	if !(strings.ToLower(args[0]) == "bitcoin" || strings.ToLower(args[0]) == "bitcoincash" || strings.ToLower(args[0]) == "zcash") {
		fmt.Fprintf(os.Stderr, "Unknown currency type: please enter either bitcoin, bitcoincash, or zcash.\n")
		return nil
	}

	var str string
	var cfgtype string
	var currencyCode string
	switch strings.ToLower(args[0]) {
	case "bitcoin":
		str = "Bitcoin"
		cfgtype = "spvwallet"
		currencyCode = "BTC"
	case "bitcoincash":
		str = "Bitcoin Cash"
		cfgtype = "bitcoincash"
		currencyCode = "BCH"
	case "zcash":
		str = "ZCash"
		cfgtype = "zcashd"
		currencyCode = "ZEC"
	}

	if x.Testnet {
		currencyCode = "T" + currencyCode
	}

	reader := bufio.NewReader(os.Stdin)
	fmt.Printf("This command will convert your wallet to %s. It will delete any coins you have in your wallet and will prevent you from completing any outstanding orders. Please make sure you have emptied the wallet and completed all your orders before running this command. You will also need to select a new moderator.\n", str)
	fmt.Println("Are you sure you want to continue (y/n)?")
	yn, _ := reader.ReadString('\n')
	if !(strings.ToLower(yn) == "y\n" || strings.ToLower(yn) == "yes\n" || strings.ToLower(yn)[:1] == "y") {
		fmt.Println("No changes made")
		return nil
	}

	fmt.Println("Are you REALLY sure (y/n)?")
	yn, _ = reader.ReadString('\n')
	if !(strings.ToLower(yn) == "y\n" || strings.ToLower(yn) == "yes\n" || strings.ToLower(yn)[:1] == "y") {
		fmt.Println("No changes made")
		return nil
	}

	fmt.Println("Working...")

	// Set repo path
	repoPath, err := repo.GetRepoPath(x.Testnet)
	if err != nil {
		return err
	}
	if x.DataDir != "" {
		repoPath = x.DataDir
	}

	// Wipe database tables
	var dbPath string
	if x.Testnet {
		dbPath = path.Join(repoPath, "datastore", "testnet.db")
	} else {
		dbPath = path.Join(repoPath, "datastore", "mainnet.db")
	}
	sqlitedb, err := sql.Open("sqlite3", dbPath)
	if err != nil {
		return err
	}
	if x.Password != "" {
		p := "pragma key='" + x.Password + "';"
		sqlitedb.Exec(p)
	}

	_, err = sqlitedb.Exec("DELETE FROM txns;")
	if err != nil {
		return err
	}
	_, err = sqlitedb.Exec("DELETE FROM stxos;")
	if err != nil {
		return err
	}
	_, err = sqlitedb.Exec("DELETE FROM utxos;")
	if err != nil {
		return err
	}

	// Update coin type in config file
	cf, err := ioutil.ReadFile(path.Join(repoPath, "config"))
	if err != nil {
		return err
	}
	var cfgIface interface{}
	json.Unmarshal(cf, &cfgIface)
	cfgObj, ok := cfgIface.(map[string]interface{})
	if !ok {
		return errors.New("Invalid config file")
	}

	walletIface, ok := cfgObj["Wallet"]
	if !ok {
		return errors.New("Config file missing wallet field")
	}
	walletCfg, ok := walletIface.(map[string]interface{})
	if !ok {
		return errors.New("Invalid config file")
	}
	walletCfg["Type"] = cfgtype
	if strings.ToLower(args[0]) == "zcash" {
		walletCfg["Binary"] = args[1]
	}
	out, err := json.MarshalIndent(cfgObj, "", "   ")
	if err != nil {
		return err
	}
	f, err := os.Create(path.Join(repoPath, "config"))
	if err != nil {
		return err
	}
	_, err = f.Write(out)
	if err != nil {
		return err
	}
	f.Close()

	// Update listings with new coin type
	creationDate := time.Now()
	var sqliteDB *db.SQLiteDatastore
	sqliteDB, err = InitializeRepo(repoPath, x.Password, "", x.Testnet, creationDate)
	if err != nil && err != repo.ErrRepoExists {
		return err
	}

	// If the database cannot be decrypted, exit
	if sqliteDB.Config().IsEncrypted() {
		sqliteDB.Close()
		fmt.Print("Database is encrypted, enter your password: ")
		bytePassword, _ := terminal.ReadPassword(int(syscall.Stdin))
		fmt.Println("")
		pw := string(bytePassword)
		sqliteDB, err = InitializeRepo(repoPath, pw, "", x.Testnet, time.Now())
		if err != nil && err != repo.ErrRepoExists {
			return err
		}
		if sqliteDB.Config().IsEncrypted() {
			PrintError("Invalid password")
			os.Exit(3)
		}
	}
	// Get current identity
	identityKey, err := sqliteDB.Config().GetIdentityKey()
	if err != nil {
		PrintError(err.Error())
		return err
	}
	identity, err := ipfs.IdentityFromKey(identityKey)
	if err != nil {
		return err
	}

	// IPFS node setup
	r, err := fsrepo.Open(repoPath)
	if err != nil {
		PrintError(err.Error())
		return err
	}
	cctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	cfg, err := r.Config()
	if err != nil {
		PrintError(err.Error())
		return err
	}

	cfg.Identity = identity

	ncfg := &ipfscore.BuildCfg{
		Repo:   r,
		Online: true,
		ExtraOpts: map[string]bool{
			"mplex": true,
		},
		DNSResolver: nil,
		Routing:     nil,
	}

	nd, err := ipfscore.NewNode(cctx, ncfg)
	if err != nil {
		PrintError(err.Error())
		return err
	}

	ctx := commands.Context{}
	ctx.Online = false
	ctx.ConfigRoot = repoPath
	ctx.LoadConfig = func(path string) (*config.Config, error) {
		return fsrepo.ConfigAt(repoPath)
	}
	ctx.ConstructNode = func() (*ipfscore.IpfsNode, error) {
		return nd, nil
	}

	files, err := ioutil.ReadDir(path.Join(repoPath, "root", "listings"))
	if err != nil {
		return err
	}

	hashes := make(map[string]string)

	for _, f := range files {
		file, err := ioutil.ReadFile(path.Join(repoPath, "root", "listings", f.Name()))
		if err != nil {
			return err
		}
		sl := new(pb.SignedListing)
		err = jsonpb.UnmarshalString(string(file), sl)
		if err != nil {
			return err
		}
		sl.Listing.Metadata.AcceptedCurrencies = []string{currencyCode}
		sl.Listing.Moderators = []string{}

		serializedListing, err := proto.Marshal(sl.Listing)
		if err != nil {
			return err
		}
		idSig, err := nd.PrivateKey.Sign(serializedListing)
		if err != nil {
			return err
		}
		sl.Signature = idSig

		m := jsonpb.Marshaler{
			EnumsAsInts:  false,
			EmitDefaults: false,
			Indent:       "    ",
			OrigName:     false,
		}
		out, err := m.MarshalToString(sl)
		if err != nil {
			return err
		}

		if err := ioutil.WriteFile(path.Join(repoPath, "root", "listings", f.Name()), []byte(out), os.ModePerm); err != nil {
			return err
		}
		h, err := ipfs.GetHashOfFile(ctx, path.Join(repoPath, "root", "listings", f.Name()))
		if err != nil {
			return err
		}
		hashes[sl.Listing.Slug] = h
	}

	indexPath := path.Join(repoPath, "root", "listings.json")
	indexBytes, err := ioutil.ReadFile(indexPath)
	if err != nil {
		return err
	}
	var index []core.ListingData

	err = json.Unmarshal(indexBytes, &index)
	if err != nil {
		return err
	}

	for i, l := range index {
		h, ok := hashes[l.Slug]
		if !ok {
			return errors.New("Malformatted index file")
		}
		l.Hash = h
		index[i] = l
	}
	// Write it back to file
	ifile, err := os.Create(indexPath)
	if err != nil {
		return err
	}
	defer ifile.Close()

	j, jerr := json.MarshalIndent(index, "", "    ")
	if jerr != nil {
		return jerr
	}
	_, werr := ifile.Write(j)
	if werr != nil {
		return werr
	}

	// Update profile
	pro, err := ioutil.ReadFile(path.Join(repoPath, "root", "profile.json"))

	profile := new(pb.Profile)
	err = jsonpb.UnmarshalString(string(pro), profile)
	if err != nil {
		return err
	}
	if profile.ModeratorInfo != nil {
		profile.ModeratorInfo.AcceptedCurrencies = []string{currencyCode}
		m := jsonpb.Marshaler{
			EnumsAsInts:  false,
			EmitDefaults: false,
			Indent:       "    ",
			OrigName:     false,
		}
		out, err := m.MarshalToString(profile)
		if err != nil {
			return err
		}

		if err := ioutil.WriteFile(path.Join(repoPath, "root", "profile.json"), []byte(out), os.ModePerm); err != nil {
			return err
		}
	}
	nd.Close()

	// Remove moderators from settings
	settings, err := sqliteDB.Settings().Get()
	if err == nil {
		settings.StoreModerators = &[]string{}
		sqliteDB.Settings().Put(settings)
	}

	// Remove headers.bin
	os.Remove(path.Join(repoPath, "headers.bin"))

	fmt.Println("Done")
	return nil
}
<commit_msg>Delete keys table when running convert command<commit_after>package cmd

import (
	"database/sql"
	"encoding/json"

	"context"
	"fmt"
	"github.com/OpenBazaar/jsonpb"
	"github.com/OpenBazaar/openbazaar-go/pb"
	"os"
	"path"

	"github.com/OpenBazaar/openbazaar-go/ipfs"

	"github.com/ipfs/go-ipfs/commands"
	ipfscore "github.com/ipfs/go-ipfs/core"
	"github.com/ipfs/go-ipfs/repo/config"
	"io/ioutil"
	"strings"

	"bufio"
	"errors"
	"github.com/OpenBazaar/openbazaar-go/repo"
	"github.com/OpenBazaar/openbazaar-go/repo/db"

	"github.com/OpenBazaar/openbazaar-go/core"
	"github.com/golang/protobuf/proto"
	"github.com/ipfs/go-ipfs/repo/fsrepo"
	"golang.org/x/crypto/ssh/terminal"
	"syscall"
	"time"
)

type Convert struct {
	Password string `short:"p" long:"password" description:"the encryption password if the database is encrypted"`
	DataDir  string `short:"d" long:"datadir" description:"specify the data directory to be used"`
	Testnet  bool   `short:"t" long:"testnet" description:"use the test network"`
}

func (x *Convert) Execute(args []string) error {
	if len(args) == 0 {
		fmt.Fprintf(os.Stderr, "Please specify the cryptocurrency you wish to convert to. Examples:\nopenbazaar-go convert bitcoin\nopenbazaar-go convert bitcoincash\nopenbazaar-go convert zcash /path/to/zcashd\n")
		return nil
	}
	if strings.ToLower(args[0]) == "zcash" && len(args) == 1 {
		fmt.Fprintf(os.Stderr, "When converting to zcash please specify the path to the zcash binary. Example:\nopenbazaar-go convert zcash /path/to/zcashd\n")
		return nil
	}
	if !(strings.ToLower(args[0]) == "bitcoin" || strings.ToLower(args[0]) == "bitcoincash" || strings.ToLower(args[0]) == "zcash") {
		fmt.Fprintf(os.Stderr, "Unknown currency type: please enter either bitcoin, bitcoincash, or zcash.\n")
		return nil
	}

	var str string
	var cfgtype string
	var currencyCode string
	switch strings.ToLower(args[0]) {
	case "bitcoin":
		str = "Bitcoin"
		cfgtype = "spvwallet"
		currencyCode = "BTC"
	case "bitcoincash":
		str = "Bitcoin Cash"
		cfgtype = "bitcoincash"
		currencyCode = "BCH"
	case "zcash":
		str = "ZCash"
		cfgtype = "zcashd"
		currencyCode = "ZEC"
	}

	if x.Testnet {
		currencyCode = "T" + currencyCode
	}

	reader := bufio.NewReader(os.Stdin)
	fmt.Printf("This command will convert your wallet to %s. It will delete any coins you have in your wallet and will prevent you from completing any outstanding orders. Please make sure you have emptied the wallet and completed all your orders before running this command. You will also need to select a new moderator.\n", str)
	fmt.Println("Are you sure you want to continue (y/n)?")
	yn, _ := reader.ReadString('\n')
	if !(strings.ToLower(yn) == "y\n" || strings.ToLower(yn) == "yes\n" || strings.ToLower(yn)[:1] == "y") {
		fmt.Println("No changes made")
		return nil
	}

	fmt.Println("Are you REALLY sure (y/n)?")
	yn, _ = reader.ReadString('\n')
	if !(strings.ToLower(yn) == "y\n" || strings.ToLower(yn) == "yes\n" || strings.ToLower(yn)[:1] == "y") {
		fmt.Println("No changes made")
		return nil
	}

	fmt.Println("Working...")

	// Set repo path
	repoPath, err := repo.GetRepoPath(x.Testnet)
	if err != nil {
		return err
	}
	if x.DataDir != "" {
		repoPath = x.DataDir
	}

	// Wipe database tables
	var dbPath string
	if x.Testnet {
		dbPath = path.Join(repoPath, "datastore", "testnet.db")
	} else {
		dbPath = path.Join(repoPath, "datastore", "mainnet.db")
	}
	sqlitedb, err := sql.Open("sqlite3", dbPath)
	if err != nil {
		return err
	}
	if x.Password != "" {
		p := "pragma key='" + x.Password + "';"
		sqlitedb.Exec(p)
	}

	_, err = sqlitedb.Exec("DELETE FROM txns;")
	if err != nil {
		return err
	}
	_, err = sqlitedb.Exec("DELETE FROM stxos;")
	if err != nil {
		return err
	}
	_, err = sqlitedb.Exec("DELETE FROM utxos;")
	if err != nil {
		return err
	}
	_, err = sqlitedb.Exec("DELETE FROM keys;")
	if err != nil {
		return err
	}

	// Update coin type in config file
	cf, err := ioutil.ReadFile(path.Join(repoPath, "config"))
	if err != nil {
		return err
	}
	var cfgIface interface{}
	json.Unmarshal(cf, &cfgIface)
	cfgObj, ok := cfgIface.(map[string]interface{})
	if !ok {
		return errors.New("Invalid config file")
	}

	walletIface, ok := cfgObj["Wallet"]
	if !ok {
		return errors.New("Config file missing wallet field")
	}
	walletCfg, ok := walletIface.(map[string]interface{})
	if !ok {
		return errors.New("Invalid config file")
	}
	walletCfg["Type"] = cfgtype
	if strings.ToLower(args[0]) == "zcash" {
		walletCfg["Binary"] = args[1]
	}
	out, err := json.MarshalIndent(cfgObj, "", "   ")
	if err != nil {
		return err
	}
	f, err := os.Create(path.Join(repoPath, "config"))
	if err != nil {
		return err
	}
	_, err = f.Write(out)
	if err != nil {
		return err
	}
	f.Close()

	// Update listings with new coin type
	creationDate := time.Now()
	var sqliteDB *db.SQLiteDatastore
	sqliteDB, err = InitializeRepo(repoPath, x.Password, "", x.Testnet, creationDate)
	if err != nil && err != repo.ErrRepoExists {
		return err
	}

	// If the database cannot be decrypted, exit
	if sqliteDB.Config().IsEncrypted() {
		sqliteDB.Close()
		fmt.Print("Database is encrypted, enter your password: ")
		bytePassword, _ := terminal.ReadPassword(int(syscall.Stdin))
		fmt.Println("")
		pw := string(bytePassword)
		sqliteDB, err = InitializeRepo(repoPath, pw, "", x.Testnet, time.Now())
		if err != nil && err != repo.ErrRepoExists {
			return err
		}
		if sqliteDB.Config().IsEncrypted() {
			PrintError("Invalid password")
			os.Exit(3)
		}
	}
	// Get current identity
	identityKey, err := sqliteDB.Config().GetIdentityKey()
	if err != nil {
		PrintError(err.Error())
		return err
	}
	identity, err := ipfs.IdentityFromKey(identityKey)
	if err != nil {
		return err
	}

	// IPFS node setup
	r, err := fsrepo.Open(repoPath)
	if err != nil {
		PrintError(err.Error())
		return err
	}
	cctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	cfg, err := r.Config()
	if err != nil {
		PrintError(err.Error())
		return err
	}

	cfg.Identity = identity

	ncfg := &ipfscore.BuildCfg{
		Repo:   r,
		Online: true,
		ExtraOpts: map[string]bool{
			"mplex": true,
		},
		DNSResolver: nil,
		Routing:     nil,
	}

	nd, err := ipfscore.NewNode(cctx, ncfg)
	if err != nil {
		PrintError(err.Error())
		return err
	}

	ctx := commands.Context{}
	ctx.Online = false
	ctx.ConfigRoot = repoPath
	ctx.LoadConfig = func(path string) (*config.Config, error) {
		return fsrepo.ConfigAt(repoPath)
	}
	ctx.ConstructNode = func() (*ipfscore.IpfsNode, error) {
		return nd, nil
	}

	files, err := ioutil.ReadDir(path.Join(repoPath, "root", "listings"))
	if err != nil {
		return err
	}

	hashes := make(map[string]string)

	for _, f := range files {
		file, err := ioutil.ReadFile(path.Join(repoPath, "root", "listings", f.Name()))
		if err != nil {
			return err
		}
		sl := new(pb.SignedListing)
		err = jsonpb.UnmarshalString(string(file), sl)
		if err != nil {
			return err
		}
		sl.Listing.Metadata.AcceptedCurrencies = []string{currencyCode}
		sl.Listing.Moderators = []string{}

		serializedListing, err := proto.Marshal(sl.Listing)
		if err != nil {
			return err
		}
		idSig, err := nd.PrivateKey.Sign(serializedListing)
		if err != nil {
			return err
		}
		sl.Signature = idSig

		m := jsonpb.Marshaler{
			EnumsAsInts:  false,
			EmitDefaults: false,
			Indent:       "    ",
			OrigName:     false,
		}
		out, err := m.MarshalToString(sl)
		if err != nil {
			return err
		}

		if err := ioutil.WriteFile(path.Join(repoPath, "root", "listings", f.Name()), []byte(out), os.ModePerm); err != nil {
			return err
		}
		h, err := ipfs.GetHashOfFile(ctx, path.Join(repoPath, "root", "listings", f.Name()))
		if err != nil {
			return err
		}
		hashes[sl.Listing.Slug] = h
	}

	indexPath := path.Join(repoPath, "root", "listings.json")
	indexBytes, err := ioutil.ReadFile(indexPath)
	if err != nil {
		return err
	}
	var index []core.ListingData

	err = json.Unmarshal(indexBytes, &index)
	if err != nil {
		return err
	}

	for i, l := range index {
		h, ok := hashes[l.Slug]
		if !ok {
			return errors.New("Malformatted index file")
		}
		l.Hash = h
		index[i] = l
	}
	// Write it back to file
	ifile, err := os.Create(indexPath)
	if err != nil {
		return err
	}
	defer ifile.Close()

	j, jerr := json.MarshalIndent(index, "", "    ")
	if jerr != nil {
		return jerr
	}
	_, werr := ifile.Write(j)
	if werr != nil {
		return werr
	}

	// Update profile
	pro, err := ioutil.ReadFile(path.Join(repoPath, "root", "profile.json"))

	profile := new(pb.Profile)
	err = jsonpb.UnmarshalString(string(pro), profile)
	if err != nil {
		return err
	}
	if profile.ModeratorInfo != nil {
		profile.ModeratorInfo.AcceptedCurrencies = []string{currencyCode}
		m := jsonpb.Marshaler{
			EnumsAsInts:  false,
			EmitDefaults: false,
			Indent:       "    ",
			OrigName:     false,
		}
		out, err := m.MarshalToString(profile)
		if err != nil {
			return err
		}

		if err := ioutil.WriteFile(path.Join(repoPath, "root", "profile.json"), []byte(out), os.ModePerm); err != nil {
			return err
		}
	}
	nd.Close()

	// Remove moderators from settings
	settings, err := sqliteDB.Settings().Get()
	if err == nil {
		settings.StoreModerators = &[]string{}
		sqliteDB.Settings().Put(settings)
	}

	// Remove headers.bin
	os.Remove(path.Join(repoPath, "headers.bin"))

	fmt.Println("Done")
	return nil
}
<|endoftext|>


<commit_before>// Copyright © 2017 The VirusTotal CLI authors. All Rights Reserved.
// 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 cmd

import (
	"bufio"
	"errors"
	"fmt"
	"os"
	"regexp"
	"strconv"
	"sync"

	vt "github.com/VirusTotal/vt-go"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

var notificationsDeleteCmdHelp = `Delete hunting notifications.

This command deletes the malware hunting notifications associated to the
currently configured API key.`

// NewHuntingNotificationDeleteCmd returns a command for deleting all hunting
// notifications for the current user.
func NewHuntingNotificationDeleteCmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "delete [notification id]...",
		Short: "Delete hunting notifications",
		Long:  notificationsDeleteCmdHelp,
		RunE: func(cmd *cobra.Command, args []string) error {
			deleteAll := viper.GetBool("all")
			deleteTag := viper.GetString("with-tag")
			if len(args) == 0 && !deleteAll && deleteTag == "" {
				return errors.New("Specify notification id or use --all or --with-tag")
			}
			client, err := NewAPIClient()
			if err != nil {
				return err
			}
			if deleteAll {
				_, err = client.Delete(vt.URL("intelligence/hunting_notifications"))
			} else if deleteTag != "" {
				_, err = client.Delete(vt.URL("intelligence/hunting_notifications?tag=%s", deleteTag))
			} else {
				var wg sync.WaitGroup
				for _, arg := range args {
					wg.Add(1)
					go func(notificationID string) {
						url := vt.URL("intelligence/hunting_notifications/%s", notificationID)
						if _, err := client.Delete(url); err != nil {
							fmt.Fprintf(os.Stderr, "%v\n", err)
						}
						wg.Done()
					}(arg)
				}
				wg.Wait()
			}
			return err
		},
	}

	cmd.Flags().BoolP("all", "a", false, "delete all notifications")
	cmd.Flags().StringP("with-tag", "t", "", "delete notifications with a given tag")

	return cmd
}

var notificationsListCmdHelp = `List malware hunting notifications.

This command list the malware hunting notifications associated to the currently
configured API key.`

// NewHuntingNotificationListCmd returns a new instance of the 'notifications list' command.
func NewHuntingNotificationListCmd() *cobra.Command {
	cmd := &cobra.Command{
		Aliases: []string{"ls"},
		Use:     "list",
		Short:   "List notifications",
		Long:    notificationsListCmdHelp,

		RunE: func(cmd *cobra.Command, args []string) error {
			p, err := NewPrinter(cmd)
			if err != nil {
				return err
			}
			return p.PrintCollection(vt.URL("intelligence/hunting_notifications?relationships=file"))
		},
	}

	addIncludeExcludeFlags(cmd.Flags())
	addFilterFlag(cmd.Flags())
	addLimitFlag(cmd.Flags())
	addCursorFlag(cmd.Flags())

	cmd.AddCommand(NewHuntingNotificationDeleteCmd())

	return cmd
}

// NewHuntingNotificationCmd returns a new instance of the 'notifications' command.
func NewHuntingNotificationCmd() *cobra.Command {
	cmd := &cobra.Command{
		Aliases: []string{"nt"},
		Use:     "notification [id]...",
		Short:   "Manage malware hunting notifications",
		Args:    cobra.MinimumNArgs(1),

		RunE: func(cmd *cobra.Command, args []string) error {
			re, _ := regexp.Compile("\\d+")
			p, err := NewPrinter(cmd)
			if err != nil {
				return err
			}
			return p.GetAndPrintObjects("intelligence/hunting_notifications/%s", args, re)
		},
	}

	addThreadsFlag(cmd.Flags())

	cmd.AddCommand(NewHuntingNotificationListCmd())
	cmd.AddCommand(NewHuntingNotificationDeleteCmd())

	return cmd
}

var rulesetsListCmdHelp = `List malware hunting rulesets.

This command list the malware hunting rulesets associated to the currently
configured API key.`

// NewHuntingRulesetListCmd returns a new instance of the 'rulesets list' command.
func NewHuntingRulesetListCmd() *cobra.Command {
	cmd := &cobra.Command{
		Aliases: []string{"ls"},
		Use:     "list",
		Short:   "List rulesets",
		Long:    rulesetsListCmdHelp,

		RunE: func(cmd *cobra.Command, args []string) error {
			p, err := NewPrinter(cmd)
			if err != nil {
				return err
			}
			return p.PrintCollection(vt.URL("intelligence/hunting_rulesets?relationships=owner,editors"))
		},
	}

	addIncludeExcludeFlags(cmd.Flags())
	addFilterFlag(cmd.Flags())
	addLimitFlag(cmd.Flags())
	addCursorFlag(cmd.Flags())
	addIDOnlyFlag(cmd.Flags())

	return cmd
}

func patchRuleset(id, attr string, value interface{}) error {
	client, err := NewAPIClient()
	if err != nil {
		return err
	}
	obj := vt.NewObjectWithID("hunting_ruleset", id)
	obj.Set(attr, value)
	return client.PatchObject(vt.URL("intelligence/hunting_rulesets/%s", id), obj)
}

// NewHuntingRulesetDisableCmd returns a command for disabling a given ruleset.
func NewHuntingRulesetDisableCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "disable [ruleset id]",
		Short: "Disable ruleset",
		Args:  cobra.ExactArgs(1),

		RunE: func(cmd *cobra.Command, args []string) error {
			return patchRuleset(args[0], "enabled", false)
		},
	}
}

// NewHuntingRulesetEnableCmd returns a command for enabling a given ruleset.
func NewHuntingRulesetEnableCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "enable [ruleset id]",
		Short: "Enable ruleset",
		Args:  cobra.ExactArgs(1),

		RunE: func(cmd *cobra.Command, args []string) error {
			return patchRuleset(args[0], "enabled", true)
		},
	}
}

// NewHuntingRulesetRenameCmd returns a command for renaming a given ruleset.
func NewHuntingRulesetRenameCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "rename [ruleset id] [name]",
		Short: "Rename ruleset",
		Args:  cobra.ExactArgs(2),

		RunE: func(cmd *cobra.Command, args []string) error {
			return patchRuleset(args[0], "name", args[1])
		},
	}
}

// NewHuntingRulesetSetLimitCmd returns a command for changing a ruleset's limit.
func NewHuntingRulesetSetLimitCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "setlimit [ruleset id] [limit]",
		Short: "Set ruleset limit",
		Args:  cobra.MinimumNArgs(2),

		RunE: func(cmd *cobra.Command, args []string) error {
			limit, err := strconv.Atoi(args[1])
			if err != nil {
				return fmt.Errorf("invalid limit: %s", args[1])
			}
			return patchRuleset(args[0], "limit", limit)
		},
	}
}

// NewHuntingRulesetUpdateCmd returns a command for updating ruleset's rules.
func NewHuntingRulesetUpdateCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "update [ruleset id] [rules file]",
		Short: "Change the rules for a ruleset.",
		Args:  cobra.MinimumNArgs(2),

		RunE: func(cmd *cobra.Command, args []string) error {
			rules, err := ReadFile(args[1])
			if err != nil {
				return err
			}
			return patchRuleset(args[0], "rules", string(rules))
		},
	}
}

// NewHuntingRulesetDeleteCmd returns a command for deleting a given ruleset.
func NewHuntingRulesetDeleteCmd() *cobra.Command {
	cmd := &cobra.Command{
		Aliases: []string{"del", "rm"},
		Use:     "delete [ruleset id]...",
		Short:   "Delete rulesets",

		RunE: func(cmd *cobra.Command, args []string) error {
			client, err := NewAPIClient()
			if err != nil {
				return err
			}
			deleteAll := viper.GetBool("all")
			if len(args) == 0 && !deleteAll {
				return errors.New("Specify ruleset id or use --all")
			}
			if deleteAll {
				fmt.Print("Enter your VirusTotal username to confirm: ")
				scanner := bufio.NewScanner(os.Stdin)
				scanner.Scan()
				username := scanner.Text()
				_, err = client.Delete(
					vt.URL("intelligence/hunting_rulesets"),
					vt.WithHeader("x-confirm-delete", username))
			} else {
				var wg sync.WaitGroup
				for _, arg := range args {
					wg.Add(1)
					go func(rulesetID string) {
						url := vt.URL("intelligence/hunting_rulesets/%s", rulesetID)
						if _, err := client.Delete(url); err != nil {
							fmt.Fprintf(os.Stderr, "%v\n", err)
						}
						wg.Done()
					}(arg)
				}
				wg.Wait()
			}
			return err
		},
	}

	cmd.Flags().BoolP("all", "a", false, "delete all rulesets")

	return cmd
}

// NewHuntingRulesetAddCmd returns a command for adding a new ruleset.
func NewHuntingRulesetAddCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "add [ruleset name] [rules file]",
		Short: "Add a new ruleset",
		Args:  cobra.ExactArgs(2),

		RunE: func(cmd *cobra.Command, args []string) error {
			client, err := NewAPIClient()
			if err != nil {
				return err
			}
			p, err := NewPrinter(cmd)
			if err != nil {
				return err
			}
			rules, err := ReadFile(args[1])
			if err != nil {
				return err
			}
			obj := vt.NewObject("hunting_ruleset")
			obj.SetString("name", args[0])
			obj.SetString("rules", string(rules))

			err = client.PostObject(vt.URL("intelligence/hunting_rulesets"), obj)
			if err != nil {
				return err
			}
			return p.PrintObject(obj)
		},
	}
}

// NewHuntingRulesetCmd returns a new instance of the 'rulesets' command.
func NewHuntingRulesetCmd() *cobra.Command {

	cmd := &cobra.Command{
		Aliases: []string{"rs"},
		Use:     "ruleset [id]...",
		Short:   "Manage hunting rulesets",
		Args:    cobra.MinimumNArgs(1),

		RunE: func(cmd *cobra.Command, args []string) error {
			re, _ := regexp.Compile("\\d+")
			p, err := NewPrinter(cmd)
			if err != nil {
				return err
			}
			return p.GetAndPrintObjects("intelligence/hunting_rulesets/%s?relationships=owner,editors", args, re)
		},
	}

	addThreadsFlag(cmd.Flags())

	cmd.AddCommand(NewHuntingRulesetAddCmd())
	cmd.AddCommand(NewHuntingRulesetDeleteCmd())
	cmd.AddCommand(NewHuntingRulesetDisableCmd())
	cmd.AddCommand(NewHuntingRulesetEnableCmd())
	cmd.AddCommand(NewHuntingRulesetListCmd())
	cmd.AddCommand(NewHuntingRulesetRenameCmd())
	cmd.AddCommand(NewHuntingRulesetSetLimitCmd())
	cmd.AddCommand(NewHuntingRulesetUpdateCmd())

	return cmd
}

// NewHuntingCmd returns a new instance of the 'hunting' command.
func NewHuntingCmd() *cobra.Command {

	cmd := &cobra.Command{
		Aliases: []string{"ht"},
		Use:     "hunting [subcommand]",
		Short:   "Manage malware hunting rules and notifications",
	}

	cmd.AddCommand(NewHuntingNotificationCmd())
	cmd.AddCommand(NewHuntingRulesetCmd())

	return cmd
}
<commit_msg>Fix command use string.<commit_after>// Copyright © 2017 The VirusTotal CLI authors. All Rights Reserved.
// 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 cmd

import (
	"bufio"
	"errors"
	"fmt"
	"os"
	"regexp"
	"strconv"
	"sync"

	vt "github.com/VirusTotal/vt-go"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

var notificationsDeleteCmdHelp = `Delete hunting notifications.

This command deletes the malware hunting notifications associated to the
currently configured API key.`

// NewHuntingNotificationDeleteCmd returns a command for deleting all hunting
// notifications for the current user.
func NewHuntingNotificationDeleteCmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "delete [notification id]...",
		Short: "Delete hunting notifications",
		Long:  notificationsDeleteCmdHelp,
		RunE: func(cmd *cobra.Command, args []string) error {
			deleteAll := viper.GetBool("all")
			deleteTag := viper.GetString("with-tag")
			if len(args) == 0 && !deleteAll && deleteTag == "" {
				return errors.New("Specify notification id or use --all or --with-tag")
			}
			client, err := NewAPIClient()
			if err != nil {
				return err
			}
			if deleteAll {
				_, err = client.Delete(vt.URL("intelligence/hunting_notifications"))
			} else if deleteTag != "" {
				_, err = client.Delete(vt.URL("intelligence/hunting_notifications?tag=%s", deleteTag))
			} else {
				var wg sync.WaitGroup
				for _, arg := range args {
					wg.Add(1)
					go func(notificationID string) {
						url := vt.URL("intelligence/hunting_notifications/%s", notificationID)
						if _, err := client.Delete(url); err != nil {
							fmt.Fprintf(os.Stderr, "%v\n", err)
						}
						wg.Done()
					}(arg)
				}
				wg.Wait()
			}
			return err
		},
	}

	cmd.Flags().BoolP("all", "a", false, "delete all notifications")
	cmd.Flags().StringP("with-tag", "t", "", "delete notifications with a given tag")

	return cmd
}

var notificationsListCmdHelp = `List malware hunting notifications.

This command list the malware hunting notifications associated to the currently
configured API key.`

// NewHuntingNotificationListCmd returns a new instance of the 'notifications list' command.
func NewHuntingNotificationListCmd() *cobra.Command {
	cmd := &cobra.Command{
		Aliases: []string{"ls"},
		Use:     "list",
		Short:   "List notifications",
		Long:    notificationsListCmdHelp,

		RunE: func(cmd *cobra.Command, args []string) error {
			p, err := NewPrinter(cmd)
			if err != nil {
				return err
			}
			return p.PrintCollection(vt.URL("intelligence/hunting_notifications?relationships=file"))
		},
	}

	addIncludeExcludeFlags(cmd.Flags())
	addFilterFlag(cmd.Flags())
	addLimitFlag(cmd.Flags())
	addCursorFlag(cmd.Flags())

	cmd.AddCommand(NewHuntingNotificationDeleteCmd())

	return cmd
}

// NewHuntingNotificationCmd returns a new instance of the 'notifications' command.
func NewHuntingNotificationCmd() *cobra.Command {
	cmd := &cobra.Command{
		Aliases: []string{"nt"},
		Use:     "notification [id]...",
		Short:   "Manage malware hunting notifications",
		Args:    cobra.MinimumNArgs(1),

		RunE: func(cmd *cobra.Command, args []string) error {
			re, _ := regexp.Compile("\\d+")
			p, err := NewPrinter(cmd)
			if err != nil {
				return err
			}
			return p.GetAndPrintObjects("intelligence/hunting_notifications/%s", args, re)
		},
	}

	addThreadsFlag(cmd.Flags())

	cmd.AddCommand(NewHuntingNotificationListCmd())
	cmd.AddCommand(NewHuntingNotificationDeleteCmd())

	return cmd
}

var rulesetsListCmdHelp = `List malware hunting rulesets.

This command list the malware hunting rulesets associated to the currently
configured API key.`

// NewHuntingRulesetListCmd returns a new instance of the 'rulesets list' command.
func NewHuntingRulesetListCmd() *cobra.Command {
	cmd := &cobra.Command{
		Aliases: []string{"ls"},
		Use:     "list",
		Short:   "List rulesets",
		Long:    rulesetsListCmdHelp,

		RunE: func(cmd *cobra.Command, args []string) error {
			p, err := NewPrinter(cmd)
			if err != nil {
				return err
			}
			return p.PrintCollection(vt.URL("intelligence/hunting_rulesets?relationships=owner,editors"))
		},
	}

	addIncludeExcludeFlags(cmd.Flags())
	addFilterFlag(cmd.Flags())
	addLimitFlag(cmd.Flags())
	addCursorFlag(cmd.Flags())
	addIDOnlyFlag(cmd.Flags())

	return cmd
}

func patchRuleset(id, attr string, value interface{}) error {
	client, err := NewAPIClient()
	if err != nil {
		return err
	}
	obj := vt.NewObjectWithID("hunting_ruleset", id)
	obj.Set(attr, value)
	return client.PatchObject(vt.URL("intelligence/hunting_rulesets/%s", id), obj)
}

// NewHuntingRulesetDisableCmd returns a command for disabling a given ruleset.
func NewHuntingRulesetDisableCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "disable [ruleset id]",
		Short: "Disable ruleset",
		Args:  cobra.ExactArgs(1),

		RunE: func(cmd *cobra.Command, args []string) error {
			return patchRuleset(args[0], "enabled", false)
		},
	}
}

// NewHuntingRulesetEnableCmd returns a command for enabling a given ruleset.
func NewHuntingRulesetEnableCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "enable [ruleset id]",
		Short: "Enable ruleset",
		Args:  cobra.ExactArgs(1),

		RunE: func(cmd *cobra.Command, args []string) error {
			return patchRuleset(args[0], "enabled", true)
		},
	}
}

// NewHuntingRulesetRenameCmd returns a command for renaming a given ruleset.
func NewHuntingRulesetRenameCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "rename [ruleset id] [name]",
		Short: "Rename ruleset",
		Args:  cobra.ExactArgs(2),

		RunE: func(cmd *cobra.Command, args []string) error {
			return patchRuleset(args[0], "name", args[1])
		},
	}
}

// NewHuntingRulesetSetLimitCmd returns a command for changing a ruleset's limit.
func NewHuntingRulesetSetLimitCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "setlimit [ruleset id] [limit]",
		Short: "Set ruleset limit",
		Args:  cobra.MinimumNArgs(2),

		RunE: func(cmd *cobra.Command, args []string) error {
			limit, err := strconv.Atoi(args[1])
			if err != nil {
				return fmt.Errorf("invalid limit: %s", args[1])
			}
			return patchRuleset(args[0], "limit", limit)
		},
	}
}

// NewHuntingRulesetUpdateCmd returns a command for updating ruleset's rules.
func NewHuntingRulesetUpdateCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "update [ruleset id] [rules file]",
		Short: "Change the rules for a ruleset.",
		Args:  cobra.MinimumNArgs(2),

		RunE: func(cmd *cobra.Command, args []string) error {
			rules, err := ReadFile(args[1])
			if err != nil {
				return err
			}
			return patchRuleset(args[0], "rules", string(rules))
		},
	}
}

// NewHuntingRulesetDeleteCmd returns a command for deleting a given ruleset.
func NewHuntingRulesetDeleteCmd() *cobra.Command {
	cmd := &cobra.Command{
		Aliases: []string{"del", "rm"},
		Use:     "delete [ruleset id]...",
		Short:   "Delete rulesets",

		RunE: func(cmd *cobra.Command, args []string) error {
			client, err := NewAPIClient()
			if err != nil {
				return err
			}
			deleteAll := viper.GetBool("all")
			if len(args) == 0 && !deleteAll {
				return errors.New("Specify ruleset id or use --all")
			}
			if deleteAll {
				fmt.Print("Enter your VirusTotal username to confirm: ")
				scanner := bufio.NewScanner(os.Stdin)
				scanner.Scan()
				username := scanner.Text()
				_, err = client.Delete(
					vt.URL("intelligence/hunting_rulesets"),
					vt.WithHeader("x-confirm-delete", username))
			} else {
				var wg sync.WaitGroup
				for _, arg := range args {
					wg.Add(1)
					go func(rulesetID string) {
						url := vt.URL("intelligence/hunting_rulesets/%s", rulesetID)
						if _, err := client.Delete(url); err != nil {
							fmt.Fprintf(os.Stderr, "%v\n", err)
						}
						wg.Done()
					}(arg)
				}
				wg.Wait()
			}
			return err
		},
	}

	cmd.Flags().BoolP("all", "a", false, "delete all rulesets")

	return cmd
}

// NewHuntingRulesetAddCmd returns a command for adding a new ruleset.
func NewHuntingRulesetAddCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "add [ruleset name] [rules file]",
		Short: "Add a new ruleset",
		Args:  cobra.ExactArgs(2),

		RunE: func(cmd *cobra.Command, args []string) error {
			client, err := NewAPIClient()
			if err != nil {
				return err
			}
			p, err := NewPrinter(cmd)
			if err != nil {
				return err
			}
			rules, err := ReadFile(args[1])
			if err != nil {
				return err
			}
			obj := vt.NewObject("hunting_ruleset")
			obj.SetString("name", args[0])
			obj.SetString("rules", string(rules))

			err = client.PostObject(vt.URL("intelligence/hunting_rulesets"), obj)
			if err != nil {
				return err
			}
			return p.PrintObject(obj)
		},
	}
}

// NewHuntingRulesetCmd returns a new instance of the 'rulesets' command.
func NewHuntingRulesetCmd() *cobra.Command {

	cmd := &cobra.Command{
		Aliases: []string{"rs"},
		Use:     "ruleset [id]...",
		Short:   "Manage hunting rulesets",
		Args:    cobra.MinimumNArgs(1),

		RunE: func(cmd *cobra.Command, args []string) error {
			re, _ := regexp.Compile("\\d+")
			p, err := NewPrinter(cmd)
			if err != nil {
				return err
			}
			return p.GetAndPrintObjects("intelligence/hunting_rulesets/%s?relationships=owner,editors", args, re)
		},
	}

	addThreadsFlag(cmd.Flags())

	cmd.AddCommand(NewHuntingRulesetAddCmd())
	cmd.AddCommand(NewHuntingRulesetDeleteCmd())
	cmd.AddCommand(NewHuntingRulesetDisableCmd())
	cmd.AddCommand(NewHuntingRulesetEnableCmd())
	cmd.AddCommand(NewHuntingRulesetListCmd())
	cmd.AddCommand(NewHuntingRulesetRenameCmd())
	cmd.AddCommand(NewHuntingRulesetSetLimitCmd())
	cmd.AddCommand(NewHuntingRulesetUpdateCmd())

	return cmd
}

// NewHuntingCmd returns a new instance of the 'hunting' command.
func NewHuntingCmd() *cobra.Command {

	cmd := &cobra.Command{
		Aliases: []string{"ht"},
		Use:     "hunting",
		Short:   "Manage malware hunting rules and notifications",
	}

	cmd.AddCommand(NewHuntingNotificationCmd())
	cmd.AddCommand(NewHuntingRulesetCmd())

	return cmd
}
<|endoftext|>


<commit_before>package cmd

import (
	"github.com/codegangsta/cli"
)

var Migrate = cli.Command{
	Name:   "migrate",
	Usage:  "Install & upgrade database to latest version",
	Subcommands: []cli.Command{
		{
			Name:  "version",
			Usage: "version of the database",
			Action: getversion,
		},
		{
			Name:  "up",
			Usage: "upgrade the database",
			Action: upgrade,
		},
		{
			Name:  "down",
			Usage: "downgrade the database",
			Action: downgrade,
		},
	},
}

func upgrade(ctx *cli.Context) {
	println("Will ugprade the datbase in the future")
}

func downgrade(ctx *cli.Context) {
	println("Will downgrade the datbase in the future")
}

func getversion(ctx *cli.Context) {
	println("Still version 0")
}<commit_msg>Fixed case on function name<commit_after>package cmd

import (
	"github.com/codegangsta/cli"
)

var Migrate = cli.Command{
	Name:   "migrate",
	Usage:  "Install & upgrade database to latest version",
	Subcommands: []cli.Command{
		{
			Name:  "version",
			Usage: "version of the database",
			Action: getVersion,
		},
		{
			Name:  "up",
			Usage: "upgrade the database",
			Action: upgrade,
		},
		{
			Name:  "down",
			Usage: "downgrade the database",
			Action: downgrade,
		},
	},
}

func upgrade(ctx *cli.Context) {
	println("Will ugprade the datbase in the future")
}

func downgrade(ctx *cli.Context) {
	println("Will downgrade the datbase in the future")
}

func getVersion(ctx *cli.Context) {
	println("Still version 0")
}<|endoftext|>