From b99c13a8a35d13489dbd90f9e15e829df6234102 Mon Sep 17 00:00:00 2001 From: Theotime Leveque Date: Sun, 19 Mar 2017 23:17:27 -0400 Subject: [PATCH] Share: New feature. --- backend/share/share.go | 107 +++++++++++++++++++++++++++ cmd/projects.go | 10 ++- cmd/share.go | 163 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 278 insertions(+), 2 deletions(-) create mode 100644 backend/share/share.go create mode 100644 cmd/share.go diff --git a/backend/share/share.go b/backend/share/share.go new file mode 100644 index 0000000..404c391 --- /dev/null +++ b/backend/share/share.go @@ -0,0 +1,107 @@ +package share + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "fmt" + "io" + "io/ioutil" + "mime/multipart" + "net/http" + "os" +) + +// Upload to transfer.sh +func Upload(filename string, targetURL string, key string) (string, error) { + bodyBuf := &bytes.Buffer{} + bodyWriter := multipart.NewWriter(bodyBuf) + + fileWriter, err := bodyWriter.CreateFormFile("uploadfile", filename) + if err != nil { + fmt.Println("error writing to buffer") + return "", err + } + + var fh io.Reader + if key == "" { + fh, err = os.Open(filename) + if err != nil { + fmt.Println("error opening file") + return "", err + } + } else { + fh = EncryptFile(filename, []byte(key)) + } + + // iocopy + _, err = io.Copy(fileWriter, fh) + if err != nil { + return "", err + } + + contentType := bodyWriter.FormDataContentType() + bodyWriter.Close() + + resp, err := http.Post(targetURL, contentType, bodyBuf) + if err != nil { + return "", err + } + defer resp.Body.Close() + respBody, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", err + } + fmt.Println(resp.Status) + return string(respBody), nil +} + +// ZipConfigFiles produces a Zip archive +func ZipConfigFiles(filename string) { + +} + +// EncryptFile encrypt and return a file +func EncryptFile(filename string, key []byte) io.Reader { + fileContent, err := ioutil.ReadFile(filename) + + block, err := aes.NewCipher(key) + if err != nil { + panic(err) + } + + // The IV needs to be unique, but not secure. Therefore it's common to + // include it at the beginning of the ciphertext. + ciphertext := make([]byte, aes.BlockSize+len(fileContent)) + iv := ciphertext[:aes.BlockSize] + if _, err := io.ReadFull(rand.Reader, iv); err != nil { + panic(err) + } + + stream := cipher.NewCFBEncrypter(block, iv) + stream.XORKeyStream(ciphertext[aes.BlockSize:], fileContent) + return bytes.NewReader(ciphertext) +} + +// GetshareSetFromLinkCmdUsageTemplate returns shareSetFromLinkCmd usage template +func GetshareSetFromLinkCmdUsageTemplate() string { + return `Usage:{{if .Runnable}} + {{if .HasAvailableFlags}}{{appendIfNotPresent .UseLine " [flags]"}}{{else}}{{.UseLine}}{{end}}{{end}}{{if .HasAvailableSubCommands}} + {{ .CommandPath}} [command]{{end}}{{if gt .Aliases 0}} +Aliases: + {{.NameAndAliases}} +{{end}}{{if .HasExample}} +Examples: +{{ .Example }}{{end}}{{if .HasAvailableSubCommands}} +Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}} + {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}} +Flags: +{{.LocalFlags.FlagUsages | trimRightSpace}}{{end}}{{if .HasAvailableInheritedFlags}} +Global Flags: +{{.InheritedFlags.FlagUsages | trimRightSpace}}{{end}}{{if .HasHelpSubCommands}} +Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}} + {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}} +Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}} +` +} diff --git a/cmd/projects.go b/cmd/projects.go index 42689d7..1e823b8 100644 --- a/cmd/projects.go +++ b/cmd/projects.go @@ -216,7 +216,10 @@ func setProjectCmd() *cobra.Command { return &cobra.Command{ Use: "set", Short: "Define a subcommand", - Long: `Define a subcommand.`, + Long: `Define a subcommand. + + Usage: + ian project set `, Run: func(cmd *cobra.Command, args []string) { if len(args) < 2 { fmt.Fprint(os.Stderr, "Not enough argument.\n\n") @@ -262,7 +265,10 @@ func unsetProjectCmd() *cobra.Command { return &cobra.Command{ Use: "unset", Short: "Remove a subcommand", - Long: `Remove a subcommand.`, + Long: `Remove a subcommand. + + Usage: + ian project remove `, Run: func(cmd *cobra.Command, args []string) { if len(args) < 1 { fmt.Fprint(os.Stderr, "Not enough argument.\n\n") diff --git a/cmd/share.go b/cmd/share.go new file mode 100644 index 0000000..0647109 --- /dev/null +++ b/cmd/share.go @@ -0,0 +1,163 @@ +// Copyright © 2016 Theotime LEVEQUE theotime@protonmail.com +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "fmt" + "io" + "net/http" + "net/url" + "os" + "strings" + + "github.com/spf13/cobra" + "github.com/thylong/ian/backend/config" + "github.com/thylong/ian/backend/share" +) + +var key = "" + +var encryptShareCmdParam bool + +// var shortLinkShareCmdParam bool + +func init() { + RootCmd.AddCommand(shareCmd) + + shareCmd.PersistentFlags().BoolVarP(&encryptShareCmdParam, "encrypt", "s", false, "Encrypt with private key before uploading") + // shareCmd.PersistentFlags().BoolVarP(&shortLinkShareCmdParam, "bitlink", "b", false, "Get a Bit.ly shorten URL") + + shareSetFromLinkCmd.SetUsageTemplate(share.GetshareSetFromLinkCmdUsageTemplate()) + + shareCmd.AddCommand( + shareConfigCmd, + shareProjectsCmd, + shareEnvCmd, + shareSetFromLinkCmd, + // shareAllCmd, + ) +} + +// shareCmd represents the env command +var shareCmd = &cobra.Command{ + Use: "share", + Short: "Share ian configuration", + Long: `Share a public link to a single (or multiple) ian configuration file.`, +} + +var shareConfigCmd = &cobra.Command{ + Use: "config", + Short: "Share a public link to ian config.yml file", + Long: `Share a public link to ian config.yml file.`, + Run: func(cmd *cobra.Command, args []string) { + if encryptShareCmdParam { + key = config.GetUserInput("Enter a secret key: ") + } + link, err := share.Upload(config.ConfigFilesPathes[cmd.Use], "https://transfer.sh/", key) + if err != nil { + fmt.Fprint(os.Stderr, "Sorry, it looks like I cannot upload configuration file... :(") + return + } + fmt.Println(link) + }, +} + +var shareProjectsCmd = &cobra.Command{ + Use: "projects", + Short: "Share a public link to ian projects.yml file", + Long: `Share a public link to ian projects.yml file.`, + Run: func(cmd *cobra.Command, args []string) { + if encryptShareCmdParam { + key = config.GetUserInput("Enter a secret key: ") + } + link, err := share.Upload(config.ConfigFilesPathes[cmd.Use], "https://transfer.sh/", key) + if err != nil { + fmt.Fprint(os.Stderr, "Sorry, it looks like I cannot upload configuration file... :(") + return + } + fmt.Println(link) + }, +} + +var shareEnvCmd = &cobra.Command{ + Use: "env", + Short: "Share a public link to ian env.yml file", + Long: `Share a public link to ian env.yml file.`, + Run: func(cmd *cobra.Command, args []string) { + if encryptShareCmdParam { + key = config.GetUserInput("Enter a secret key: ") + } + link, err := share.Upload(config.ConfigFilesPathes[cmd.Use], "https://transfer.sh/", key) + if err != nil { + fmt.Fprint(os.Stderr, "Sorry, it looks like I cannot upload configuration file... :(") + return + } + fmt.Println(link) + }, +} + +var shareSetFromLinkCmd = &cobra.Command{ + Use: "set", + Short: "Set config from config file link", + Long: `Set config from config file link.`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) < 1 { + fmt.Fprint(os.Stderr, "Not enough argument.\n\n") + cmd.Usage() + os.Exit(1) + } + + _, err := url.ParseRequestURI(args[0]) + if err != nil { + fmt.Fprint(os.Stderr, "Sorry, it looks like the link you provided is invalid.") + } + + resp, err := http.Get(args[0]) + if err != nil { + fmt.Fprint(os.Stderr, "Sorry, the link is unreachable.") + } + defer resp.Body.Close() + + if strings.HasSuffix(string(strings.TrimSuffix(args[0], "/")), "_e") { + // DecryptFile + } + + confFileName := strings.TrimSuffix(args[1], ".yml") + fmt.Print(confFileName) + if confFilePath, ok := config.ConfigFilesPathes[confFileName]; ok { + f, err := os.OpenFile(confFilePath, os.O_TRUNC|os.O_WRONLY, 0600) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + defer f.Close() + + if _, err := io.Copy(f, resp.Body); err != nil { + fmt.Fprintf(os.Stderr, "Error: %s", err.Error()) + os.Exit(1) + } + return + } + fmt.Fprint(os.Stderr, "Sorry, something went wrong.") + }, +} + +var shareAllCmd = &cobra.Command{ + Use: "all", + Short: "Share a public link to ian a zip containing all files", + Long: `Share a public link to ian env.yml file.`, + Run: func(cmd *cobra.Command, args []string) { + }, +}