<commit_before>package main

import (
	"context"
	"fmt"
	"os"
	"os/signal"
	"time"
	"webup/backr"
	"webup/backr/http"
	"webup/backr/privatehttp"
	"webup/backr/randstr"
	"webup/backr/state"
	"webup/backr/tasks"

	"io/ioutil"

	jwt "github.com/dgrijalva/jwt-go"
	"github.com/evalphobia/logrus_sentry"
	cli "github.com/jawher/mow.cli"
	homedir "github.com/mitchellh/go-homedir"
	log "github.com/sirupsen/logrus"
)

func main() {
	app := cli.App("backr", "Perform backups")

	app.Version("v version", "Backr 4 (build 9)")

	log.SetFormatter(&log.TextFormatter{FullTimestamp: true})

	app.Command("daemon", "Start the backup process", func(cmd *cli.Cmd) {

		// cmd.Spec = "-w... --etcd|--local [--time] [--config-refresh-rate]"
		cmd.Spec = "-w... --etcd|--local [--time] [--secret-file-path] [--api-listen] [--debug] [--sentry-dsn]"

		// state storage
		stateStorageSettings := getStateStorateSettings(cmd)

		// swift
		swiftSettings := getSwiftSettings(cmd)
		if swiftSettings == nil {
			log.Warnln("Swift upload will be unavailable because some args or env vars are missing to configure Swift upload")
		}

		// options
		watchDirs := cmd.StringsOpt("w watch", []string{}, "Specifies the directories to watch for finding backup.yml files")
		timeOpt := cmd.StringOpt("time", "01:00", "Specifies the moment when the backup process will be started")
		secretFilePath := cmd.StringOpt("secret-file-path", "~/.backr/jwt_secret", "Path to the file storing the secret used for generating access token to backup files")
		apiListenOpt := cmd.StringOpt("api-listen", ":22257", "Configure IP and port for HTTP API")
		debug := cmd.BoolOpt("debug", false, "Enables the debug logs output")
		sentryDSN := cmd.StringOpt("sentry-dsn", "", "DSN for sending logs to Sentry")

		cmd.Action = func() {

			// set debug log level if needed
			if *debug {
				log.SetLevel(log.DebugLevel)
			}

			if *sentryDSN != "" {
				hook, err := logrus_sentry.NewSentryHook(*sentryDSN, []log.Level{
					log.PanicLevel,
					log.FatalLevel,
					log.ErrorLevel,
				})
				if err == nil {
					log.AddHook(hook)
				}
			}

			ctx, cancel := context.WithCancel(context.Background())

			// prepare options
			currentSettings := backr.NewDefaultSettings()
			currentSettings.StateStorage = stateStorageSettings
			currentSettings.WatchDirs = *watchDirs
			currentSettings.Swift = swiftSettings
			currentSettings.ApiListen = *apiListenOpt

			path, _ := homedir.Expand(*secretFilePath)
			currentSettings.SecretFilepath = path

			// parse the time option
			if timeOpt != nil {
				parsedTime, err := time.Parse("15:04", *timeOpt)
				if err == nil {
					currentSettings.TimeSpec.Hour = parsedTime.Hour()
					currentSettings.TimeSpec.Minute = parsedTime.Minute()
				} else {
					log.Warnf("Time option is not correctly formatted, must be like '00:00'. Default option will be used instead")
				}
			}

			ctx = backr.NewContextWithSettings(ctx, currentSettings)

			// handle the SIGINT signal
			waiting := make(chan os.Signal, 1)
			signal.Notify(waiting, os.Interrupt, os.Kill)

			// prepare ticker
			ticker := time.NewTicker(5 * time.Minute) // 1 minute

			go func() {
				isRunning := false

				for {
					select {
					case <-ticker.C:
						log.Debugln("Tick received")

						if !isRunning {
							isRunning = true

							// execute the update of state from specs (yml files)
							tasks.UpdateStateFromSpec(ctx)
							// execute the backup routine
							tasks.PerformBackup(ctx)

							isRunning = false
						} else {
							log.Infoln("Backup process is already running. Skipping.")
						}
					}
				}
			}()

			// start HTTP API daemons
			startAPI(ctx)
			startPrivateAPI(ctx)

			// waiting for signal
			<-waiting
			// stop the ticker
			ticker.Stop()
			// cancelling ctx
			cancel()
			// cleanup current state storage
			state.CleanupStorage(currentSettings)

			log.Infoln("Stopped.")
		}
	})

	app.Command("token", "Create a token to access to backup archives", func(cmd *cli.Cmd) {

		cmd.Spec = "[--secret-file-path] [-q]"

		secretFilePath := cmd.StringOpt("secret-file-path", "~/.backr/jwt_secret", "Path to the file storing the secret used for generating access token to backup files")
		quiet := cmd.BoolOpt("q quiet", false, "Just display the token")

		cmd.Action = func() {

			filepath, _ := homedir.Expand(*secretFilePath)

			secret, err := ioutil.ReadFile(filepath)
			if err != nil {
				if !*quiet {
					fmt.Println("Secret file not found. Create it at", filepath)
				}
				secret = randstr.SecureRandomBytes(64)
				err := ioutil.WriteFile(filepath, secret, 0600)
				if err != nil {
					fmt.Println("Unable to create secret file")
					fmt.Println(err)
					cli.Exit(1)
				}
			}

			// Create a new token object, specifying signing method and the claims
			// you would like it to contain.
			token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
				"exp": time.Now().Add(24 * time.Hour).Unix(),
			})

			// Sign and get the complete encoded token as a string using the secret
			tokenString, err := token.SignedString(secret)
			if err != nil {
				fmt.Println("Unable to generate token")
				fmt.Println(err)
				cli.Exit(1)
			}

			fmt.Println(tokenString)
		}

	})

	app.Command("now", "Execute a backup immediately", func(cmd *cli.Cmd) {

		cmd.Spec = "[--url] PROJECT_NAME"

		url := cmd.StringOpt("url", "http://127.0.0.1:22258", "URL of private API")
		projectName := cmd.StringArg("PROJECT_NAME", "", "A project name configured inside backr")

		cmd.Action = func() {
			client := privatehttp.NewClient(*url)
			info, err := client.Backup(*projectName)
			if err != nil {
				fmt.Printf("ERROR: %v\n", err)
			}

			fmt.Println("name:", info.Name)
			fmt.Println("url:", info.URL)
		}

	})

	app.Run(os.Args)
}

func getSwiftSettings(cmd *cli.Cmd) *backr.SwiftSettings {
	url := cmd.String(cli.StringOpt{
		Name:   "swift-auth-url",
		Value:  "",
		Desc:   "Swift auth URL",
		EnvVar: "OS_AUTH_URL",
	})
	user := cmd.String(cli.StringOpt{
		Name:   "swift-user",
		Value:  "",
		Desc:   "Swift username",
		EnvVar: "OS_USERNAME",
	})
	apiKey := cmd.String(cli.StringOpt{
		Name:   "swift-password",
		Value:  "",
		Desc:   "Swift API Key / Password",
		EnvVar: "OS_PASSWORD",
	})
	tenantName := cmd.String(cli.StringOpt{
		Name:   "swift-tenant-name",
		Value:  "",
		Desc:   "Swift Tenant name",
		EnvVar: "OS_TENANT_NAME",
	})
	region := cmd.String(cli.StringOpt{
		Name:   "swift-region",
		Value:  "",
		Desc:   "Swift region name",
		EnvVar: "OS_REGION_NAME",
	})

	if *url != "" && *user != "" && *apiKey != "" && *tenantName != "" {
		return &backr.SwiftSettings{
			AuthURL:       *url,
			User:          *user,
			APIKey:        *apiKey,
			TenantName:    *tenantName,
			Region:        *region,
			ContainerName: "backups",
		}
	}

	return nil
}

func getStateStorateSettings(cmd *cli.Cmd) backr.StateStorageSettings {
	etcdEndpoints := cmd.String(cli.StringOpt{
		Name:   "etcd",
		Value:  "http://localhost:2379",
		Desc:   "Endpoints for etcd (separated by a comma)",
		EnvVar: "ETCD_ADVERTISE_URLS",
	})

	localPath := cmd.String(cli.StringOpt{
		Name:   "local",
		Value:  "",
		Desc:   "Local directory where the state will be stored (r/w permissions required)",
		EnvVar: "STATE_STORAGE_LOCAL",
	})

	return backr.StateStorageSettings{
		EtcdEndpoints: etcdEndpoints,
		LocalPath:     localPath,
	}
}

func startAPI(ctx context.Context) {
	go func() {
		api := http.NewAPI()
		api.Listen(ctx)
	}()
}

func startPrivateAPI(ctx context.Context) {
	go func() {
		api := privatehttp.NewAPI()
		api.Listen(ctx)
	}()
}
<commit_msg>v5 build 10<commit_after>package main

import (
	"context"
	"fmt"
	"os"
	"os/signal"
	"time"
	"webup/backr"
	"webup/backr/http"
	"webup/backr/privatehttp"
	"webup/backr/randstr"
	"webup/backr/state"
	"webup/backr/tasks"

	"io/ioutil"

	jwt "github.com/dgrijalva/jwt-go"
	"github.com/evalphobia/logrus_sentry"
	cli "github.com/jawher/mow.cli"
	homedir "github.com/mitchellh/go-homedir"
	log "github.com/sirupsen/logrus"
)

func main() {
	app := cli.App("backr", "Perform backups")

	app.Version("v version", "Backr 5 (build 10)")

	log.SetFormatter(&log.TextFormatter{FullTimestamp: true})

	app.Command("daemon", "Start the backup process", func(cmd *cli.Cmd) {

		// cmd.Spec = "-w... --etcd|--local [--time] [--config-refresh-rate]"
		cmd.Spec = "-w... --etcd|--local [--time] [--secret-file-path] [--api-listen] [--debug] [--sentry-dsn]"

		// state storage
		stateStorageSettings := getStateStorateSettings(cmd)

		// swift
		swiftSettings := getSwiftSettings(cmd)
		if swiftSettings == nil {
			log.Warnln("Swift upload will be unavailable because some args or env vars are missing to configure Swift upload")
		}

		// options
		watchDirs := cmd.StringsOpt("w watch", []string{}, "Specifies the directories to watch for finding backup.yml files")
		timeOpt := cmd.StringOpt("time", "01:00", "Specifies the moment when the backup process will be started")
		secretFilePath := cmd.StringOpt("secret-file-path", "~/.backr/jwt_secret", "Path to the file storing the secret used for generating access token to backup files")
		apiListenOpt := cmd.StringOpt("api-listen", ":22257", "Configure IP and port for HTTP API")
		debug := cmd.BoolOpt("debug", false, "Enables the debug logs output")
		sentryDSN := cmd.StringOpt("sentry-dsn", "", "DSN for sending logs to Sentry")

		cmd.Action = func() {

			// set debug log level if needed
			if *debug {
				log.SetLevel(log.DebugLevel)
			}

			if *sentryDSN != "" {
				hook, err := logrus_sentry.NewSentryHook(*sentryDSN, []log.Level{
					log.PanicLevel,
					log.FatalLevel,
					log.ErrorLevel,
				})
				if err == nil {
					log.AddHook(hook)
				}
			}

			ctx, cancel := context.WithCancel(context.Background())

			// prepare options
			currentSettings := backr.NewDefaultSettings()
			currentSettings.StateStorage = stateStorageSettings
			currentSettings.WatchDirs = *watchDirs
			currentSettings.Swift = swiftSettings
			currentSettings.ApiListen = *apiListenOpt

			path, _ := homedir.Expand(*secretFilePath)
			currentSettings.SecretFilepath = path

			// parse the time option
			if timeOpt != nil {
				parsedTime, err := time.Parse("15:04", *timeOpt)
				if err == nil {
					currentSettings.TimeSpec.Hour = parsedTime.Hour()
					currentSettings.TimeSpec.Minute = parsedTime.Minute()
				} else {
					log.Warnf("Time option is not correctly formatted, must be like '00:00'. Default option will be used instead")
				}
			}

			ctx = backr.NewContextWithSettings(ctx, currentSettings)

			// handle the SIGINT signal
			waiting := make(chan os.Signal, 1)
			signal.Notify(waiting, os.Interrupt, os.Kill)

			// prepare ticker
			ticker := time.NewTicker(5 * time.Minute) // 1 minute

			go func() {
				isRunning := false

				for {
					select {
					case <-ticker.C:
						log.Debugln("Tick received")

						if !isRunning {
							isRunning = true

							// execute the update of state from specs (yml files)
							tasks.UpdateStateFromSpec(ctx)
							// execute the backup routine
							tasks.PerformBackup(ctx)

							isRunning = false
						} else {
							log.Infoln("Backup process is already running. Skipping.")
						}
					}
				}
			}()

			// start HTTP API daemons
			startAPI(ctx)
			startPrivateAPI(ctx)

			// waiting for signal
			<-waiting
			// stop the ticker
			ticker.Stop()
			// cancelling ctx
			cancel()
			// cleanup current state storage
			state.CleanupStorage(currentSettings)

			log.Infoln("Stopped.")
		}
	})

	app.Command("token", "Create a token to access to backup archives", func(cmd *cli.Cmd) {

		cmd.Spec = "[--secret-file-path] [-q]"

		secretFilePath := cmd.StringOpt("secret-file-path", "~/.backr/jwt_secret", "Path to the file storing the secret used for generating access token to backup files")
		quiet := cmd.BoolOpt("q quiet", false, "Just display the token")

		cmd.Action = func() {

			filepath, _ := homedir.Expand(*secretFilePath)

			secret, err := ioutil.ReadFile(filepath)
			if err != nil {
				if !*quiet {
					fmt.Println("Secret file not found. Create it at", filepath)
				}
				secret = randstr.SecureRandomBytes(64)
				err := ioutil.WriteFile(filepath, secret, 0600)
				if err != nil {
					fmt.Println("Unable to create secret file")
					fmt.Println(err)
					cli.Exit(1)
				}
			}

			// Create a new token object, specifying signing method and the claims
			// you would like it to contain.
			token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
				"exp": time.Now().Add(24 * time.Hour).Unix(),
			})

			// Sign and get the complete encoded token as a string using the secret
			tokenString, err := token.SignedString(secret)
			if err != nil {
				fmt.Println("Unable to generate token")
				fmt.Println(err)
				cli.Exit(1)
			}

			fmt.Println(tokenString)
		}

	})

	app.Command("now", "Execute a backup immediately", func(cmd *cli.Cmd) {

		cmd.Spec = "[--url] PROJECT_NAME"

		url := cmd.StringOpt("url", "http://127.0.0.1:22258", "URL of private API")
		projectName := cmd.StringArg("PROJECT_NAME", "", "A project name configured inside backr")

		cmd.Action = func() {
			client := privatehttp.NewClient(*url)
			info, err := client.Backup(*projectName)
			if err != nil {
				fmt.Printf("ERROR: %v\n", err)
			}

			fmt.Println("name:", info.Name)
			fmt.Println("url:", info.URL)
		}

	})

	app.Run(os.Args)
}

func getSwiftSettings(cmd *cli.Cmd) *backr.SwiftSettings {
	url := cmd.String(cli.StringOpt{
		Name:   "swift-auth-url",
		Value:  "",
		Desc:   "Swift auth URL",
		EnvVar: "OS_AUTH_URL",
	})
	user := cmd.String(cli.StringOpt{
		Name:   "swift-user",
		Value:  "",
		Desc:   "Swift username",
		EnvVar: "OS_USERNAME",
	})
	apiKey := cmd.String(cli.StringOpt{
		Name:   "swift-password",
		Value:  "",
		Desc:   "Swift API Key / Password",
		EnvVar: "OS_PASSWORD",
	})
	tenantName := cmd.String(cli.StringOpt{
		Name:   "swift-tenant-name",
		Value:  "",
		Desc:   "Swift Tenant name",
		EnvVar: "OS_TENANT_NAME",
	})
	region := cmd.String(cli.StringOpt{
		Name:   "swift-region",
		Value:  "",
		Desc:   "Swift region name",
		EnvVar: "OS_REGION_NAME",
	})

	if *url != "" && *user != "" && *apiKey != "" && *tenantName != "" {
		return &backr.SwiftSettings{
			AuthURL:       *url,
			User:          *user,
			APIKey:        *apiKey,
			TenantName:    *tenantName,
			Region:        *region,
			ContainerName: "backups",
		}
	}

	return nil
}

func getStateStorateSettings(cmd *cli.Cmd) backr.StateStorageSettings {
	etcdEndpoints := cmd.String(cli.StringOpt{
		Name:   "etcd",
		Value:  "http://localhost:2379",
		Desc:   "Endpoints for etcd (separated by a comma)",
		EnvVar: "ETCD_ADVERTISE_URLS",
	})

	localPath := cmd.String(cli.StringOpt{
		Name:   "local",
		Value:  "",
		Desc:   "Local directory where the state will be stored (r/w permissions required)",
		EnvVar: "STATE_STORAGE_LOCAL",
	})

	return backr.StateStorageSettings{
		EtcdEndpoints: etcdEndpoints,
		LocalPath:     localPath,
	}
}

func startAPI(ctx context.Context) {
	go func() {
		api := http.NewAPI()
		api.Listen(ctx)
	}()
}

func startPrivateAPI(ctx context.Context) {
	go func() {
		api := privatehttp.NewAPI()
		api.Listen(ctx)
	}()
}
<|endoftext|>


<commit_before>// Copyright 2014 Volker Dobler.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"flag"
	"fmt"
)

var cmdVersion = &Command{
	RunArgs:     runVersion,
	Usage:       "version",
	Description: "print version information",
	Flag:        flag.NewFlagSet("version", flag.ContinueOnError),
	Help: `
Version prints version information about ht.
	`,
}

var (
	version = "4.4.0"
)

func runVersion(cmd *Command, _ []string) {
	fmt.Printf("ht version %s\n", version)
}
<commit_msg>all: start development cycle for 4.5<commit_after>// Copyright 2014 Volker Dobler.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"flag"
	"fmt"
)

var cmdVersion = &Command{
	RunArgs:     runVersion,
	Usage:       "version",
	Description: "print version information",
	Flag:        flag.NewFlagSet("version", flag.ContinueOnError),
	Help: `
Version prints version information about ht.
	`,
}

var (
	version = "4.5.0-beta"
)

func runVersion(cmd *Command, _ []string) {
	fmt.Printf("ht version %s\n", version)
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"os"
	"path"
	"path/filepath"

	cmds "github.com/jbenet/go-ipfs/commands"
	config "github.com/jbenet/go-ipfs/config"
	core "github.com/jbenet/go-ipfs/core"
	ci "github.com/jbenet/go-ipfs/crypto"
	imp "github.com/jbenet/go-ipfs/importer"
	chunk "github.com/jbenet/go-ipfs/importer/chunk"
	peer "github.com/jbenet/go-ipfs/peer"
	u "github.com/jbenet/go-ipfs/util"
	errors "github.com/jbenet/go-ipfs/util/errors"
)

const nBitsForKeypairDefault = 4096

var initCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline:          "Initializes IPFS config file",
		ShortDescription: "Initializes IPFS configuration files and generates a new keypair.",
	},

	Options: []cmds.Option{
		cmds.IntOption("bits", "b", "Number of bits to use in the generated RSA private key (defaults to 4096)"),
		cmds.StringOption("passphrase", "p", "Passphrase for encrypting the private key"),
		cmds.BoolOption("force", "f", "Overwrite existing config (if it exists)"),
		cmds.StringOption("datastore", "d", "Location for the IPFS data store"),

		// TODO need to decide whether to expose the override as a file or a
		// directory. That is: should we allow the user to also specify the
		// name of the file?
		// TODO cmds.StringOption("event-logs", "l", "Location for machine-readable event logs"),
	},
	Run: func(req cmds.Request) (interface{}, error) {

		dspathOverride, _, err := req.Option("d").String() // if !found it's okay. Let == ""
		if err != nil {
			return nil, err
		}

		force, _, err := req.Option("f").Bool() // if !found, it's okay force == false
		if err != nil {
			return nil, err
		}

		nBitsForKeypair, bitsOptFound, err := req.Option("b").Int()
		if err != nil {
			return nil, err
		}
		if !bitsOptFound {
			nBitsForKeypair = nBitsForKeypairDefault
		}

		return doInit(req.Context().ConfigRoot, dspathOverride, force, nBitsForKeypair)
	},
}

var errCannotInitConfigExists = errors.New(`ipfs configuration file already exists!
Reinitializing would overwrite your keys.
(use -f to force overwrite)
`)

var welcomeMsg = `Hello and Welcome to IPFS!

██╗██████╗ ███████╗███████╗
██║██╔══██╗██╔════╝██╔════╝
██║██████╔╝█████╗  ███████╗
██║██╔═══╝ ██╔══╝  ╚════██║
██║██║     ██║     ███████║
╚═╝╚═╝     ╚═╝     ╚══════╝

If you're seeing this, you have successfully installed
IPFS and are now interfacing with the ipfs merkledag!

For a short demo of what you can do, enter 'ipfs tour'
`

// NB: if dspath is not provided, it will be retrieved from the config
func doInit(configRoot string, dspathOverride string, force bool, nBitsForKeypair int) (interface{}, error) {

	u.POut("initializing ipfs node at %s\n", configRoot)

	configFilename, err := config.Filename(configRoot)
	if err != nil {
		return nil, errors.New("Couldn't get home directory path")
	}

	fi, err := os.Lstat(configFilename)
	if fi != nil || (err != nil && !os.IsNotExist(err)) {
		if !force {
			// TODO multi-line string
			return nil, errCannotInitConfigExists
		}
	}

	conf, err := initConfig(configFilename, dspathOverride, nBitsForKeypair)
	if err != nil {
		return nil, err
	}

	err = addTheWelcomeFile(conf, func(k u.Key) {
		fmt.Printf("\nto get started, enter: ipfs cat %s\n", k)
	})
	if err != nil {
		return nil, err
	}

	return nil, nil
}

// addTheWelcomeFile adds a file containing the welcome message to the newly
// minted node. On success, it calls onSuccess
func addTheWelcomeFile(conf *config.Config, onSuccess func(u.Key)) error {
	// TODO extract this file creation operation into a function
	nd, err := core.NewIpfsNode(conf, false)
	if err != nil {
		return err
	}
	defer nd.Close()

	// Set up default file
	reader := bytes.NewBufferString(welcomeMsg)

	defnd, err := imp.BuildDagFromReader(reader, nd.DAG, nd.Pinning.GetManual(), chunk.DefaultSplitter)
	if err != nil {
		return err
	}

	k, err := defnd.Key()
	if err != nil {
		return fmt.Errorf("failed to write test file: %s", err)
	}
	onSuccess(k)
	return nil
}

func datastoreConfig(dspath string) (config.Datastore, error) {
	ds := config.Datastore{}
	if len(dspath) == 0 {
		var err error
		dspath, err = config.DataStorePath("")
		if err != nil {
			return ds, err
		}
	}
	ds.Path = dspath
	ds.Type = "leveldb"

	err := initCheckDir(dspath)
	if err != nil {
		return ds, errors.Errorf("datastore: %s", err)
	}

	return ds, nil
}

func initConfig(configFilename string, dspathOverride string, nBitsForKeypair int) (*config.Config, error) {
	ds, err := datastoreConfig(dspathOverride)
	if err != nil {
		return nil, err
	}

	identity, err := identityConfig(nBitsForKeypair, func() {
		fmt.Printf("generating key pair...")
	}, func(ident config.Identity) {
		fmt.Printf("done\n")
		fmt.Printf("peer identity: %s\n", ident.PeerID)
	})
	if err != nil {
		return nil, err
	}

	logConfig, err := initLogs("") // TODO allow user to override dir
	if err != nil {
		return nil, err
	}

	conf := &config.Config{

		// setup the node addresses.
		Addresses: config.Addresses{
			Swarm: "/ip4/0.0.0.0/tcp/4001",
			API:   "/ip4/127.0.0.1/tcp/5001",
		},

		Bootstrap: []*config.BootstrapPeer{
			&config.BootstrapPeer{ // Use these hardcoded bootstrap peers for now.
				// mars.i.ipfs.io
				PeerID:  "QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ",
				Address: "/ip4/104.131.131.82/tcp/4001",
			},
		},

		Datastore: ds,

		Logs: logConfig,

		Identity: identity,

		// setup the node mount points.
		Mounts: config.Mounts{
			IPFS: "/ipfs",
			IPNS: "/ipns",
		},

		// tracking ipfs version used to generate the init folder and adding
		// update checker default setting.
		Version: config.VersionDefaultValue(),
	}

	if err := config.WriteConfigFile(configFilename, conf); err != nil {
		return nil, err
	}

	return conf, nil
}

// identityConfig initializes a new identity. It calls onBegin when it begins
// to generate the identity and it calls onSuccess once the operation is
// completed successfully
func identityConfig(nbits int, onBegin func(), onSuccess func(config.Identity)) (config.Identity, error) {
	// TODO guard higher up
	ident := config.Identity{}
	if nbits < 1024 {
		return ident, errors.New("Bitsize less than 1024 is considered unsafe.")
	}

	onBegin()
	sk, pk, err := ci.GenerateKeyPair(ci.RSA, nbits)
	if err != nil {
		return ident, err
	}

	// currently storing key unencrypted. in the future we need to encrypt it.
	// TODO(security)
	skbytes, err := sk.Bytes()
	if err != nil {
		return ident, err
	}
	ident.PrivKey = base64.StdEncoding.EncodeToString(skbytes)

	id, err := peer.IDFromPubKey(pk)
	if err != nil {
		return ident, err
	}
	ident.PeerID = id.Pretty()
	onSuccess(ident)
	return ident, nil
}

func initLogs(logpath string) (config.Logs, error) {
	if len(logpath) == 0 {
		var err error
		logpath, err = config.LogsPath("")
		if err != nil {
			return config.Logs{}, errors.Wrap(err)
		}
	}

	err := initCheckDir(logpath)
	if err != nil {
		return config.Logs{}, errors.Errorf("logs: %s", err)
	}

	return config.Logs{
		Filename: path.Join(logpath, "events.log"),
	}, nil
}

// initCheckDir ensures the directory exists and is writable
func initCheckDir(path string) error {
	// Construct the path if missing
	if err := os.MkdirAll(path, os.ModePerm); err != nil {
		return err
	}

	// Check the directory is writeable
	if f, err := os.Create(filepath.Join(path, "._check_writeable")); err == nil {
		os.Remove(f.Name())
	} else {
		return errors.New("'" + path + "' is not writeable")
	}
	return nil
}
<commit_msg>refactor(2/init) use util.FileExists<commit_after>package main

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"os"
	"path"
	"path/filepath"

	cmds "github.com/jbenet/go-ipfs/commands"
	config "github.com/jbenet/go-ipfs/config"
	core "github.com/jbenet/go-ipfs/core"
	ci "github.com/jbenet/go-ipfs/crypto"
	imp "github.com/jbenet/go-ipfs/importer"
	chunk "github.com/jbenet/go-ipfs/importer/chunk"
	peer "github.com/jbenet/go-ipfs/peer"
	u "github.com/jbenet/go-ipfs/util"
	errors "github.com/jbenet/go-ipfs/util/errors"
)

const nBitsForKeypairDefault = 4096

var initCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline:          "Initializes IPFS config file",
		ShortDescription: "Initializes IPFS configuration files and generates a new keypair.",
	},

	Options: []cmds.Option{
		cmds.IntOption("bits", "b", "Number of bits to use in the generated RSA private key (defaults to 4096)"),
		cmds.StringOption("passphrase", "p", "Passphrase for encrypting the private key"),
		cmds.BoolOption("force", "f", "Overwrite existing config (if it exists)"),
		cmds.StringOption("datastore", "d", "Location for the IPFS data store"),

		// TODO need to decide whether to expose the override as a file or a
		// directory. That is: should we allow the user to also specify the
		// name of the file?
		// TODO cmds.StringOption("event-logs", "l", "Location for machine-readable event logs"),
	},
	Run: func(req cmds.Request) (interface{}, error) {

		dspathOverride, _, err := req.Option("d").String() // if !found it's okay. Let == ""
		if err != nil {
			return nil, err
		}

		force, _, err := req.Option("f").Bool() // if !found, it's okay force == false
		if err != nil {
			return nil, err
		}

		nBitsForKeypair, bitsOptFound, err := req.Option("b").Int()
		if err != nil {
			return nil, err
		}
		if !bitsOptFound {
			nBitsForKeypair = nBitsForKeypairDefault
		}

		return doInit(req.Context().ConfigRoot, dspathOverride, force, nBitsForKeypair)
	},
}

var errCannotInitConfigExists = errors.New(`ipfs configuration file already exists!
Reinitializing would overwrite your keys.
(use -f to force overwrite)
`)

var welcomeMsg = `Hello and Welcome to IPFS!

██╗██████╗ ███████╗███████╗
██║██╔══██╗██╔════╝██╔════╝
██║██████╔╝█████╗  ███████╗
██║██╔═══╝ ██╔══╝  ╚════██║
██║██║     ██║     ███████║
╚═╝╚═╝     ╚═╝     ╚══════╝

If you're seeing this, you have successfully installed
IPFS and are now interfacing with the ipfs merkledag!

For a short demo of what you can do, enter 'ipfs tour'
`

// NB: if dspath is not provided, it will be retrieved from the config
func doInit(configRoot string, dspathOverride string, force bool, nBitsForKeypair int) (interface{}, error) {

	u.POut("initializing ipfs node at %s\n", configRoot)

	configFilename, err := config.Filename(configRoot)
	if err != nil {
		return nil, errors.New("Couldn't get home directory path")
	}

	if u.FileExists(configFilename) && !force {
		return nil, errCannotInitConfigExists
	}

	conf, err := initConfig(configFilename, dspathOverride, nBitsForKeypair)
	if err != nil {
		return nil, err
	}

	err = addTheWelcomeFile(conf, func(k u.Key) {
		fmt.Printf("\nto get started, enter: ipfs cat %s\n", k)
	})
	if err != nil {
		return nil, err
	}

	return nil, nil
}

// addTheWelcomeFile adds a file containing the welcome message to the newly
// minted node. On success, it calls onSuccess
func addTheWelcomeFile(conf *config.Config, onSuccess func(u.Key)) error {
	// TODO extract this file creation operation into a function
	nd, err := core.NewIpfsNode(conf, false)
	if err != nil {
		return err
	}
	defer nd.Close()

	// Set up default file
	reader := bytes.NewBufferString(welcomeMsg)

	defnd, err := imp.BuildDagFromReader(reader, nd.DAG, nd.Pinning.GetManual(), chunk.DefaultSplitter)
	if err != nil {
		return err
	}

	k, err := defnd.Key()
	if err != nil {
		return fmt.Errorf("failed to write test file: %s", err)
	}
	onSuccess(k)
	return nil
}

func datastoreConfig(dspath string) (config.Datastore, error) {
	ds := config.Datastore{}
	if len(dspath) == 0 {
		var err error
		dspath, err = config.DataStorePath("")
		if err != nil {
			return ds, err
		}
	}
	ds.Path = dspath
	ds.Type = "leveldb"

	err := initCheckDir(dspath)
	if err != nil {
		return ds, errors.Errorf("datastore: %s", err)
	}

	return ds, nil
}

func initConfig(configFilename string, dspathOverride string, nBitsForKeypair int) (*config.Config, error) {
	ds, err := datastoreConfig(dspathOverride)
	if err != nil {
		return nil, err
	}

	identity, err := identityConfig(nBitsForKeypair, func() {
		fmt.Printf("generating key pair...")
	}, func(ident config.Identity) {
		fmt.Printf("done\n")
		fmt.Printf("peer identity: %s\n", ident.PeerID)
	})
	if err != nil {
		return nil, err
	}

	logConfig, err := initLogs("") // TODO allow user to override dir
	if err != nil {
		return nil, err
	}

	conf := &config.Config{

		// setup the node addresses.
		Addresses: config.Addresses{
			Swarm: "/ip4/0.0.0.0/tcp/4001",
			API:   "/ip4/127.0.0.1/tcp/5001",
		},

		Bootstrap: []*config.BootstrapPeer{
			&config.BootstrapPeer{ // Use these hardcoded bootstrap peers for now.
				// mars.i.ipfs.io
				PeerID:  "QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ",
				Address: "/ip4/104.131.131.82/tcp/4001",
			},
		},

		Datastore: ds,

		Logs: logConfig,

		Identity: identity,

		// setup the node mount points.
		Mounts: config.Mounts{
			IPFS: "/ipfs",
			IPNS: "/ipns",
		},

		// tracking ipfs version used to generate the init folder and adding
		// update checker default setting.
		Version: config.VersionDefaultValue(),
	}

	if err := config.WriteConfigFile(configFilename, conf); err != nil {
		return nil, err
	}

	return conf, nil
}

// identityConfig initializes a new identity. It calls onBegin when it begins
// to generate the identity and it calls onSuccess once the operation is
// completed successfully
func identityConfig(nbits int, onBegin func(), onSuccess func(config.Identity)) (config.Identity, error) {
	// TODO guard higher up
	ident := config.Identity{}
	if nbits < 1024 {
		return ident, errors.New("Bitsize less than 1024 is considered unsafe.")
	}

	onBegin()
	sk, pk, err := ci.GenerateKeyPair(ci.RSA, nbits)
	if err != nil {
		return ident, err
	}

	// currently storing key unencrypted. in the future we need to encrypt it.
	// TODO(security)
	skbytes, err := sk.Bytes()
	if err != nil {
		return ident, err
	}
	ident.PrivKey = base64.StdEncoding.EncodeToString(skbytes)

	id, err := peer.IDFromPubKey(pk)
	if err != nil {
		return ident, err
	}
	ident.PeerID = id.Pretty()
	onSuccess(ident)
	return ident, nil
}

func initLogs(logpath string) (config.Logs, error) {
	if len(logpath) == 0 {
		var err error
		logpath, err = config.LogsPath("")
		if err != nil {
			return config.Logs{}, errors.Wrap(err)
		}
	}

	err := initCheckDir(logpath)
	if err != nil {
		return config.Logs{}, errors.Errorf("logs: %s", err)
	}

	return config.Logs{
		Filename: path.Join(logpath, "events.log"),
	}, nil
}

// initCheckDir ensures the directory exists and is writable
func initCheckDir(path string) error {
	// Construct the path if missing
	if err := os.MkdirAll(path, os.ModePerm); err != nil {
		return err
	}

	// Check the directory is writeable
	if f, err := os.Create(filepath.Join(path, "._check_writeable")); err == nil {
		os.Remove(f.Name())
	} else {
		return errors.New("'" + path + "' is not writeable")
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/base64"
	"errors"
	"os"
	"path/filepath"

	cmds "github.com/jbenet/go-ipfs/commands"
	config "github.com/jbenet/go-ipfs/config"
	ci "github.com/jbenet/go-ipfs/crypto"
	peer "github.com/jbenet/go-ipfs/peer"
	updates "github.com/jbenet/go-ipfs/updates"
	u "github.com/jbenet/go-ipfs/util"
)

var initCmd = &cmds.Command{
	Options: []cmds.Option{
		cmds.Option{[]string{"bits", "b"}, cmds.Int},
		cmds.Option{[]string{"passphrase", "p"}, cmds.String},
		cmds.Option{[]string{"force", "f"}, cmds.Bool},
		cmds.Option{[]string{"datastore", "d"}, cmds.String},
	},
	Help: `ipfs init

	Initializes ipfs configuration files and generates a
	new keypair.
	`,
	Run: func(res cmds.Response, req cmds.Request) {

		arg, found := req.Option("d")
		dspath, ok := arg.(string)
		if found && !ok {
			res.SetError(errors.New("failed to parse datastore flag"), cmds.ErrNormal)
			return
		}

		arg, found = req.Option("f")
		force, ok := arg.(bool) // TODO param
		if found && !ok {
			res.SetError(errors.New("failed to parse force flag"), cmds.ErrNormal)
			return
		}

		arg, found = req.Option("b")
		nBitsForKeypair, ok := arg.(int) // TODO param
		if found && !ok {
			res.SetError(errors.New("failed to get bits flag"), cmds.ErrNormal)
			return
		} else if !found {
			nBitsForKeypair = 4096
		}

		err := foo(res, req, dspath, force, nBitsForKeypair)
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
	},
}

func foo(res cmds.Response, req cmds.Request, dspath string, force bool, nBitsForKeypair int) error {
	ctx := req.Context()

	u.POut("initializing ipfs node at %s\n", ctx.ConfigRoot)
	filename, err := config.Filename(ctx.ConfigRoot)
	if err != nil {
		return errors.New("Couldn't get home directory path")
	}

	fi, err := os.Lstat(filename)
	if fi != nil || (err != nil && !os.IsNotExist(err)) {
		if !force {
			// TODO multi-line string
			return errors.New("ipfs configuration file already exists!\nReinitializing would overwrite your keys.\n(use -f to force overwrite)")
		}
	}
	cfg := new(config.Config)

	cfg.Datastore = config.Datastore{}
	if len(dspath) == 0 {
		dspath, err = config.DataStorePath("")
		if err != nil {
			return err
		}
	}
	cfg.Datastore.Path = dspath
	cfg.Datastore.Type = "leveldb"

	// Construct the data store if missing
	if err := os.MkdirAll(dspath, os.ModePerm); err != nil {
		return err
	}

	// Check the directory is writeable
	if f, err := os.Create(filepath.Join(dspath, "._check_writeable")); err == nil {
		os.Remove(f.Name())
	} else {
		return errors.New("Datastore '" + dspath + "' is not writeable")
	}

	cfg.Identity = config.Identity{}

	// setup the node addresses.
	cfg.Addresses = config.Addresses{
		Swarm: "/ip4/0.0.0.0/tcp/4001",
		API:   "/ip4/127.0.0.1/tcp/5001",
	}

	// setup the node mount points.
	cfg.Mounts = config.Mounts{
		IPFS: "/ipfs",
		IPNS: "/ipns",
	}

	// TODO guard
	if nBitsForKeypair < 1024 {
		return errors.New("Bitsize less than 1024 is considered unsafe.")
	}

	u.POut("generating key pair\n")
	sk, pk, err := ci.GenerateKeyPair(ci.RSA, nBitsForKeypair)
	if err != nil {
		return err
	}

	// currently storing key unencrypted. in the future we need to encrypt it.
	// TODO(security)
	skbytes, err := sk.Bytes()
	if err != nil {
		return err
	}
	cfg.Identity.PrivKey = base64.StdEncoding.EncodeToString(skbytes)

	id, err := peer.IDFromPubKey(pk)
	if err != nil {
		return err
	}
	cfg.Identity.PeerID = id.Pretty()

	// Use these hardcoded bootstrap peers for now.
	cfg.Bootstrap = []*config.BootstrapPeer{
		&config.BootstrapPeer{
			// mars.i.ipfs.io
			PeerID:  "QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ",
			Address: "/ip4/104.131.131.82/tcp/4001",
		},
	}

	// tracking ipfs version used to generate the init folder and adding update checker default setting.
	cfg.Version = config.Version{
		Check:   "error",
		Current: updates.Version,
	}

	err = config.WriteConfigFile(filename, cfg)
	if err != nil {
		return err
	}
	return nil
}
<commit_msg>refactor(init) extract context<commit_after>package main

import (
	"encoding/base64"
	"errors"
	"os"
	"path/filepath"

	cmds "github.com/jbenet/go-ipfs/commands"
	config "github.com/jbenet/go-ipfs/config"
	ci "github.com/jbenet/go-ipfs/crypto"
	peer "github.com/jbenet/go-ipfs/peer"
	updates "github.com/jbenet/go-ipfs/updates"
	u "github.com/jbenet/go-ipfs/util"
)

var initCmd = &cmds.Command{
	Options: []cmds.Option{
		cmds.Option{[]string{"bits", "b"}, cmds.Int},
		cmds.Option{[]string{"passphrase", "p"}, cmds.String},
		cmds.Option{[]string{"force", "f"}, cmds.Bool},
		cmds.Option{[]string{"datastore", "d"}, cmds.String},
	},
	Help: `ipfs init

	Initializes ipfs configuration files and generates a
	new keypair.
	`,
	Run: func(res cmds.Response, req cmds.Request) {

		arg, found := req.Option("d")
		dspath, ok := arg.(string)
		if found && !ok {
			res.SetError(errors.New("failed to parse datastore flag"), cmds.ErrNormal)
			return
		}

		arg, found = req.Option("f")
		force, ok := arg.(bool) // TODO param
		if found && !ok {
			res.SetError(errors.New("failed to parse force flag"), cmds.ErrNormal)
			return
		}

		arg, found = req.Option("b")
		nBitsForKeypair, ok := arg.(int) // TODO param
		if found && !ok {
			res.SetError(errors.New("failed to get bits flag"), cmds.ErrNormal)
			return
		} else if !found {
			nBitsForKeypair = 4096
		}

		err := foo(res, req.Context().ConfigRoot, dspath, force, nBitsForKeypair)
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
	},
}

func foo(res cmds.Response, configRoot string, dspath string, force bool, nBitsForKeypair int) error {

	u.POut("initializing ipfs node at %s\n", configRoot)
	filename, err := config.Filename(configRoot)
	if err != nil {
		return errors.New("Couldn't get home directory path")
	}

	fi, err := os.Lstat(filename)
	if fi != nil || (err != nil && !os.IsNotExist(err)) {
		if !force {
			// TODO multi-line string
			return errors.New("ipfs configuration file already exists!\nReinitializing would overwrite your keys.\n(use -f to force overwrite)")
		}
	}
	cfg := new(config.Config)

	cfg.Datastore = config.Datastore{}
	if len(dspath) == 0 {
		dspath, err = config.DataStorePath("")
		if err != nil {
			return err
		}
	}
	cfg.Datastore.Path = dspath
	cfg.Datastore.Type = "leveldb"

	// Construct the data store if missing
	if err := os.MkdirAll(dspath, os.ModePerm); err != nil {
		return err
	}

	// Check the directory is writeable
	if f, err := os.Create(filepath.Join(dspath, "._check_writeable")); err == nil {
		os.Remove(f.Name())
	} else {
		return errors.New("Datastore '" + dspath + "' is not writeable")
	}

	cfg.Identity = config.Identity{}

	// setup the node addresses.
	cfg.Addresses = config.Addresses{
		Swarm: "/ip4/0.0.0.0/tcp/4001",
		API:   "/ip4/127.0.0.1/tcp/5001",
	}

	// setup the node mount points.
	cfg.Mounts = config.Mounts{
		IPFS: "/ipfs",
		IPNS: "/ipns",
	}

	// TODO guard
	if nBitsForKeypair < 1024 {
		return errors.New("Bitsize less than 1024 is considered unsafe.")
	}

	u.POut("generating key pair\n")
	sk, pk, err := ci.GenerateKeyPair(ci.RSA, nBitsForKeypair)
	if err != nil {
		return err
	}

	// currently storing key unencrypted. in the future we need to encrypt it.
	// TODO(security)
	skbytes, err := sk.Bytes()
	if err != nil {
		return err
	}
	cfg.Identity.PrivKey = base64.StdEncoding.EncodeToString(skbytes)

	id, err := peer.IDFromPubKey(pk)
	if err != nil {
		return err
	}
	cfg.Identity.PeerID = id.Pretty()

	// Use these hardcoded bootstrap peers for now.
	cfg.Bootstrap = []*config.BootstrapPeer{
		&config.BootstrapPeer{
			// mars.i.ipfs.io
			PeerID:  "QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ",
			Address: "/ip4/104.131.131.82/tcp/4001",
		},
	}

	// tracking ipfs version used to generate the init folder and adding update checker default setting.
	cfg.Version = config.Version{
		Check:   "error",
		Current: updates.Version,
	}

	err = config.WriteConfigFile(filename, cfg)
	if err != nil {
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"os"
	"path/filepath"
	"reflect"
	"runtime"
	"strconv"
	"strings"
	"time"
	"unicode/utf8"

	"github.com/mattn/go-runewidth"
)

// Util.go is a collection of utility functions that are used throughout
// the program

// Count returns the length of a string in runes
// This is exactly equivalent to utf8.RuneCountInString(), just less characters
func Count(s string) int {
	return utf8.RuneCountInString(s)
}

// NumOccurrences counts the number of occurences of a byte in a string
func NumOccurrences(s string, c byte) int {
	var n int
	for i := 0; i < len(s); i++ {
		if s[i] == c {
			n++
		}
	}
	return n
}

// Spaces returns a string with n spaces
func Spaces(n int) string {
	var str string
	for i := 0; i < n; i++ {
		str += " "
	}
	return str
}

// Min takes the min of two ints
func Min(a, b int) int {
	if a > b {
		return b
	}
	return a
}

// Max takes the max of two ints
func Max(a, b int) int {
	if a > b {
		return a
	}
	return b
}

// IsWordChar returns whether or not the string is a 'word character'
// If it is a unicode character, then it does not match
// Word characters are defined as [A-Za-z0-9_]
func IsWordChar(str string) bool {
	if len(str) > 1 {
		// Unicode
		return false
	}
	c := str[0]
	return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_')
}

// IsWhitespace returns true if the given rune is a space, tab, or newline
func IsWhitespace(c rune) bool {
	return c == ' ' || c == '\t' || c == '\n'
}

// Contains returns whether or not a string array contains a given string
func Contains(list []string, a string) bool {
	for _, b := range list {
		if b == a {
			return true
		}
	}
	return false
}

// Insert makes a simple insert into a string at the given position
func Insert(str string, pos int, value string) string {
	return string([]rune(str)[:pos]) + value + string([]rune(str)[pos:])
}

// GetLeadingWhitespace returns the leading whitespace of the given string
func GetLeadingWhitespace(str string) string {
	ws := ""
	for _, c := range str {
		if c == ' ' || c == '\t' {
			ws += string(c)
		} else {
			break
		}
	}
	return ws
}

// IsSpaces checks if a given string is only spaces
func IsSpaces(str string) bool {
	for _, c := range str {
		if c != ' ' {
			return false
		}
	}

	return true
}

// IsSpacesOrTabs checks if a given string contains only spaces and tabs
func IsSpacesOrTabs(str string) bool {
	for _, c := range str {
		if c != ' ' && c != '\t' {
			return false
		}
	}

	return true
}

// ParseBool is almost exactly like strconv.ParseBool, except it also accepts 'on' and 'off'
// as 'true' and 'false' respectively
func ParseBool(str string) (bool, error) {
	if str == "on" {
		return true, nil
	}
	if str == "off" {
		return false, nil
	}
	return strconv.ParseBool(str)
}

// EscapePath replaces every path separator in a given path with a %
func EscapePath(path string) string {
	path = filepath.ToSlash(path)
	return strings.Replace(path, "/", "%", -1)
}

// GetModTime returns the last modification time for a given file
// It also returns a boolean if there was a problem accessing the file
func GetModTime(path string) (time.Time, bool) {
	info, err := os.Stat(path)
	if err != nil {
		return time.Now(), false
	}
	return info.ModTime(), true
}

// StringWidth returns the width of a string where tabs count as `tabsize` width
func StringWidth(str string, tabsize int) int {
	sw := runewidth.StringWidth(str)
	sw += NumOccurrences(str, '\t') * (tabsize - 1)
	return sw
}

// WidthOfLargeRunes searches all the runes in a string and counts up all the widths of runes
// that have a width larger than 1 (this also counts tabs as `tabsize` width)
func WidthOfLargeRunes(str string, tabsize int) int {
	count := 0
	for _, ch := range str {
		var w int
		if ch == '\t' {
			w = tabsize
		} else {
			w = runewidth.RuneWidth(ch)
		}
		if w > 1 {
			count += (w - 1)
		}
	}
	return count
}

// RunePos returns the rune index of a given byte index
// This could cause problems if the byte index is between code points
func runePos(p int, str string) int {
	return utf8.RuneCountInString(str[:p])
}

func lcs(a, b string) string {
	arunes := []rune(a)
	brunes := []rune(b)

	lcs := ""
	for i, r := range arunes {
		if i >= len(brunes) {
			break
		}
		if r == brunes[i] {
			lcs += string(r)
		} else {
			break
		}
	}
	return lcs
}

func CommonSubstring(arr ...string) string {
	commonStr := arr[0]

	for _, str := range arr[1:] {
		commonStr = lcs(commonStr, str)
	}

	return commonStr
}

// Abs is a simple absolute value function for ints
func Abs(n int) int {
	if n < 0 {
		return -n
	}
	return n
}

// FuncName returns the name of a given function object
func FuncName(i interface{}) string {
	return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
}

// SplitCommandArgs seperates multiple command arguments which may be quoted.
// The returned slice contains at least one string
func SplitCommandArgs(input string) []string {
	var result []string
	curArg := new(bytes.Buffer)
	inQuote := false
	escape := false

	appendResult := func() {
		str := curArg.String()
		inQuote = false
		escape = false
		if strings.HasPrefix(str, `"`) && strings.HasSuffix(str, `"`) {
			if unquoted, err := strconv.Unquote(str); err == nil {
				str = unquoted
			}
		}
		result = append(result, str)
		curArg.Reset()
	}

	for _, r := range input {
		if r == ' ' && !inQuote {
			appendResult()
		} else {
			curArg.WriteRune(r)

			if r == '"' && !inQuote {
				inQuote = true
			} else {
				if inQuote && !escape {
					if r == '"' {
						inQuote = false
					}
					if r == '\\' {
						escape = true
						continue
					}
				}
			}
		}

		escape = false
	}
	appendResult()
	return result
}

// JoinCommandArgs joins multiple command arguments and quote the strings if needed.
func JoinCommandArgs(args ...string) string {
	buf := new(bytes.Buffer)
	first := true
	for _, arg := range args {
		if first {
			first = false
		} else {
			buf.WriteRune(' ')
		}
		quoted := strconv.Quote(arg)
		if quoted[1:len(quoted)-1] != arg || strings.ContainsRune(arg, ' ') {
			buf.WriteString(quoted)
		} else {
			buf.WriteString(arg)
		}
	}

	return buf.String()
}
<commit_msg>This fixes zyedidia/micro#329 Now all multi-byte characters are considered a wordchar<commit_after>package main

import (
	"bytes"
	"os"
	"path/filepath"
	"reflect"
	"runtime"
	"strconv"
	"strings"
	"time"
	"unicode/utf8"

	"github.com/mattn/go-runewidth"
)

// Util.go is a collection of utility functions that are used throughout
// the program

// Count returns the length of a string in runes
// This is exactly equivalent to utf8.RuneCountInString(), just less characters
func Count(s string) int {
	return utf8.RuneCountInString(s)
}

// NumOccurrences counts the number of occurences of a byte in a string
func NumOccurrences(s string, c byte) int {
	var n int
	for i := 0; i < len(s); i++ {
		if s[i] == c {
			n++
		}
	}
	return n
}

// Spaces returns a string with n spaces
func Spaces(n int) string {
	var str string
	for i := 0; i < n; i++ {
		str += " "
	}
	return str
}

// Min takes the min of two ints
func Min(a, b int) int {
	if a > b {
		return b
	}
	return a
}

// Max takes the max of two ints
func Max(a, b int) int {
	if a > b {
		return a
	}
	return b
}

// IsWordChar returns whether or not the string is a 'word character'
// If it is a unicode character, then it does not match
// Word characters are defined as [A-Za-z0-9_]
func IsWordChar(str string) bool {
	if len(str) > 1 {
		// Unicode
		return true
	}
	c := str[0]
	return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_')
}

// IsWhitespace returns true if the given rune is a space, tab, or newline
func IsWhitespace(c rune) bool {
	return c == ' ' || c == '\t' || c == '\n'
}

// Contains returns whether or not a string array contains a given string
func Contains(list []string, a string) bool {
	for _, b := range list {
		if b == a {
			return true
		}
	}
	return false
}

// Insert makes a simple insert into a string at the given position
func Insert(str string, pos int, value string) string {
	return string([]rune(str)[:pos]) + value + string([]rune(str)[pos:])
}

// GetLeadingWhitespace returns the leading whitespace of the given string
func GetLeadingWhitespace(str string) string {
	ws := ""
	for _, c := range str {
		if c == ' ' || c == '\t' {
			ws += string(c)
		} else {
			break
		}
	}
	return ws
}

// IsSpaces checks if a given string is only spaces
func IsSpaces(str string) bool {
	for _, c := range str {
		if c != ' ' {
			return false
		}
	}

	return true
}

// IsSpacesOrTabs checks if a given string contains only spaces and tabs
func IsSpacesOrTabs(str string) bool {
	for _, c := range str {
		if c != ' ' && c != '\t' {
			return false
		}
	}

	return true
}

// ParseBool is almost exactly like strconv.ParseBool, except it also accepts 'on' and 'off'
// as 'true' and 'false' respectively
func ParseBool(str string) (bool, error) {
	if str == "on" {
		return true, nil
	}
	if str == "off" {
		return false, nil
	}
	return strconv.ParseBool(str)
}

// EscapePath replaces every path separator in a given path with a %
func EscapePath(path string) string {
	path = filepath.ToSlash(path)
	return strings.Replace(path, "/", "%", -1)
}

// GetModTime returns the last modification time for a given file
// It also returns a boolean if there was a problem accessing the file
func GetModTime(path string) (time.Time, bool) {
	info, err := os.Stat(path)
	if err != nil {
		return time.Now(), false
	}
	return info.ModTime(), true
}

// StringWidth returns the width of a string where tabs count as `tabsize` width
func StringWidth(str string, tabsize int) int {
	sw := runewidth.StringWidth(str)
	sw += NumOccurrences(str, '\t') * (tabsize - 1)
	return sw
}

// WidthOfLargeRunes searches all the runes in a string and counts up all the widths of runes
// that have a width larger than 1 (this also counts tabs as `tabsize` width)
func WidthOfLargeRunes(str string, tabsize int) int {
	count := 0
	for _, ch := range str {
		var w int
		if ch == '\t' {
			w = tabsize
		} else {
			w = runewidth.RuneWidth(ch)
		}
		if w > 1 {
			count += (w - 1)
		}
	}
	return count
}

// RunePos returns the rune index of a given byte index
// This could cause problems if the byte index is between code points
func runePos(p int, str string) int {
	return utf8.RuneCountInString(str[:p])
}

func lcs(a, b string) string {
	arunes := []rune(a)
	brunes := []rune(b)

	lcs := ""
	for i, r := range arunes {
		if i >= len(brunes) {
			break
		}
		if r == brunes[i] {
			lcs += string(r)
		} else {
			break
		}
	}
	return lcs
}

func CommonSubstring(arr ...string) string {
	commonStr := arr[0]

	for _, str := range arr[1:] {
		commonStr = lcs(commonStr, str)
	}

	return commonStr
}

// Abs is a simple absolute value function for ints
func Abs(n int) int {
	if n < 0 {
		return -n
	}
	return n
}

// FuncName returns the name of a given function object
func FuncName(i interface{}) string {
	return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
}

// SplitCommandArgs seperates multiple command arguments which may be quoted.
// The returned slice contains at least one string
func SplitCommandArgs(input string) []string {
	var result []string
	curArg := new(bytes.Buffer)
	inQuote := false
	escape := false

	appendResult := func() {
		str := curArg.String()
		inQuote = false
		escape = false
		if strings.HasPrefix(str, `"`) && strings.HasSuffix(str, `"`) {
			if unquoted, err := strconv.Unquote(str); err == nil {
				str = unquoted
			}
		}
		result = append(result, str)
		curArg.Reset()
	}

	for _, r := range input {
		if r == ' ' && !inQuote {
			appendResult()
		} else {
			curArg.WriteRune(r)

			if r == '"' && !inQuote {
				inQuote = true
			} else {
				if inQuote && !escape {
					if r == '"' {
						inQuote = false
					}
					if r == '\\' {
						escape = true
						continue
					}
				}
			}
		}

		escape = false
	}
	appendResult()
	return result
}

// JoinCommandArgs joins multiple command arguments and quote the strings if needed.
func JoinCommandArgs(args ...string) string {
	buf := new(bytes.Buffer)
	first := true
	for _, arg := range args {
		if first {
			first = false
		} else {
			buf.WriteRune(' ')
		}
		quoted := strconv.Quote(arg)
		if quoted[1:len(quoted)-1] != arg || strings.ContainsRune(arg, ' ') {
			buf.WriteString(quoted)
		} else {
			buf.WriteString(arg)
		}
	}

	return buf.String()
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	"os"
	"strconv"

	"github.com/itchyny/volume-go"
)

func run() error {
	if len(os.Args) <= 1 {
		return errors.New("no arg")
	}
	switch os.Args[1] {
	case "get":
		if len(os.Args) == 2 {
			return getVolume()
		}
		return errors.New("invalid argument for volume get")
	case "set":
		if len(os.Args) == 3 {
			return setVolume(os.Args[2])
		}
		return errors.New("invalid argument for volume get")
	}
	return fmt.Errorf("invalid argument for volume: %+v", os.Args[1:])
}

func getVolume() error {
	vol, err := volume.GetVolume()
	if err != nil {
		return err
	}
	fmt.Println(vol)
	return nil
}

func setVolume(volStr string) error {
	vol, err := strconv.Atoi(volStr)
	if err != nil {
		return err
	}
	return volume.SetVolume(vol)
}
<commit_msg>add mute and unmute subcommands to volume cli tool<commit_after>package main

import (
	"errors"
	"fmt"
	"os"
	"strconv"

	"github.com/itchyny/volume-go"
)

func run() error {
	if len(os.Args) <= 1 {
		return errors.New("no arg")
	}
	switch os.Args[1] {
	case "get":
		if len(os.Args) == 2 {
			return getVolume()
		}
		return errors.New("invalid argument for volume get")
	case "set":
		if len(os.Args) == 3 {
			return setVolume(os.Args[2])
		}
		return errors.New("invalid argument for volume get")
	case "mute":
		if len(os.Args) == 2 {
			return volume.Mute()
		}
		return errors.New("invalid argument for volume mute")
	case "unmute":
		if len(os.Args) == 2 {
			return volume.Unmute()
		}
		return errors.New("invalid argument for volume unmute")
	}
	return fmt.Errorf("invalid argument for volume: %+v", os.Args[1:])
}

func getVolume() error {
	vol, err := volume.GetVolume()
	if err != nil {
		return err
	}
	fmt.Println(vol)
	return nil
}

func setVolume(volStr string) error {
	vol, err := strconv.Atoi(volStr)
	if err != nil {
		return err
	}
	return volume.SetVolume(vol)
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net"
	"os"
	"os/exec"

	"github.com/u-root/u-root/pkg/pty"
	"golang.org/x/crypto/ssh"
)

// The ssh package does not define these things so we will
type (
	ptyReq struct {
		TERM   string //TERM environment variable value (e.g., vt100)
		Col    uint32
		Row    uint32
		Xpixel uint32
		Ypixel uint32
		Modes  string //encoded terminal modes
	}
)

var (
	shells  = [...]string{"bash", "zsh", "rush"}
	shell   = "/bin/sh"
	debug   = flag.Bool("d", false, "Enable debug prints")
	keys    = flag.String("keys", "authorized_keys", "Path to the authorized_keys file")
	privkey = flag.String("privatekey", "id_rsa", "Path of private key")
	ip      = flag.String("ip", "0.0.0.0", "ip address to listen on")
	port    = flag.String("port", "2022", "port to listen on")
	dprintf = func(string, ...interface{}) {}
)

func echoCopy(w io.Writer, r io.Reader) (int64, error) {
	var b [8192]byte
	var err error
	var tot int64
	for err == nil {
		var amt int
		if amt, err = r.Read(b[:]); err != nil || amt < 1 {
			fmt.Printf("Read: %v", err)
			break
		}
		log.Printf("Read %d bytes: %q\n", amt, b[:amt])
		if _, err = w.Write(b[:amt]); err != nil {
			fmt.Printf("Write: %v", err)
		}
		tot += int64(amt)
	}
	return tot, err
}

// start a shell
// TODO: use /etc/passwd, but the Go support for that is incomplete
func runShell(c ssh.Channel, p *pty.Pty, shell string) error {
	copy := io.Copy
	defer c.Close()

	p.Command(shell)
	if err := p.C.Start(); err != nil {
		return err
	}
	defer p.C.Wait()
	if *debug {
		copy = echoCopy
	}
	go copy(p.Ptm, c)
	go copy(c, p.Ptm)
	return nil
}

func newPTY(b []byte) (*pty.Pty, error) {
	ptyReq := &ptyReq{}
	err := ssh.Unmarshal(b, ptyReq)
	dprintf("newPTY: %q", ptyReq)
	if err != nil {
		return nil, err
	}
	p, err := pty.New()
	ws, err := p.TTY.GetWinSize()
	if err != nil {
		return nil, err
	}
	ws.Row = uint16(ptyReq.Row)
	ws.Ypixel = uint16(ptyReq.Ypixel)
	ws.Col = uint16(ptyReq.Col)
	ws.Xpixel = uint16(ptyReq.Xpixel)
	dprintf("newPTY: Set winsizes to %v", ws)
	if err := p.TTY.SetWinSize(ws); err != nil {
		return nil, err
	}
	dprintf("newPTY: set TERM to %q", ptyReq.TERM)
	if err := os.Setenv("TERM", ptyReq.TERM); err != nil {
		return nil, err
	}
	return p, nil
}

func init() {
	for _, s := range shells {
		if _, err := exec.LookPath(s); err == nil {
			shell = s
		}
	}
}

func session(chans <-chan ssh.NewChannel) {
	var p *pty.Pty
	// Service the incoming Channel channel.
	for newChannel := range chans {
		// Channels have a type, depending on the application level
		// protocol intended. In the case of a shell, the type is
		// "session" and ServerShell may be used to present a simple
		// terminal interface.
		if newChannel.ChannelType() != "session" {
			newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
			continue
		}
		channel, requests, err := newChannel.Accept()
		if err != nil {
			log.Printf("Could not accept channel: %v", err)
			continue
		}

		// Sessions have out-of-band requests such as "shell",
		// "pty-req" and "env".  Here we handle only the
		// "shell" request.
		go func(in <-chan *ssh.Request) {
			for req := range in {
				dprintf("Request %v", req.Type)
				switch req.Type {
				case "shell":
					if p == nil {
						p, err = pty.New()
						if err != nil {
							log.Printf("sshd: pty.New failed, not running a shell")
							break
						}
					}
					err := runShell(channel, p, shell)
					req.Reply(true, []byte(fmt.Sprintf("%v", err)))
				case "pty-req":
					p, err = newPTY(req.Payload)
					req.Reply(err == nil, nil)
				default:
					log.Printf("Not handling req %v %q", req, string(req.Payload))
					req.Reply(false, nil)
				}
			}
		}(requests)

	}
}

func main() {
	flag.Parse()
	if *debug {
		dprintf = log.Printf
	}
	// Public key authentication is done by comparing
	// the public key of a received connection
	// with the entries in the authorized_keys file.
	authorizedKeysBytes, err := ioutil.ReadFile(*keys)
	if err != nil {
		log.Fatal(err)
	}

	authorizedKeysMap := map[string]bool{}
	for len(authorizedKeysBytes) > 0 {
		pubKey, _, _, rest, err := ssh.ParseAuthorizedKey(authorizedKeysBytes)
		if err != nil {
			log.Fatal(err)
		}

		authorizedKeysMap[string(pubKey.Marshal())] = true
		authorizedKeysBytes = rest
	}

	// An SSH server is represented by a ServerConfig, which holds
	// certificate details and handles authentication of ServerConns.
	config := &ssh.ServerConfig{
		// Remove to disable public key auth.
		PublicKeyCallback: func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {
			if authorizedKeysMap[string(pubKey.Marshal())] {
				return &ssh.Permissions{
					// Record the public key used for authentication.
					Extensions: map[string]string{
						"pubkey-fp": ssh.FingerprintSHA256(pubKey),
					},
				}, nil
			}
			return nil, fmt.Errorf("unknown public key for %q", c.User())
		},
	}

	privateBytes, err := ioutil.ReadFile(*privkey)
	if err != nil {
		log.Fatal(err)
	}

	private, err := ssh.ParsePrivateKey(privateBytes)
	if err != nil {
		log.Fatal(err)
	}

	config.AddHostKey(private)

	// Once a ServerConfig has been configured, connections can be
	// accepted.
	listener, err := net.Listen("tcp", *ip+":"+*port)
	if err != nil {
		log.Fatal(err)
	}
	for {
		nConn, err := listener.Accept()
		if err != nil {
			log.Printf("failed to accept incoming connection: %s", err)
			continue
		}

		// Before use, a handshake must be performed on the incoming
		// net.Conn.
		conn, chans, reqs, err := ssh.NewServerConn(nConn, config)
		if err != nil {
			log.Printf("failed to handshake: %v", err)
			continue
		}
		log.Printf("%v logged in with key %s", conn.RemoteAddr(), conn.Permissions.Extensions["pubkey-fp"])

		// The incoming Request channel must be serviced.
		go ssh.DiscardRequests(reqs)

		go session(chans)
	}
}
<commit_msg>Fixing ssh pty error<commit_after>package main

import (
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net"
	"os"
	"os/exec"

	"github.com/u-root/u-root/pkg/pty"
	"golang.org/x/crypto/ssh"
)

// The ssh package does not define these things so we will
type (
	ptyReq struct {
		TERM   string //TERM environment variable value (e.g., vt100)
		Col    uint32
		Row    uint32
		Xpixel uint32
		Ypixel uint32
		Modes  string //encoded terminal modes
	}
)

var (
	shells  = [...]string{"bash", "zsh", "rush"}
	shell   = "/bin/sh"
	debug   = flag.Bool("d", false, "Enable debug prints")
	keys    = flag.String("keys", "authorized_keys", "Path to the authorized_keys file")
	privkey = flag.String("privatekey", "id_rsa", "Path of private key")
	ip      = flag.String("ip", "0.0.0.0", "ip address to listen on")
	port    = flag.String("port", "2022", "port to listen on")
	dprintf = func(string, ...interface{}) {}
)

func echoCopy(w io.Writer, r io.Reader) (int64, error) {
	var b [8192]byte
	var err error
	var tot int64
	for err == nil {
		var amt int
		if amt, err = r.Read(b[:]); err != nil || amt < 1 {
			fmt.Printf("Read: %v", err)
			break
		}
		log.Printf("Read %d bytes: %q\n", amt, b[:amt])
		if _, err = w.Write(b[:amt]); err != nil {
			fmt.Printf("Write: %v", err)
		}
		tot += int64(amt)
	}
	return tot, err
}

// start a shell
// TODO: use /etc/passwd, but the Go support for that is incomplete
func runShell(c ssh.Channel, p *pty.Pty, shell string) error {
	copy := io.Copy
	defer c.Close()

	p.Command(shell)
	if err := p.C.Start(); err != nil {
		return err
	}
	defer p.C.Wait()
	if *debug {
		copy = echoCopy
	}
	go copy(p.Ptm, c)
	go copy(c, p.Ptm)
	return nil
}

func newPTY(b []byte) (*pty.Pty, error) {
	ptyReq := &ptyReq{}
	err := ssh.Unmarshal(b, ptyReq)
	dprintf("newPTY: %q", ptyReq)
	if err != nil {
		return nil, err
	}
	p, err := pty.New()
	if err != nil {
		return nil, err
	}
	ws, err := p.TTY.GetWinSize()
	if err != nil {
		return nil, err
	}
	ws.Row = uint16(ptyReq.Row)
	ws.Ypixel = uint16(ptyReq.Ypixel)
	ws.Col = uint16(ptyReq.Col)
	ws.Xpixel = uint16(ptyReq.Xpixel)
	dprintf("newPTY: Set winsizes to %v", ws)
	if err := p.TTY.SetWinSize(ws); err != nil {
		return nil, err
	}
	dprintf("newPTY: set TERM to %q", ptyReq.TERM)
	if err := os.Setenv("TERM", ptyReq.TERM); err != nil {
		return nil, err
	}
	return p, nil
}

func init() {
	for _, s := range shells {
		if _, err := exec.LookPath(s); err == nil {
			shell = s
		}
	}
}

func session(chans <-chan ssh.NewChannel) {
	var p *pty.Pty
	// Service the incoming Channel channel.
	for newChannel := range chans {
		// Channels have a type, depending on the application level
		// protocol intended. In the case of a shell, the type is
		// "session" and ServerShell may be used to present a simple
		// terminal interface.
		if newChannel.ChannelType() != "session" {
			newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
			continue
		}
		channel, requests, err := newChannel.Accept()
		if err != nil {
			log.Printf("Could not accept channel: %v", err)
			continue
		}

		// Sessions have out-of-band requests such as "shell",
		// "pty-req" and "env".  Here we handle only the
		// "shell" request.
		go func(in <-chan *ssh.Request) {
			for req := range in {
				dprintf("Request %v", req.Type)
				switch req.Type {
				case "shell":
					if p == nil {
						p, err = pty.New()
						if err != nil {
							log.Printf("sshd: pty.New failed, not running a shell")
							break
						}
					}
					err := runShell(channel, p, shell)
					req.Reply(true, []byte(fmt.Sprintf("%v", err)))
				case "pty-req":
					p, err = newPTY(req.Payload)
					req.Reply(err == nil, nil)
				default:
					log.Printf("Not handling req %v %q", req, string(req.Payload))
					req.Reply(false, nil)
				}
			}
		}(requests)

	}
}

func main() {
	flag.Parse()
	if *debug {
		dprintf = log.Printf
	}
	// Public key authentication is done by comparing
	// the public key of a received connection
	// with the entries in the authorized_keys file.
	authorizedKeysBytes, err := ioutil.ReadFile(*keys)
	if err != nil {
		log.Fatal(err)
	}

	authorizedKeysMap := map[string]bool{}
	for len(authorizedKeysBytes) > 0 {
		pubKey, _, _, rest, err := ssh.ParseAuthorizedKey(authorizedKeysBytes)
		if err != nil {
			log.Fatal(err)
		}

		authorizedKeysMap[string(pubKey.Marshal())] = true
		authorizedKeysBytes = rest
	}

	// An SSH server is represented by a ServerConfig, which holds
	// certificate details and handles authentication of ServerConns.
	config := &ssh.ServerConfig{
		// Remove to disable public key auth.
		PublicKeyCallback: func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {
			if authorizedKeysMap[string(pubKey.Marshal())] {
				return &ssh.Permissions{
					// Record the public key used for authentication.
					Extensions: map[string]string{
						"pubkey-fp": ssh.FingerprintSHA256(pubKey),
					},
				}, nil
			}
			return nil, fmt.Errorf("unknown public key for %q", c.User())
		},
	}

	privateBytes, err := ioutil.ReadFile(*privkey)
	if err != nil {
		log.Fatal(err)
	}

	private, err := ssh.ParsePrivateKey(privateBytes)
	if err != nil {
		log.Fatal(err)
	}

	config.AddHostKey(private)

	// Once a ServerConfig has been configured, connections can be
	// accepted.
	listener, err := net.Listen("tcp", *ip+":"+*port)
	if err != nil {
		log.Fatal(err)
	}
	for {
		nConn, err := listener.Accept()
		if err != nil {
			log.Printf("failed to accept incoming connection: %s", err)
			continue
		}

		// Before use, a handshake must be performed on the incoming
		// net.Conn.
		conn, chans, reqs, err := ssh.NewServerConn(nConn, config)
		if err != nil {
			log.Printf("failed to handshake: %v", err)
			continue
		}
		log.Printf("%v logged in with key %s", conn.RemoteAddr(), conn.Permissions.Extensions["pubkey-fp"])

		// The incoming Request channel must be serviced.
		go ssh.DiscardRequests(reqs)

		go session(chans)
	}
}
<|endoftext|>


<commit_before>// Copyright © 2017 NAME HERE <EMAIL ADDRESS>
//
// 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 main

import "github.com/weirdsnap/m/cmd"

func main() {
	cmd.Execute()
}
<commit_msg>Delete main.go<commit_after><|endoftext|>


<commit_before>package bmattermost

import (
	"github.com/42wim/matterbridge/bridge/config"
	"github.com/42wim/matterbridge/matterclient"
	"github.com/42wim/matterbridge/matterhook"
	log "github.com/Sirupsen/logrus"
	"strings"
)

type MMhook struct {
	mh *matterhook.Client
}

type MMapi struct {
	mc            *matterclient.MMClient
	mmMap         map[string]string
	mmIgnoreNicks []string
}

type MMMessage struct {
	Text     string
	Channel  string
	Username string
}

type Bmattermost struct {
	MMhook
	MMapi
	Config   *config.Protocol
	Remote   chan config.Message
	name     string
	origin   string
	protocol string
}

var flog *log.Entry
var protocol = "mattermost"

func init() {
	flog = log.WithFields(log.Fields{"module": protocol})
}

func New(cfg config.Protocol, origin string, c chan config.Message) *Bmattermost {
	b := &Bmattermost{}
	b.Config = &cfg
	b.origin = origin
	b.Remote = c
	b.protocol = "mattermost"
	b.name = cfg.Name
	b.mmMap = make(map[string]string)
	return b
}

func (b *Bmattermost) Command(cmd string) string {
	return ""
}

func (b *Bmattermost) Connect() error {
	if !b.Config.UseAPI {
		flog.Info("Connecting webhooks")
		b.mh = matterhook.New(b.Config.URL,
			matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify,
				BindAddress: b.Config.BindAddress})
	} else {
		b.mc = matterclient.New(b.Config.Login, b.Config.Password,
			b.Config.Team, b.Config.Server)
		b.mc.SkipTLSVerify = b.Config.SkipTLSVerify
		b.mc.NoTLS = b.Config.NoTLS
		flog.Infof("Connecting %s (team: %s) on %s", b.Config.Login, b.Config.Team, b.Config.Server)
		err := b.mc.Login()
		if err != nil {
			return err
		}
		flog.Info("Connection succeeded")
		go b.mc.WsReceiver()
	}
	go b.handleMatter()
	return nil
}

func (b *Bmattermost) FullOrigin() string {
	return b.protocol + "." + b.origin
}

func (b *Bmattermost) JoinChannel(channel string) error {
	// we can only join channels using the API
	if b.Config.UseAPI {
		return b.mc.JoinChannel(channel)
	}
	return nil
}

func (b *Bmattermost) Name() string {
	return b.protocol + "." + b.origin
}

func (b *Bmattermost) Origin() string {
	return b.origin
}

func (b *Bmattermost) Protocol() string {
	return b.protocol
}

func (b *Bmattermost) Send(msg config.Message) error {
	flog.Debugf("Receiving %#v", msg)
	if msg.FullOrigin != b.FullOrigin() {
		return b.SendType(msg.Username, msg.Text, msg.Channel, "")
	}
	return nil
}

func (b *Bmattermost) SendType(nick string, message string, channel string, mtype string) error {
	if b.Config.PrefixMessagesWithNick {
		/*if IsMarkup(message) {
			message = nick + "\n\n" + message
		} else {
		*/
		message = nick + " " + message
		//}
	}
	if !b.Config.UseAPI {
		matterMessage := matterhook.OMessage{IconURL: b.Config.IconURL}
		matterMessage.Channel = channel
		matterMessage.UserName = nick
		matterMessage.Type = mtype
		matterMessage.Text = message
		err := b.mh.Send(matterMessage)
		if err != nil {
			flog.Info(err)
			return err
		}
		return nil
	}
	b.mc.PostMessage(b.mc.GetChannelId(channel, ""), message)
	return nil
}

func (b *Bmattermost) handleMatter() {
	flog.Debugf("Choosing API based Mattermost connection: %t", b.Config.UseAPI)
	mchan := make(chan *MMMessage)
	if b.Config.UseAPI {
		go b.handleMatterClient(mchan)
	} else {
		go b.handleMatterHook(mchan)
	}
	for message := range mchan {
		texts := strings.Split(message.Text, "\n")
		for _, text := range texts {
			flog.Debugf("Sending message from %s on %s to gateway", message.Username, b.FullOrigin())
			b.Remote <- config.Message{Text: text, Username: message.Username, Channel: message.Channel, Origin: b.origin, Protocol: b.protocol, FullOrigin: b.FullOrigin()}
		}
	}
}

func (b *Bmattermost) handleMatterClient(mchan chan *MMMessage) {
	for message := range b.mc.MessageChan {
		// do not post our own messages back to irc
		if message.Raw.Event == "posted" && b.mc.User.Username != message.Username {
			flog.Debugf("Receiving from matterclient %#v", message)
			m := &MMMessage{}
			m.Username = message.Username
			m.Channel = message.Channel
			m.Text = message.Text
			mchan <- m
		}
	}
}

func (b *Bmattermost) handleMatterHook(mchan chan *MMMessage) {
	for {
		message := b.mh.Receive()
		flog.Debugf("Receiving from matterhook %#v", message)
		m := &MMMessage{}
		m.Username = message.UserName
		m.Text = message.Text
		m.Channel = message.ChannelName
		mchan <- m
	}
}
<commit_msg>Add support for mattermost attachments. Shows public link on bridges. Closes #32<commit_after>package bmattermost

import (
	"github.com/42wim/matterbridge/bridge/config"
	"github.com/42wim/matterbridge/matterclient"
	"github.com/42wim/matterbridge/matterhook"
	log "github.com/Sirupsen/logrus"
	"strings"
)

type MMhook struct {
	mh *matterhook.Client
}

type MMapi struct {
	mc            *matterclient.MMClient
	mmMap         map[string]string
	mmIgnoreNicks []string
}

type MMMessage struct {
	Text     string
	Channel  string
	Username string
}

type Bmattermost struct {
	MMhook
	MMapi
	Config   *config.Protocol
	Remote   chan config.Message
	name     string
	origin   string
	protocol string
}

var flog *log.Entry
var protocol = "mattermost"

func init() {
	flog = log.WithFields(log.Fields{"module": protocol})
}

func New(cfg config.Protocol, origin string, c chan config.Message) *Bmattermost {
	b := &Bmattermost{}
	b.Config = &cfg
	b.origin = origin
	b.Remote = c
	b.protocol = "mattermost"
	b.name = cfg.Name
	b.mmMap = make(map[string]string)
	return b
}

func (b *Bmattermost) Command(cmd string) string {
	return ""
}

func (b *Bmattermost) Connect() error {
	if !b.Config.UseAPI {
		flog.Info("Connecting webhooks")
		b.mh = matterhook.New(b.Config.URL,
			matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify,
				BindAddress: b.Config.BindAddress})
	} else {
		b.mc = matterclient.New(b.Config.Login, b.Config.Password,
			b.Config.Team, b.Config.Server)
		b.mc.SkipTLSVerify = b.Config.SkipTLSVerify
		b.mc.NoTLS = b.Config.NoTLS
		flog.Infof("Connecting %s (team: %s) on %s", b.Config.Login, b.Config.Team, b.Config.Server)
		err := b.mc.Login()
		if err != nil {
			return err
		}
		flog.Info("Connection succeeded")
		go b.mc.WsReceiver()
	}
	go b.handleMatter()
	return nil
}

func (b *Bmattermost) FullOrigin() string {
	return b.protocol + "." + b.origin
}

func (b *Bmattermost) JoinChannel(channel string) error {
	// we can only join channels using the API
	if b.Config.UseAPI {
		return b.mc.JoinChannel(channel)
	}
	return nil
}

func (b *Bmattermost) Name() string {
	return b.protocol + "." + b.origin
}

func (b *Bmattermost) Origin() string {
	return b.origin
}

func (b *Bmattermost) Protocol() string {
	return b.protocol
}

func (b *Bmattermost) Send(msg config.Message) error {
	flog.Debugf("Receiving %#v", msg)
	if msg.FullOrigin != b.FullOrigin() {
		return b.SendType(msg.Username, msg.Text, msg.Channel, "")
	}
	return nil
}

func (b *Bmattermost) SendType(nick string, message string, channel string, mtype string) error {
	if b.Config.PrefixMessagesWithNick {
		/*if IsMarkup(message) {
			message = nick + "\n\n" + message
		} else {
		*/
		message = nick + " " + message
		//}
	}
	if !b.Config.UseAPI {
		matterMessage := matterhook.OMessage{IconURL: b.Config.IconURL}
		matterMessage.Channel = channel
		matterMessage.UserName = nick
		matterMessage.Type = mtype
		matterMessage.Text = message
		err := b.mh.Send(matterMessage)
		if err != nil {
			flog.Info(err)
			return err
		}
		return nil
	}
	b.mc.PostMessage(b.mc.GetChannelId(channel, ""), message)
	return nil
}

func (b *Bmattermost) handleMatter() {
	flog.Debugf("Choosing API based Mattermost connection: %t", b.Config.UseAPI)
	mchan := make(chan *MMMessage)
	if b.Config.UseAPI {
		go b.handleMatterClient(mchan)
	} else {
		go b.handleMatterHook(mchan)
	}
	for message := range mchan {
		texts := strings.Split(message.Text, "\n")
		for _, text := range texts {
			flog.Debugf("Sending message from %s on %s to gateway", message.Username, b.FullOrigin())
			b.Remote <- config.Message{Text: text, Username: message.Username, Channel: message.Channel, Origin: b.origin, Protocol: b.protocol, FullOrigin: b.FullOrigin()}
		}
	}
}

func (b *Bmattermost) handleMatterClient(mchan chan *MMMessage) {
	for message := range b.mc.MessageChan {
		// do not post our own messages back to irc
		if message.Raw.Event == "posted" && b.mc.User.Username != message.Username {
			flog.Debugf("Receiving from matterclient %#v", message)
			m := &MMMessage{}
			m.Username = message.Username
			m.Channel = message.Channel
			m.Text = message.Text
			if len(message.Post.Filenames) > 0 {
				for _, link := range b.mc.GetPublicLinks(message.Post.Filenames) {
					m.Text = m.Text + "\n" + link
				}
			}
			mchan <- m
		}
	}
}

func (b *Bmattermost) handleMatterHook(mchan chan *MMMessage) {
	for {
		message := b.mh.Receive()
		flog.Debugf("Receiving from matterhook %#v", message)
		m := &MMMessage{}
		m.Username = message.UserName
		m.Text = message.Text
		m.Channel = message.ChannelName
		mchan <- m
	}
}
<|endoftext|>


<commit_before>package vmware

import (
	"bytes"
	"errors"
	"fmt"
	"github.com/mitchellh/multistep"
	"github.com/mitchellh/packer/packer"
	"log"
	"path/filepath"
	"strings"
	"time"
)

// This step shuts down the machine. It first attempts to do so gracefully,
// but ultimately forcefully shuts it down if that fails.
//
// Uses:
//   communicator packer.Communicator
//   config *config
//   driver Driver
//   ui     packer.Ui
//   vmx_path string
//
// Produces:
//   <nothing>
type stepShutdown struct{}

func (s *stepShutdown) Run(state map[string]interface{}) multistep.StepAction {
	comm := state["communicator"].(packer.Communicator)
	config := state["config"].(*config)
	driver := state["driver"].(Driver)
	ui := state["ui"].(packer.Ui)
	vmxPath := state["vmx_path"].(string)

	if config.ShutdownCommand != "" {
		ui.Say("Gracefully halting virtual machine...")
		log.Printf("Executing shutdown command: %s", config.ShutdownCommand)

		var stdout, stderr bytes.Buffer
		cmd := &packer.RemoteCmd{
			Command: config.ShutdownCommand,
			Stdout:  &stdout,
			Stderr:  &stderr,
		}
		if err := comm.Start(cmd); err != nil {
			err := fmt.Errorf("Failed to send shutdown command: %s", err)
			state["error"] = err
			ui.Error(err.Error())
			return multistep.ActionHalt
		}

		// Wait for the command to run
		cmd.Wait()

		// If the command failed to run, notify the user in some way.
		if cmd.ExitStatus != 0 {
			state["error"] = fmt.Errorf(
				"Shutdown command has non-zero exit status.\n\nStdout: %s\n\nStderr: %s",
				stdout.String(), stderr.String())
			return multistep.ActionHalt
		}

		log.Printf("Shutdown stdout: %s", stdout.String())
		log.Printf("Shutdown stderr: %s", stderr.String())

		// Wait for the machine to actually shut down
		log.Printf("Waiting max %s for shutdown to complete", config.shutdownTimeout)
		shutdownTimer := time.After(config.shutdownTimeout)
		for {
			running, _ := driver.IsRunning(vmxPath)
			if !running {
				break
			}

			select {
			case <-shutdownTimer:
				err := errors.New("Timeout while waiting for machine to shut down.")
				state["error"] = err
				ui.Error(err.Error())
				return multistep.ActionHalt
			default:
				time.Sleep(1 * time.Second)
			}
		}
	} else {
		if err := driver.Stop(vmxPath); err != nil {
			err := fmt.Errorf("Error stopping VM: %s", err)
			state["error"] = err
			ui.Error(err.Error())
			return multistep.ActionHalt
		}
	}

	ui.Message("Waiting for VMware to clean up after itself...")
	lockPattern := filepath.Join(config.OutputDir, "*.lck")
	timer := time.After(15 * time.Second)
LockWaitLoop:
	for {
		locks, err := filepath.Glob(lockPattern)
		if err == nil {
			if len(locks) == 0 {
				log.Println("No more lock files found. VMware is clean.")
				break
			}

			if len(locks) == 1 && strings.HasSuffix(locks[0], ".vmx.lck") {
				log.Println("Only waiting on VMX lock. VMware is clean.")
				break
			}

			log.Printf("Waiting on lock files: %#v", locks)
		}

		select {
		case <-timer:
			log.Println("Reached timeout on waiting for clean VMware. Assuming clean.")
			break LockWaitLoop
		case <-time.After(1 * time.Second):
		}
	}

	log.Println("VM shut down.")
	return multistep.ActionContinue
}

func (s *stepShutdown) Cleanup(state map[string]interface{}) {}
<commit_msg>builder/vmware: sleep before cleaning files on Windows<commit_after>package vmware

import (
	"bytes"
	"errors"
	"fmt"
	"github.com/mitchellh/multistep"
	"github.com/mitchellh/packer/packer"
	"log"
	"path/filepath"
	"runtime"
	"strings"
	"time"
)

// This step shuts down the machine. It first attempts to do so gracefully,
// but ultimately forcefully shuts it down if that fails.
//
// Uses:
//   communicator packer.Communicator
//   config *config
//   driver Driver
//   ui     packer.Ui
//   vmx_path string
//
// Produces:
//   <nothing>
type stepShutdown struct{}

func (s *stepShutdown) Run(state map[string]interface{}) multistep.StepAction {
	comm := state["communicator"].(packer.Communicator)
	config := state["config"].(*config)
	driver := state["driver"].(Driver)
	ui := state["ui"].(packer.Ui)
	vmxPath := state["vmx_path"].(string)

	if config.ShutdownCommand != "" {
		ui.Say("Gracefully halting virtual machine...")
		log.Printf("Executing shutdown command: %s", config.ShutdownCommand)

		var stdout, stderr bytes.Buffer
		cmd := &packer.RemoteCmd{
			Command: config.ShutdownCommand,
			Stdout:  &stdout,
			Stderr:  &stderr,
		}
		if err := comm.Start(cmd); err != nil {
			err := fmt.Errorf("Failed to send shutdown command: %s", err)
			state["error"] = err
			ui.Error(err.Error())
			return multistep.ActionHalt
		}

		// Wait for the command to run
		cmd.Wait()

		// If the command failed to run, notify the user in some way.
		if cmd.ExitStatus != 0 {
			state["error"] = fmt.Errorf(
				"Shutdown command has non-zero exit status.\n\nStdout: %s\n\nStderr: %s",
				stdout.String(), stderr.String())
			return multistep.ActionHalt
		}

		log.Printf("Shutdown stdout: %s", stdout.String())
		log.Printf("Shutdown stderr: %s", stderr.String())

		// Wait for the machine to actually shut down
		log.Printf("Waiting max %s for shutdown to complete", config.shutdownTimeout)
		shutdownTimer := time.After(config.shutdownTimeout)
		for {
			running, _ := driver.IsRunning(vmxPath)
			if !running {
				break
			}

			select {
			case <-shutdownTimer:
				err := errors.New("Timeout while waiting for machine to shut down.")
				state["error"] = err
				ui.Error(err.Error())
				return multistep.ActionHalt
			default:
				time.Sleep(1 * time.Second)
			}
		}
	} else {
		if err := driver.Stop(vmxPath); err != nil {
			err := fmt.Errorf("Error stopping VM: %s", err)
			state["error"] = err
			ui.Error(err.Error())
			return multistep.ActionHalt
		}
	}

	ui.Message("Waiting for VMware to clean up after itself...")
	lockPattern := filepath.Join(config.OutputDir, "*.lck")
	timer := time.After(15 * time.Second)
LockWaitLoop:
	for {
		locks, err := filepath.Glob(lockPattern)
		if err == nil {
			if len(locks) == 0 {
				log.Println("No more lock files found. VMware is clean.")
				break
			}

			if len(locks) == 1 && strings.HasSuffix(locks[0], ".vmx.lck") {
				log.Println("Only waiting on VMX lock. VMware is clean.")
				break
			}

			log.Printf("Waiting on lock files: %#v", locks)
		}

		select {
		case <-timer:
			log.Println("Reached timeout on waiting for clean VMware. Assuming clean.")
			break LockWaitLoop
		case <-time.After(1 * time.Second):
		}
	}

	if runtime.GOOS == "windows" {
		// Windows takes a while to yield control of the files when the
		// process is exiting. We just sleep here. In the future, it'd be
		// nice to find a better solution to this.
		time.Sleep(5 * time.Second)
	}

	log.Println("VM shut down.")
	return multistep.ActionContinue
}

func (s *stepShutdown) Cleanup(state map[string]interface{}) {}
<|endoftext|>


<commit_before>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/

package sync

import (
	"bytes"
	"code.google.com/p/go.crypto/hkdf"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"errors"
	"github.com/st3fan/gofxa/fxa"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"strconv"
)

const USER_AGENT = "SyncAPI/0.1 (https://github.com/st3fan/moz-syncapi)"

//

type KeyBundle struct {
	EncryptionKey []byte
	ValidationKey []byte
}

func NewKeyBundle(key []byte) (*KeyBundle, error) {
	secret := make([]byte, 2*32)
	if _, err := io.ReadFull(hkdf.New(sha256.New, key, nil, []byte("identity.mozilla.com/picl/v1/oldsync")), secret); err != nil {
		return nil, err
	}
	return &KeyBundle{
		EncryptionKey: secret[0:32],
		ValidationKey: secret[32:64],
	}, nil
}

//

type StorageClient struct {
	endpoint  string
	hawkKeyId string
	hawkKey   string
	secret    []byte
	keyBundle KeyBundle
}

func NewStorageClient(endpoint, hawkKeyId, hawkKey string, secret []byte) (*StorageClient, error) {
	return &StorageClient{
		endpoint:  endpoint,
		hawkKeyId: hawkKeyId,
		hawkKey:   hawkKey,
		secret:    secret,
	}, nil
}

type KeysPayload struct {
	Default []string `json:"default"`
}

func (sc *StorageClient) FetchKeys() (KeyBundle, error) {
	globalKeyBundle, err := NewKeyBundle(sc.secret)
	if err != nil {
		return KeyBundle{}, err
	}

	record, err := sc.GetEncryptedRecord("crypto", "keys", globalKeyBundle)
	if err != nil {
		return KeyBundle{}, err
	}

	keysPayload := KeysPayload{}
	if err = json.Unmarshal([]byte(record.Payload), &keysPayload); err != nil {
		return KeyBundle{}, err
	}

	encryptionKey, err := base64.StdEncoding.DecodeString(keysPayload.Default[0])
	if err != nil {
		return KeyBundle{}, err
	}

	validationKey, err := base64.StdEncoding.DecodeString(keysPayload.Default[1])
	if err != nil {
		return KeyBundle{}, err
	}

	return KeyBundle{
		EncryptionKey: encryptionKey,
		ValidationKey: validationKey,
	}, nil
}

func (sc *StorageClient) GetRecord(collectionName, recordId string) (Record, error) {
	req, err := http.NewRequest("GET", sc.endpoint+"/storage/"+collectionName+"/"+recordId, nil)
	if err != nil {
		return Record{}, err
	}

	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Accept", "application/json")
	req.Header.Set("User-Agent", USER_AGENT)

	hawkCredentials := fxa.NewHawkCredentials(sc.hawkKeyId, []byte(sc.hawkKey))
	if err := hawkCredentials.AuthorizeRequest(req, nil, ""); err != nil {
		return Record{}, err
	}

	client := &http.Client{}
	res, err := client.Do(req)
	if err != nil {
		return Record{}, err
	}
	defer res.Body.Close()

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

	if res.StatusCode != http.StatusOK {
		return Record{}, errors.New(res.Status) // TODO: Proper errors based on what the server returns
	}

	record := Record{}
	if err = json.Unmarshal(body, &record); err != nil {
		return Record{}, err
	}

	return record, nil
}

func (sc *StorageClient) GetEncryptedRecord(collectionName, recordId string, keyBundle *KeyBundle) (Record, error) {
	record, err := sc.GetRecord(collectionName, recordId)
	if err != nil {
		return Record{}, err
	}

	if keyBundle == nil {
		keyBundle = &sc.keyBundle
	}

	if err := record.Decrypt(keyBundle); err != nil {
		return Record{}, err
	}

	return record, nil
}

type GetRecordsOptions struct {
	Limit int
	Sort  string
}

func (sc *StorageClient) GetEncryptedRecords(collectionName string, keyBundle *KeyBundle, options *GetRecordsOptions) ([]Record, error) {
	url := sc.endpoint + "/storage/" + collectionName + "?full=1"
	if options != nil {
		if options.Limit != 0 {
			url += "&limit=" + strconv.Itoa(options.Limit)
		}
		if options.Sort != "" {
			url += "&sort=" + options.Sort
		}
	}

	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, err
	}

	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Accept", "application/json")
	req.Header.Set("User-Agent", USER_AGENT)

	hawkCredentials := fxa.NewHawkCredentials(sc.hawkKeyId, []byte(sc.hawkKey))
	if err := hawkCredentials.AuthorizeRequest(req, nil, ""); err != nil {
		return nil, err
	}

	client := &http.Client{}
	res, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()

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

	if res.StatusCode != http.StatusOK {
		return nil, errors.New(res.Status) // TODO: Proper errors based on what the server returns
	}

	records := []Record{}
	if err = json.Unmarshal(body, &records); err != nil {
		return nil, err
	}

	if keyBundle == nil {
		keyBundle = &sc.keyBundle
	}

	for i := 0; i < len(records); i++ {
		if err := records[i].Decrypt(keyBundle); err != nil {
			return nil, err
		}
	}

	return records, nil
}

func (sc *StorageClient) Login() error {
	keyBundle, err := sc.FetchKeys()
	if err != nil {
		return err
	}
	sc.keyBundle = keyBundle
	return nil
}

func (sc *StorageClient) PutEncryptedRecord(collectionName string, record Record, keyBundle *KeyBundle) (string, error) {
	if keyBundle == nil {
		keyBundle = &sc.keyBundle
	}

	if err := record.Encrypt(keyBundle); err != nil {
		return "", err
	}

	encodedRecord, err := json.Marshal(&record)
	if err != nil {
		return "", err
	}

	// Upload the record

	url := sc.endpoint + "/storage/" + collectionName + "/" + record.Id

	client := &http.Client{}

	req, err := http.NewRequest("PUT", url, bytes.NewReader(encodedRecord))
	if err != nil {
		return "", err
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Accept", "application/json")
	req.Header.Set("User-Agent", USER_AGENT)

	hawkCredentials := fxa.NewHawkCredentials(sc.hawkKeyId, []byte(sc.hawkKey))
	if err := hawkCredentials.AuthorizeRequest(req, bytes.NewReader(encodedRecord), ""); err != nil {
		return "", err
	}

	res, err := client.Do(req)
	if err != nil {
		return "", err
	}

	if res.StatusCode != http.StatusOK {
		return "", errors.New(res.Status) // TODO: Proper errors based on what the server returns
	}

	return record.Id, nil
}
<commit_msg>Added PutEncryptedRecords and DeleteCollection<commit_after>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/

package sync

import (
	"bytes"
	"code.google.com/p/go.crypto/hkdf"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"errors"
	"github.com/st3fan/gofxa/fxa"
	"io"
	"io/ioutil"
	"net/http"
	"strconv"
)

const USER_AGENT = "SyncAPI/0.1 (https://github.com/st3fan/moz-syncapi)"

//

type KeyBundle struct {
	EncryptionKey []byte
	ValidationKey []byte
}

func NewKeyBundle(key []byte) (*KeyBundle, error) {
	secret := make([]byte, 2*32)
	if _, err := io.ReadFull(hkdf.New(sha256.New, key, nil, []byte("identity.mozilla.com/picl/v1/oldsync")), secret); err != nil {
		return nil, err
	}
	return &KeyBundle{
		EncryptionKey: secret[0:32],
		ValidationKey: secret[32:64],
	}, nil
}

//

type StorageClient struct {
	endpoint  string
	hawkKeyId string
	hawkKey   string
	secret    []byte
	keyBundle KeyBundle
}

func NewStorageClient(endpoint, hawkKeyId, hawkKey string, secret []byte) (*StorageClient, error) {
	return &StorageClient{
		endpoint:  endpoint,
		hawkKeyId: hawkKeyId,
		hawkKey:   hawkKey,
		secret:    secret,
	}, nil
}

type KeysPayload struct {
	Default []string `json:"default"`
}

func (sc *StorageClient) FetchKeys() (KeyBundle, error) {
	globalKeyBundle, err := NewKeyBundle(sc.secret)
	if err != nil {
		return KeyBundle{}, err
	}

	record, err := sc.GetEncryptedRecord("crypto", "keys", globalKeyBundle)
	if err != nil {
		return KeyBundle{}, err
	}

	keysPayload := KeysPayload{}
	if err = json.Unmarshal([]byte(record.Payload), &keysPayload); err != nil {
		return KeyBundle{}, err
	}

	encryptionKey, err := base64.StdEncoding.DecodeString(keysPayload.Default[0])
	if err != nil {
		return KeyBundle{}, err
	}

	validationKey, err := base64.StdEncoding.DecodeString(keysPayload.Default[1])
	if err != nil {
		return KeyBundle{}, err
	}

	return KeyBundle{
		EncryptionKey: encryptionKey,
		ValidationKey: validationKey,
	}, nil
}

func (sc *StorageClient) GetRecord(collectionName, recordId string) (Record, error) {
	req, err := http.NewRequest("GET", sc.endpoint+"/storage/"+collectionName+"/"+recordId, nil)
	if err != nil {
		return Record{}, err
	}

	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Accept", "application/json")
	req.Header.Set("User-Agent", USER_AGENT)

	hawkCredentials := fxa.NewHawkCredentials(sc.hawkKeyId, []byte(sc.hawkKey))
	if err := hawkCredentials.AuthorizeRequest(req, nil, ""); err != nil {
		return Record{}, err
	}

	client := &http.Client{}
	res, err := client.Do(req)
	if err != nil {
		return Record{}, err
	}
	defer res.Body.Close()

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

	if res.StatusCode != http.StatusOK {
		return Record{}, errors.New(res.Status) // TODO: Proper errors based on what the server returns
	}

	record := Record{}
	if err = json.Unmarshal(body, &record); err != nil {
		return Record{}, err
	}

	return record, nil
}

func (sc *StorageClient) GetEncryptedRecord(collectionName, recordId string, keyBundle *KeyBundle) (Record, error) {
	record, err := sc.GetRecord(collectionName, recordId)
	if err != nil {
		return Record{}, err
	}

	if keyBundle == nil {
		keyBundle = &sc.keyBundle
	}

	if err := record.Decrypt(keyBundle); err != nil {
		return Record{}, err
	}

	return record, nil
}

type GetRecordsOptions struct {
	Limit int
	Sort  string
}

func (sc *StorageClient) GetEncryptedRecords(collectionName string, keyBundle *KeyBundle, options *GetRecordsOptions) ([]Record, error) {
	url := sc.endpoint + "/storage/" + collectionName + "?full=1"
	if options != nil {
		if options.Limit != 0 {
			url += "&limit=" + strconv.Itoa(options.Limit)
		}
		if options.Sort != "" {
			url += "&sort=" + options.Sort
		}
	}

	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, err
	}

	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Accept", "application/json")
	req.Header.Set("User-Agent", USER_AGENT)

	hawkCredentials := fxa.NewHawkCredentials(sc.hawkKeyId, []byte(sc.hawkKey))
	if err := hawkCredentials.AuthorizeRequest(req, nil, ""); err != nil {
		return nil, err
	}

	client := &http.Client{}
	res, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()

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

	if res.StatusCode != http.StatusOK {
		return nil, errors.New(res.Status) // TODO: Proper errors based on what the server returns
	}

	records := []Record{}
	if err = json.Unmarshal(body, &records); err != nil {
		return nil, err
	}

	if keyBundle == nil {
		keyBundle = &sc.keyBundle
	}

	for i := 0; i < len(records); i++ {
		if err := records[i].Decrypt(keyBundle); err != nil {
			return nil, err
		}
	}

	return records, nil
}

func (sc *StorageClient) Login() error {
	keyBundle, err := sc.FetchKeys()
	if err != nil {
		return err
	}
	sc.keyBundle = keyBundle
	return nil
}

func (sc *StorageClient) PutEncryptedRecord(collectionName string, record Record, keyBundle *KeyBundle) (string, error) {
	if keyBundle == nil {
		keyBundle = &sc.keyBundle
	}

	if err := record.Encrypt(keyBundle); err != nil {
		return "", err
	}

	encodedRecord, err := json.Marshal(&record)
	if err != nil {
		return "", err
	}

	// Upload the record

	url := sc.endpoint + "/storage/" + collectionName + "/" + record.Id

	client := &http.Client{}

	req, err := http.NewRequest("PUT", url, bytes.NewReader(encodedRecord))
	if err != nil {
		return "", err
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Accept", "application/json")
	req.Header.Set("User-Agent", USER_AGENT)

	hawkCredentials := fxa.NewHawkCredentials(sc.hawkKeyId, []byte(sc.hawkKey))
	if err := hawkCredentials.AuthorizeRequest(req, bytes.NewReader(encodedRecord), ""); err != nil {
		return "", err
	}

	res, err := client.Do(req)
	if err != nil {
		return "", err
	}

	if res.StatusCode != http.StatusOK {
		return "", errors.New(res.Status) // TODO: Proper errors based on what the server returns
	}

	return record.Id, nil
}

func (sc *StorageClient) PutEncryptedRecords(collectionName string, records []Record, keyBundle *KeyBundle) error {
	if keyBundle == nil {
		keyBundle = &sc.keyBundle
	}

	for i := 0; i < len(records); i++ {
		if err := records[i].Encrypt(keyBundle); err != nil {
			return err
		}
	}

	encodedRecords, err := json.Marshal(&records)
	if err != nil {
		return err
	}

	// Upload the record

	url := sc.endpoint + "/storage/" + collectionName

	client := &http.Client{}

	req, err := http.NewRequest("POST", url, bytes.NewReader(encodedRecords))
	if err != nil {
		return err
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Accept", "application/json")
	req.Header.Set("User-Agent", USER_AGENT)

	hawkCredentials := fxa.NewHawkCredentials(sc.hawkKeyId, []byte(sc.hawkKey))
	if err := hawkCredentials.AuthorizeRequest(req, bytes.NewReader(encodedRecords), ""); err != nil {
		return err
	}

	res, err := client.Do(req)
	if err != nil {
		return err
	}

	defer res.Body.Close()

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

	// TODO Do something useful with the body

	if res.StatusCode != http.StatusOK {
		return errors.New(res.Status) // TODO: Proper errors based on what the server returns
	}

	return nil
}

func (sc *StorageClient) DeleteCollection(collectionName string) error {
	url := sc.endpoint + "/storage/" + collectionName

	client := &http.Client{}

	req, err := http.NewRequest("DELETE", url, nil)
	if err != nil {
		return err
	}
	//req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Accept", "application/json")
	req.Header.Set("User-Agent", USER_AGENT)

	hawkCredentials := fxa.NewHawkCredentials(sc.hawkKeyId, []byte(sc.hawkKey))
	if err := hawkCredentials.AuthorizeRequest(req, nil, ""); err != nil {
		return err
	}

	res, err := client.Do(req)
	if err != nil {
		return err
	}

	if res.StatusCode != http.StatusOK {
		return errors.New(res.Status) // TODO: Proper errors based on what the server returns
	}

	return nil
}
<|endoftext|>