├── main.go ├── Makefile ├── go.mod ├── util └── config.go ├── upload ├── file_test.go ├── model.go ├── file.go ├── instance_test.go └── instance.go ├── .github └── workflows │ ├── go.yml │ └── install.yml ├── get ├── write_counter.go ├── model.go └── download.go ├── cmd ├── get.go ├── root.go └── upload.go ├── .gitignore ├── README.md ├── install.sh ├── LICENSE └── go.sum /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/NUAA-Open-Source/safeu-cli/cmd" 4 | 5 | func main() { 6 | cmd.Execute() 7 | } 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | go build -o safeu 3 | # On Mac OSX use this build binary file for Linux 4 | linux-build: 5 | GOOS=linux GOARCH=amd64 go build -o safeu 6 | 7 | 8 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/NUAA-Open-Source/safeu-cli 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/dustin/go-humanize v1.0.0 7 | github.com/spf13/cobra v1.0.0 8 | ) 9 | -------------------------------------------------------------------------------- /util/config.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | const ( 4 | // 如果是私有部署 SafeU 请修改链接 5 | SAFEU_BASE_URL = "https://api.safeu.a2os.club" 6 | VERSION = "v1.0.0-beta" 7 | DEFAULT_PASSWORD = "" 8 | DEFAULT_DOWN_COUNT = 10 9 | DEFAULT_EXPIRE_TIME = 8 10 | ) 11 | -------------------------------------------------------------------------------- /upload/file_test.go: -------------------------------------------------------------------------------- 1 | package upload 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestUploadFile_buildUploadRequest(t *testing.T) { 8 | var u Instance 9 | var f UploadFile 10 | err := u.getUploadPolicy() 11 | if err != nil { 12 | t.Error("buildUploadRequest error", err) 13 | } 14 | err = f.buildUploadRequest(u.UploadPolicy, "/tmp/upload.txt") 15 | if err != nil { 16 | t.Error("buildUploadRequest error", err) 17 | } 18 | t.Logf("%s", f.Url) 19 | t.Logf("%v", f.Values) 20 | } 21 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | 7 | build: 8 | name: Build 9 | runs-on: ubuntu-latest 10 | steps: 11 | 12 | - name: Set up Go 1.x 13 | uses: actions/setup-go@v2 14 | with: 15 | go-version: ^1.13 16 | id: go 17 | 18 | - name: Check out code into the Go module directory 19 | uses: actions/checkout@v2 20 | 21 | - name: Get dependencies 22 | run: | 23 | go get -v -t -d ./... 24 | if [ -f Gopkg.toml ]; then 25 | curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh 26 | dep ensure 27 | fi 28 | 29 | - name: Build 30 | run: go build -v . 31 | 32 | - name: Test 33 | run: go test -v . 34 | -------------------------------------------------------------------------------- /get/write_counter.go: -------------------------------------------------------------------------------- 1 | package get 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/dustin/go-humanize" 8 | ) 9 | 10 | type WriteCounter struct { 11 | Total uint64 12 | } 13 | 14 | func (wc *WriteCounter) Write(p []byte) (int, error) { 15 | n := len(p) 16 | wc.Total += uint64(n) 17 | wc.PrintProgress() 18 | return n, nil 19 | } 20 | 21 | func (wc WriteCounter) PrintProgress() { 22 | // Clear the line by using a character return to go back to the start and remove 23 | // the remaining characters by filling it with spaces 24 | fmt.Printf("\r%s", strings.Repeat(" ", 35)) 25 | 26 | // Return again and print current status of download 27 | // We use the humanize package to print the bytes in a meaningful way (e.g. 10 MB) 28 | fmt.Printf("\rDownloading... %s complete", humanize.Bytes(wc.Total)) 29 | } 30 | -------------------------------------------------------------------------------- /cmd/get.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/NUAA-Open-Source/safeu-cli/get" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | var userRecode string 9 | var userPassword string 10 | var targetDir string 11 | var isPrint bool 12 | 13 | func init() { 14 | getCmd.Flags().StringVarP(&userPassword, "password", "p", "", "specific password") 15 | getCmd.Flags().StringVarP(&targetDir, "dir", "d", "", "download to specific directory") 16 | getCmd.Flags().BoolVarP(&isPrint, "print", "", false, "print the file URL directly, then you can download the file by other download tools (e.g. wget, aria2).") 17 | } 18 | 19 | var getCmd = &cobra.Command{ 20 | Use: "get", 21 | Short: "Download file(s) from SafeU", 22 | Long: `Download file(s) by this command. 23 | SafeU is responsible for ensuring download speed and file safety :) 24 | `, 25 | Args: cobra.MinimumNArgs(1), 26 | Run: func(cmd *cobra.Command, args []string) { 27 | userRecode = args[0] 28 | get.Start(userRecode, userPassword, targetDir, isPrint) 29 | }, 30 | } 31 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/NUAA-Open-Source/safeu-cli/util" 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | var ( 11 | rootCmd = &cobra.Command{ 12 | Use: "safeu", 13 | Short: "SafeU CLI is the command line for SafeU", 14 | Long: `SafeU CLI is the command line tool for SafeU. 15 | 16 | You can access SafeU by via website: https://safeu.a2os.club/ 17 | Any question please open issue on https://github.com/NUAA-Open-Source/safeu-cli/issues/new`, 18 | } 19 | ) 20 | 21 | func Execute() error { 22 | return rootCmd.Execute() 23 | } 24 | 25 | func init() { 26 | rootCmd.AddCommand(versionCmd) 27 | rootCmd.SetHelpCommand(helpCmd) 28 | 29 | rootCmd.AddCommand(uploadCmd) 30 | rootCmd.AddCommand(getCmd) 31 | } 32 | 33 | // 打印版本 34 | var versionCmd = &cobra.Command{ 35 | Use: "version", 36 | Short: "The version number of SafeU CLI tool", 37 | Run: func(cmd *cobra.Command, args []string) { 38 | fmt.Println(util.VERSION) 39 | }, 40 | } 41 | 42 | var helpCmd = &cobra.Command{ 43 | Use: "help", 44 | Short: "help", 45 | Run: func(cmd *cobra.Command, args []string) { 46 | fmt.Println(`upload one file : safeu upload filename 47 | upload more file : safeu upload filename1 filename2 filename3 .... 48 | `) 49 | }, 50 | } 51 | -------------------------------------------------------------------------------- /get/model.go: -------------------------------------------------------------------------------- 1 | package get 2 | 3 | type ValidationRequest struct { 4 | Password string `json:"password"` 5 | } 6 | 7 | type ValidationResponse struct { 8 | Token string `json:"token"` 9 | Items []ItemDownload `json:"items"` 10 | } 11 | 12 | type DownloadRequest struct { 13 | Full bool `json:"full"` 14 | Items []ItemDownload `json:"items"` 15 | } 16 | 17 | type DownloadResponse struct { 18 | URL string `json:"url"` 19 | } 20 | 21 | type MinusDownCountRequest struct { 22 | Bucket string `json:"bucket"` 23 | Path string `json:"path"` 24 | } 25 | 26 | type ItemDownload struct { 27 | OriginalName string `json:"original_name"` 28 | Protocol string `json:"protocol"` 29 | Bucket string `json:"bucket"` 30 | Endpoint string `json:"endpoint"` 31 | Path string `json:"path"` 32 | } 33 | 34 | // 下载包结构体 35 | type DownloadModel struct { 36 | Dir string // 下载目录(默认为当前目录) 37 | Filepath string // 文件下载地址 38 | UserRecode string // 提取码 39 | UserPassword string // 密码 40 | Csrf string // CSRF 口令 41 | Cookie string // Cookie 42 | Token string // 临时认证 Token 43 | Items []ItemDownload // 需要下载的文件信息 44 | URL string // 已签名的下载链接 45 | } 46 | -------------------------------------------------------------------------------- /.github/workflows/install.yml: -------------------------------------------------------------------------------- 1 | name: Install 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | install: 7 | name: Install 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Check out the latest code 11 | uses: actions/checkout@v2 12 | # - name: run install script 13 | # run: | 14 | # alias wget="wget --no-check-certificate" 15 | # sh -c "$(echo "alias wget=\"wget --no-check-certificate\""; yes | wget -qO- https://raw.githubusercontent.com/NUAA-Open-Source/safeu-cli/master/install.sh)" 16 | 17 | - name: print install script help message 18 | run: | 19 | sh ./install.sh --help 20 | # should not have safeu command 21 | if [ -x "$(command -v safeu)" ]; then echo "should not have the safeu command"; exit 1; else exit 0; fi 22 | 23 | - name: print install script version for safeu-cli 24 | run: | 25 | sh ./install.sh --version 26 | # should not have safeu command 27 | if [ -x "$(command -v safeu)" ]; then echo "should not have the safeu command"; exit 1; else exit 0; fi 28 | 29 | - name: run install script 30 | run: sh ./install.sh 31 | 32 | - name: test safeu cli 33 | run: safeu version 34 | 35 | - name: remove the safeu cli 36 | run: | 37 | sudo rm -rf /usr/local/bin/safeu 38 | # should not have safeu command 39 | if [ -x "$(command -v safeu)" ]; then echo "should not have the safeu command"; exit 1; else exit 0; fi 40 | 41 | - name: run install script with local option 42 | run: | 43 | export PATH=$HOME/.local/bin:$PATH 44 | sh ./install.sh --local 45 | 46 | - name: test safeu cli 47 | run: | 48 | export PATH=$HOME/.local/bin:$PATH 49 | safeu version 50 | -------------------------------------------------------------------------------- /cmd/upload.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/NUAA-Open-Source/safeu-cli/upload" 7 | "github.com/NUAA-Open-Source/safeu-cli/util" 8 | 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | var UserRecode string 13 | var UserPassword string 14 | var UserDownCount int 15 | var UserExpireTime int 16 | 17 | func init() { 18 | uploadCmd.Flags().StringVarP(&UserRecode, "recode", "r", "", "specific recode") 19 | uploadCmd.Flags().StringVarP(&UserPassword, "password", "p", "", "specific password") 20 | uploadCmd.Flags().IntVarP(&UserDownCount, "downcount", "d", 0, "specific down count") 21 | uploadCmd.Flags().IntVarP(&UserExpireTime, "expiretime", "e", 0, "specific expire time") 22 | } 23 | 24 | var uploadCmd = &cobra.Command{ 25 | Use: "upload", 26 | Short: "Upload file to SafeU", 27 | Long: `Send and Share file by this command. 28 | SafeU is responsible for ensuring upload speed and file safety 29 | `, 30 | Args: cobra.MinimumNArgs(1), 31 | Run: func(cmd *cobra.Command, args []string) { 32 | var uploadFiles []string 33 | 34 | for i := 0; i < len(args); i++ { 35 | fileName := args[i] 36 | fmt.Println("ready upload file: " + fileName) 37 | uploadFiles = append(uploadFiles, fileName) 38 | } 39 | printUserModifyInfo() 40 | upload.Start(uploadFiles, UserRecode, UserPassword, UserDownCount, UserExpireTime) 41 | }, 42 | } 43 | 44 | func printUserModifyInfo() { 45 | var showNotifyRecode string 46 | var showNotifyPassword string 47 | var showNotifyDownCount int 48 | var showNotifyExpireTime int 49 | if UserRecode == "" { 50 | showNotifyRecode = "RANDOM" 51 | } else { 52 | showNotifyRecode = UserRecode 53 | } 54 | if UserPassword == "" { 55 | showNotifyPassword = "EMPTY" 56 | } else { 57 | showNotifyPassword = UserPassword 58 | } 59 | if UserDownCount == 0 { 60 | showNotifyDownCount = util.DEFAULT_DOWN_COUNT 61 | } else { 62 | showNotifyDownCount = UserDownCount 63 | } 64 | if UserExpireTime == 0 { 65 | showNotifyExpireTime = util.DEFAULT_EXPIRE_TIME 66 | } else { 67 | showNotifyExpireTime = UserExpireTime 68 | } 69 | fmt.Println(fmt.Sprintf("user setting recode:%s , password: %s , downcount: %d , expiretime: %d", showNotifyRecode, showNotifyPassword, showNotifyDownCount, showNotifyExpireTime)) 70 | } 71 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Go template 3 | # Binaries for programs and plugins 4 | *.exe 5 | *.exe~ 6 | *.dll 7 | *.so 8 | *.dylib 9 | 10 | # Test binary, built with `go test -c` 11 | *.test 12 | 13 | # Output of the go coverage tool, specifically when used with LiteIDE 14 | *.out 15 | 16 | # Dependency directories (remove the comment below to include it) 17 | # vendor/ 18 | 19 | ### JetBrains template 20 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 21 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 22 | 23 | # User-specific stuff 24 | .idea/**/workspace.xml 25 | .idea/**/tasks.xml 26 | .idea/**/usage.statistics.xml 27 | .idea/**/dictionaries 28 | .idea/**/shelf 29 | 30 | # Generated files 31 | .idea/**/contentModel.xml 32 | 33 | # Sensitive or high-churn files 34 | .idea/**/dataSources/ 35 | .idea/**/dataSources.ids 36 | .idea/**/dataSources.local.xml 37 | .idea/**/sqlDataSources.xml 38 | .idea/**/dynamic.xml 39 | .idea/**/uiDesigner.xml 40 | .idea/**/dbnavigator.xml 41 | 42 | # Gradle 43 | .idea/**/gradle.xml 44 | .idea/**/libraries 45 | 46 | # Gradle and Maven with auto-import 47 | # When using Gradle or Maven with auto-import, you should exclude module files, 48 | # since they will be recreated, and may cause churn. Uncomment if using 49 | # auto-import. 50 | # .idea/artifacts 51 | # .idea/compiler.xml 52 | # .idea/jarRepositories.xml 53 | # .idea/modules.xml 54 | # .idea/*.iml 55 | # .idea/modules 56 | # *.iml 57 | # *.ipr 58 | 59 | # CMake 60 | cmake-build-*/ 61 | 62 | # Mongo Explorer plugin 63 | .idea/**/mongoSettings.xml 64 | 65 | # File-based project format 66 | *.iws 67 | 68 | # IntelliJ 69 | out/ 70 | 71 | # mpeltonen/sbt-idea plugin 72 | .idea_modules/ 73 | 74 | # JIRA plugin 75 | atlassian-ide-plugin.xml 76 | 77 | # Cursive Clojure plugin 78 | .idea/replstate.xml 79 | 80 | # Crashlytics plugin (for Android Studio and IntelliJ) 81 | com_crashlytics_export_strings.xml 82 | crashlytics.properties 83 | crashlytics-build.properties 84 | fabric.properties 85 | 86 | # Editor-based Rest Client 87 | .idea/httpRequests 88 | 89 | # Android studio 3.1+ serialized cache file 90 | .idea/caches/build_file_checksums.ser 91 | 92 | .idea/.gitignore 93 | .idea/Nuwa.iml 94 | .idea/misc.xml 95 | .idea/modules.xml 96 | .idea/vcs.xml 97 | Nuwa 98 | safeu* 99 | 100 | *.zip 101 | -------------------------------------------------------------------------------- /upload/model.go: -------------------------------------------------------------------------------- 1 | package upload 2 | 3 | // 上传相关数据结构 4 | import ( 5 | "io" 6 | "net/http" 7 | "os" 8 | ) 9 | 10 | // 上传策略 11 | 12 | // 阿里云 OSS 接口返回 的 uuid 13 | type UploadResponse struct { 14 | UUID string 15 | } 16 | 17 | // 上传文件对应状态 18 | const ( 19 | UploadFileReadyCode = iota // 0 20 | UploadFileSuccessCode // 1 21 | UploadFileFailedCode // 2 22 | ) 23 | 24 | // 上传实例 25 | // 一次上传策略对应一个上传实例 26 | // 一个上传实例包含多个文件上传 27 | type Instance struct { 28 | UploadPolicy UploadPolicy 29 | UploadFiles []UploadFile 30 | Owner string 31 | Recode string 32 | CSRF string // csrf token 33 | Cookie string // csrf cookie 34 | Password string // 密码 35 | DownCount int // 下载次数 36 | ExpireTime int // 过期时间(小时) 37 | } 38 | 39 | // 上传策略 40 | type UploadPolicy struct { 41 | AccessID string 42 | Host string 43 | Expire int64 44 | Signature string 45 | Policy string 46 | Dir string 47 | Callback string 48 | } 49 | 50 | // 上传文件 51 | type UploadFile struct { 52 | File *os.File // 文件本体 53 | UploadResponse *UploadResponse // 上传到OSS返回的结构体 54 | StatusCode int // 状态码 55 | 56 | Client *http.Client // http Client 57 | Url string // 上传地址 58 | Values map[string]io.Reader // 上传结构体 59 | } 60 | 61 | // 工具函数区 62 | // 上传文件完毕发送Finish请求 63 | // example : {"files":["0f652be1-394b-43f6-95bf-948de1520d0c","5561366d-e18c-4e48-8ae9-ec46f0a70ecf"]} 64 | type FinishRequest struct { 65 | Files []string `json:"files"` 66 | } 67 | 68 | // Finish 请求返回结果 69 | type FinishResponse struct { 70 | Owner string `json:"owner"` 71 | Recode string `json:"recode"` 72 | } 73 | 74 | // 修改请求 75 | // 修改提取码 76 | type ChangeRecode struct { 77 | Auth string `json:"auth"` // 如果设置了密码需要上传密码才可以修改提取码 78 | NewReCode string `json:"new_re_code"` 79 | UserToken string `json:"user_token"` 80 | } 81 | 82 | // 修改密码 83 | type ChangePassword struct { 84 | Auth string `json:"auth"` // 加密密码 85 | UserToken string `json:"user_token"` 86 | } 87 | 88 | // 修改下载次数 89 | type ChangeDownCount struct { 90 | NewDownCount int `json:"new_down_count"` 91 | UserToken string `json:"user_token"` 92 | } 93 | 94 | // 修改过期时间 95 | type ChangeExpireTime struct { 96 | NewExpireTime int `json:"new_expire_time"` 97 | UserToken string `json:"user_token"` 98 | } 99 | 100 | // 修改返回的请求 101 | type ChangeRequestResponse struct { 102 | Message string `json:"message"` 103 | } 104 | 105 | // 修改返回的错误请求 106 | type ChangeRequestErrorResponse struct { 107 | ErrCode int `json:"err_code"` 108 | Message string `json:"message"` 109 | } 110 | -------------------------------------------------------------------------------- /upload/file.go: -------------------------------------------------------------------------------- 1 | package upload 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "mime/multipart" 10 | "net/http" 11 | "os" 12 | "path" 13 | "strings" 14 | "time" 15 | ) 16 | 17 | // 构造文件上传请求 18 | // fileFullPath 文件完全路径 /home/just/pig.jpg 19 | func (f *UploadFile) buildUploadRequest(uploadPolicy UploadPolicy, fileFullPath string) (err error) { 20 | // TODO: http Client 优化及可配置 21 | f.Client = &http.Client{} 22 | // fileName:pig.jpg 23 | fileName := path.Base(fileFullPath) 24 | 25 | f.File, err = os.Open(fileFullPath) 26 | if err != nil { 27 | fmt.Println("buildUploadRequest open File failed ", err, "fileFullPath", fileFullPath) 28 | return err 29 | } 30 | fmt.Println("buildUploadRequest File ", fileName, "open success") 31 | f.Url = fmt.Sprintf("https://%s", uploadPolicy.Host) 32 | f.Values = map[string]io.Reader{ 33 | "name": strings.NewReader(fileName), 34 | "key": strings.NewReader(uploadPolicy.Dir + fileName), 35 | "policy": strings.NewReader(uploadPolicy.Policy), 36 | "OSSAccessKeyId": strings.NewReader(uploadPolicy.AccessID), 37 | "success_action_status": strings.NewReader("200"), 38 | "signature": strings.NewReader(uploadPolicy.Signature), 39 | "callback": strings.NewReader(uploadPolicy.Callback), 40 | } 41 | return 42 | } 43 | 44 | // 核心函数 上传文件 45 | // TODO: 上传进度条 46 | func (f *UploadFile) upload() (err error) { 47 | var b bytes.Buffer 48 | w := multipart.NewWriter(&b) 49 | for key, r := range f.Values { 50 | var fw io.Writer 51 | if x, ok := r.(io.Closer); ok { 52 | defer x.Close() 53 | } 54 | 55 | // 添加其他表单信息 56 | if fw, err = w.CreateFormField(key); err != nil { 57 | return 58 | } 59 | 60 | if _, err = io.Copy(fw, r); err != nil { 61 | return err 62 | } 63 | 64 | } 65 | // 拷贝文件 文件需要放到最末尾 https://www.alibabacloud.com/help/zh/doc-detail/42976.htm 66 | fw, err := w.CreateFormFile("File", f.File.Name()) 67 | if err != nil { 68 | return err 69 | } 70 | if _, err = io.Copy(fw, f.File); err != nil { 71 | return err 72 | } 73 | 74 | _ = w.Close() 75 | 76 | req, err := http.NewRequest("POST", f.Url, &b) 77 | if err != nil { 78 | return 79 | } 80 | 81 | req.Header.Set("Content-Type", w.FormDataContentType()) 82 | uploadBeginTime := time.Now() 83 | resp, err := f.Client.Do(req) 84 | if err != nil { 85 | return 86 | } 87 | // 读取返回响应 88 | respBody, _ := ioutil.ReadAll(resp.Body) 89 | uploadUseTime := time.Since(uploadBeginTime) 90 | fmt.Println("upload use time:", uploadUseTime) 91 | 92 | if resp.StatusCode != http.StatusOK { 93 | err = fmt.Errorf("upload File to aliyun oss failed, status code:%s,response body: %s", resp.Status, string(respBody)) 94 | return err 95 | } 96 | // 解析返回结果 97 | var uploadResponse UploadResponse 98 | err = json.Unmarshal(respBody, &uploadResponse) 99 | if err != nil { 100 | fmt.Println("handleUploadResponse json unmarshal failed", err) 101 | return err 102 | } 103 | f.UploadResponse = &uploadResponse 104 | return nil 105 | } 106 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # SafeU CLI 3 | 4 | ![Install](https://github.com/NUAA-Open-Source/safeu-cli/workflows/Install/badge.svg) 5 | ![Go](https://github.com/NUAA-Open-Source/safeu-cli/workflows/Go/badge.svg) 6 | 7 | A command line tool for SafeU (https://safeu.a2os.club). 8 | 9 | - [Install](#install) 10 | - [Install safeu-cli just for current user](#install-safeu-cli-just-for-current-user) 11 | - [China mainland optimized](#china-mainland-optimized) 12 | - [Usage](#usage) 13 | - [Upload](#upload) 14 | - [Upload one file](#upload-one-file) 15 | - [Upload more than one file](#upload-more-than-one-file) 16 | - [Set Password / Download Count / Expired Time](#set-password--download-count--expired-time) 17 | - [Full detail of upload command](#full-detail-of-upload-command) 18 | - [Download](#download) 19 | - [Full detail of get command](#full-detail-of-get-command) 20 | - [Demo](#demo) 21 | - [Compile](#compile) 22 | - [Known issues](#known-issues) 23 | - [License](#license) 24 | 25 | ## Install 26 | 27 | > If you are in China mainland, the install methods in [China mainland optimized](#china-mainland-optimized) is a better choice. 28 | 29 | > NOTICE: The following methods would download a pre-compiled safeu-cli binary file which is ONLY for 64-bit Linux. If you are using a different architecture or OS, just check the [Compile](#compile) section below to build your own binary package. 30 | 31 | `safeu-cli` is installed by running one of the following commands in your terminal. You can install this via the command-line with either `curl` or `wget`. 32 | 33 | via curl: 34 | 35 | ```bash 36 | $ sh -c "$(curl -fsSL https://raw.githubusercontent.com/NUAA-Open-Source/safeu-cli/master/install.sh)" 37 | ``` 38 | 39 | via wget: 40 | 41 | ```bash 42 | $ sh -c "$(wget -qO- https://raw.githubusercontent.com/NUAA-Open-Source/safeu-cli/master/install.sh)" 43 | ``` 44 | 45 | Congratulations, you have successfully installed the `safeu-cli` tool :tada: 46 | 47 | ### Install safeu-cli just for current user 48 | 49 | via curl: 50 | 51 | ```bash 52 | $ sh -c "$(curl -fsSL https://raw.githubusercontent.com/NUAA-Open-Source/safeu-cli/master/install.sh) --local" 53 | ``` 54 | 55 | via wget: 56 | 57 | ```bash 58 | $ sh -c "$(wget -qO- https://raw.githubusercontent.com/NUAA-Open-Source/safeu-cli/master/install.sh) --local" 59 | ``` 60 | 61 | ### China mainland optimized 62 | 63 | via curl: 64 | 65 | ```bash 66 | $ sh -c "$(curl -fsSL https://gitee.com/A2OS/safeu-cli/raw/master/install.sh) --cn" 67 | ``` 68 | 69 | via wget: 70 | 71 | ```bash 72 | $ sh -c "$(wget -qO- https://gitee.com/A2OS/safeu-cli/raw/master/install.sh) --cn" 73 | ``` 74 | 75 | > If you want to install `safeu-cli` locally by using the china mainland optimized script, just add `--local` option after the `--cn`. 76 | 77 | ## Usage 78 | 79 | ### Upload 80 | 81 | #### Upload one file 82 | ```bash 83 | $ safeu upload filename 84 | ``` 85 | 86 | #### Upload more than one file 87 | 88 | ```bash 89 | $ safeu upload filename1 filename2 filename3 90 | ``` 91 | 92 | #### Set Password / Download Count / Expired Time 93 | 94 | Ref to [Full deteail of upload command](#full-detail-of-upload-command). 95 | 96 | Examples for this section will be supplemented lately. 97 | 98 | #### Full detail of upload command 99 | 100 | ```bash 101 | $ safeu upload --help 102 | Send and Share file by this command. 103 | SafeU is responsible for ensuring upload speed and file safety 104 | 105 | Usage: 106 | safeu upload [flags] 107 | 108 | Flags: 109 | -d, --downcount int specific down count 110 | -e, --expiretime int specific expire time 111 | -p, --password string specific password 112 | -r, --recode string specific recode 113 | -h, --help help for upload 114 | ``` 115 | 116 | ### Download 117 | ```bash 118 | $ safeu get your_recode 119 | ``` 120 | 121 | #### Full detail of get command 122 | 123 | ```bash 124 | $ safeu get --help 125 | Download file(s) by this command. 126 | SafeU is responsible for ensuring download speed and file safety :) 127 | 128 | Usage: 129 | safeu get [flags] 130 | 131 | Flags: 132 | -d, --dir string download to specific directory 133 | -p, --password string specific password 134 | --print print the file URL directly, then you can 135 | download the file by other download tools 136 | (e.g. wget, aria2). 137 | -h, --help help for get 138 | ``` 139 | 140 | ## Demo 141 | 142 | [![asciicast](https://asciinema.org/a/iZgrbrUpli4kxOQlOBco9jamH.svg)](https://asciinema.org/a/iZgrbrUpli4kxOQlOBco9jamH) 143 | 144 | ## Compile 145 | 146 | ```bash 147 | # build binary for your OS 148 | # need go version > 1.13 149 | make build 150 | 151 | # build binary for Linux 152 | make linux-build 153 | ``` 154 | 155 | ## Known issues 156 | 157 | ## License 158 | 159 | This project is open-sourced by [Apache 2.0](./LICENSE). 160 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This script should be run via curl: 4 | # sh -c "$(curl -fsSL https://raw.githubusercontent.com/NUAA-Open-Source/safeu-cli/master/install.sh)" 5 | # or wget: 6 | # sh -c "$(wget -qO- https://raw.githubusercontent.com/NUAA-Open-Source/safeu-cli/master/install.sh)" 7 | # 8 | # As an alternative, you can first download the install script and run it afterwards: 9 | # wget https://raw.githubusercontent.com/NUAA-Open-Source/safeu-cli/master/install.sh 10 | # sh install.sh 11 | 12 | SAFEU_RELEASE="https://github.com/NUAA-Open-Source/safeu-cli/releases/download/v1.0.0-beta/safeu-linux-x64" 13 | SAFEU_CN_RELEASE="https://triplez-public-1251926021.cos.ap-shanghai.myqcloud.com/safeu-cli/v1.0.0-beta/safeu-linux-x64" 14 | VERSION=v1.0.0-beta 15 | 16 | BIN_DIR=/usr/local/bin 17 | BIN_FILENAME=safeu.tmp 18 | SAFEU_CMD=safeu 19 | IS_LOCAL=0 20 | IS_CN=0 21 | 22 | 23 | show_help() { 24 | cat <<- EOF 25 | SafeU CLI tool install script. 26 | 27 | Usage: ./install.sh [options] 28 | 29 | Options: 30 | --local Install safeu-cli locally (in ~/.local/bin). 31 | --cn Use china mainland optimized install script. 32 | --version Show the safeu-cli release version. 33 | --help Show this help message. 34 | 35 | You can access SafeU by via website: https://safeu.a2os.club/ 36 | Any question please open issue on: https://github.com/NUAA-Open-Source/safeu-cli/issues/new 37 | EOF 38 | } 39 | 40 | show_version() { 41 | echo "$VERSION" 42 | } 43 | 44 | error() { 45 | echo ${RED}"Error: $@"${RESET} >&2 46 | } 47 | 48 | setup_color() { 49 | # Only use colors if connected to a terminal 50 | if [ -t 1 ]; then 51 | RED=$(printf '\033[31m') 52 | GREEN=$(printf '\033[32m') 53 | YELLOW=$(printf '\033[33m') 54 | BLUE=$(printf '\033[34m') 55 | BOLD=$(printf '\033[1m') 56 | RESET=$(printf '\033[m') 57 | else 58 | RED="" 59 | GREEN="" 60 | YELLOW="" 61 | BLUE="" 62 | BOLD="" 63 | RESET="" 64 | fi 65 | } 66 | 67 | download_safeu_cli() { 68 | if [ $IS_CN -eq 1 ]; then 69 | wget -cO ${BIN_FILENAME} ${SAFEU_CN_RELEASE} || { 70 | error "cannot download safeu-cli by ${SAFEU_CN_RELEASE}" 71 | exit 1 72 | } 73 | else 74 | wget -cO ${BIN_FILENAME} ${SAFEU_RELEASE} || { 75 | error "cannot download safeu-cli by ${SAFEU_RELEASE}" 76 | exit 1 77 | } 78 | fi 79 | 80 | } 81 | 82 | install_scope() { 83 | if [ "$(id -u)" = "0" ]; then 84 | # the user has privileges, do not need to use sudo 85 | IS_LOCAL=1 86 | BIN_DIR=/usr/local/bin 87 | return 88 | fi 89 | 90 | if [ $IS_LOCAL -eq 1 ] ; then 91 | BIN_DIR=${HOME}/.local/bin 92 | else 93 | BIN_DIR=/usr/local/bin 94 | fi 95 | } 96 | 97 | install_safeu_cli() { 98 | if [ ${IS_LOCAL} -eq 1 ]; then 99 | install -Dm755 "${BIN_FILENAME}" "${BIN_DIR}/${SAFEU_CMD}" || { 100 | error "install the safeu-cli tool failed" 101 | exit 1 102 | } 103 | else 104 | sudo install -Dm755 "${BIN_FILENAME}" "${BIN_DIR}/${SAFEU_CMD}" || { 105 | error "install the safeu-cli tool failed" 106 | exit 1 107 | } 108 | fi 109 | } 110 | 111 | post_install() { 112 | rm -f ${BIN_FILENAME} 113 | printf "$GREEN" 114 | 115 | cat <<-'EOF' 116 | ____ __ _ _ ____ _ ___ 117 | / ___| __ _ / _| ___| | | | / ___| | |_ _| 118 | \___ \ / _` | |_ / _ \ | | | | | | | | | 119 | ___) | (_| | _| __/ |_| | | |___| |___ | | 120 | |____/ \__,_|_| \___|\___/ \____|_____|___| ....is now installed! 121 | 122 | Now you can upload and download files via "safeu" command ! 123 | If you have further questions, you can find support in here: 124 | https://github.com/NUAA-Open-Source/safeu-cli/issues 125 | 126 | EOF 127 | printf " Current installed safeu-cli version: $(safeu version)\n" 128 | printf "$RESET" 129 | } 130 | 131 | get_args() { 132 | for arg in "$@"; do 133 | case $arg in 134 | --cn) 135 | IS_CN=1 136 | ;; 137 | --local) 138 | IS_LOCAL=1 139 | ;; 140 | --version) 141 | show_version 142 | exit 0 143 | ;; 144 | --help) 145 | show_help 146 | exit 0 147 | ;; 148 | *) 149 | printf "${RED}Invalid option: '%s', check the help message below!${RESET}\n\n" $arg 150 | show_help 151 | exit 1 152 | ;; 153 | esac 154 | done 155 | } 156 | 157 | main() { 158 | # preparations 159 | setup_color 160 | get_args $@ 161 | install_scope 162 | 163 | # download & install safeu-cli 164 | download_safeu_cli 165 | install_safeu_cli 166 | 167 | # print success message 168 | post_install 169 | } 170 | 171 | main $@ 172 | -------------------------------------------------------------------------------- /upload/instance_test.go: -------------------------------------------------------------------------------- 1 | package upload 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "math/rand" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func init() { 12 | rand.Seed(time.Now().UnixNano()) 13 | } 14 | 15 | // use for generate a random string 16 | var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") 17 | 18 | func RandStringRunes(n int) string { 19 | b := make([]rune, n) 20 | for i := range b { 21 | b[i] = letterRunes[rand.Intn(len(letterRunes))] 22 | } 23 | return string(b) 24 | } 25 | 26 | func TestInstance_getCSRF(t *testing.T) { 27 | var u Instance 28 | err := u.getCSRF() 29 | if err != nil { 30 | t.Error("getCSRF error", err) 31 | } 32 | t.Log(u.CSRF) 33 | t.Log(u.Cookie) 34 | } 35 | 36 | func Test_requestUploadPolicy(t *testing.T) { 37 | resp, err := requestUploadPolicy() 38 | if err != nil { 39 | t.Error("requestUploadPolicy error", err) 40 | } 41 | respBody, _ := ioutil.ReadAll(resp.Body) 42 | t.Log("response Status : ", resp.Status) 43 | t.Log("response Headers : ", resp.Header) 44 | t.Log("response Body : ", string(respBody)) 45 | } 46 | 47 | func Test_getUploadPolicy(t *testing.T) { 48 | var u Instance 49 | err := u.getUploadPolicy() 50 | if err != nil { 51 | t.Error("requestUploadPolicy error", err) 52 | } 53 | t.Log("policy AccessID", u.UploadPolicy.AccessID) 54 | t.Log("policy Callback", u.UploadPolicy.Callback) 55 | t.Log("policy Dir", u.UploadPolicy.Dir) 56 | t.Log("policy Expire", u.UploadPolicy.Expire) 57 | t.Log("policy Host", u.UploadPolicy.Host) 58 | t.Log("policy Policy", u.UploadPolicy.Policy) 59 | t.Log("policy Signature", u.UploadPolicy.Signature) 60 | } 61 | 62 | func TestInstance_ready(t *testing.T) { 63 | var u Instance 64 | err := u.getUploadPolicy() 65 | if err != nil { 66 | t.Error("getUploadPolicy error", err) 67 | } 68 | err = u.ready([]string{"/tmp/test.txt"}) 69 | if err != nil { 70 | t.Error("ready error", err) 71 | } 72 | for k, v := range u.UploadFiles[0].Values { 73 | t.Log(k, v) 74 | } 75 | } 76 | 77 | func TestInstance_run(t *testing.T) { 78 | var u Instance 79 | err := u.getUploadPolicy() 80 | if err != nil { 81 | t.Error("getUploadPolicy error", err) 82 | } 83 | err = u.ready([]string{"/tmp/test.txt"}) 84 | if err != nil { 85 | t.Error("ready error", err) 86 | } 87 | errors := u.run() 88 | if len(errors) > 0 { 89 | for _, err := range errors { 90 | t.Error("run error", err) 91 | } 92 | } 93 | for _, file := range u.UploadFiles { 94 | t.Log(file.StatusCode) 95 | t.Log(file.Values) 96 | t.Log(file.UploadResponse) 97 | } 98 | } 99 | 100 | func TestInstance_finish(t *testing.T) { 101 | var u Instance 102 | err := u.getCSRF() 103 | if err != nil { 104 | t.Error("getCSRF error ", err) 105 | } 106 | err = u.getUploadPolicy() 107 | if err != nil { 108 | t.Error("getUploadPolicy error", err) 109 | } 110 | err = u.ready([]string{"/tmp/test.txt"}) 111 | if err != nil { 112 | t.Error("ready error", err) 113 | } 114 | errors := u.run() 115 | if len(errors) > 0 { 116 | for _, err := range errors { 117 | t.Error("run error", err) 118 | } 119 | } 120 | err = u.finish() 121 | if err != nil { 122 | t.Error("finish error", err) 123 | } 124 | 125 | t.Log("Recode :", u.Recode) 126 | t.Log("Owner :", u.Owner) 127 | } 128 | 129 | func Test_getSha256(t *testing.T) { 130 | test := "hello SafeU" 131 | fmt.Println("78b12bba56d6f4a6b94faa89163994a14a92f2d246460751b4a48747fd90cf81", getSha256(test)) 132 | } 133 | 134 | func TestStart(t *testing.T) { 135 | // create file for this test 136 | // please this action will rewrite your local file ! 137 | err := ioutil.WriteFile("/tmp/testsafeucli.txt", []byte("Hello"), 0755) 138 | if err != nil { 139 | t.Error("Unable to write test file ", err) 140 | } 141 | err = ioutil.WriteFile("/tmp/testsafeucli2.txt", []byte("SafeU"), 0755) 142 | if err != nil { 143 | t.Error("Unable to write test file ", err) 144 | } 145 | type args struct { 146 | fileFullPaths []string 147 | userRecode string 148 | userPassword string 149 | userDownCount int 150 | userExpireTime int 151 | } 152 | tests := []struct { 153 | name string 154 | args args 155 | }{ 156 | { 157 | name: "one file with out modify", 158 | args: args{ 159 | fileFullPaths: []string{"/tmp/testsafeucli.txt"}, 160 | userRecode: "", 161 | userPassword: "", 162 | userDownCount: 0, 163 | userExpireTime: 0, 164 | }, 165 | }, 166 | { 167 | name: "more file with out modify", 168 | args: args{ 169 | fileFullPaths: []string{"/tmp/testsafeucli.txt", "/tmp/testsafeucli2.txt"}, 170 | userRecode: "", 171 | userPassword: "", 172 | userDownCount: 0, 173 | userExpireTime: 0, 174 | }, 175 | }, 176 | { 177 | name: "one file with modify", 178 | args: args{ 179 | fileFullPaths: []string{"/tmp/testsafeucli.txt"}, 180 | userRecode: RandStringRunes(10), 181 | userPassword: RandStringRunes(10), 182 | userDownCount: 4, 183 | userExpireTime: 10, 184 | }, 185 | }, 186 | { 187 | name: "more file with modify", 188 | args: args{ 189 | fileFullPaths: []string{"/tmp/testsafeucli.txt", "/tmp/testsafeucli2.txt"}, 190 | userRecode: RandStringRunes(10), 191 | userPassword: RandStringRunes(10), 192 | userDownCount: 4, 193 | userExpireTime: 8, 194 | }, 195 | }, 196 | } 197 | for _, tt := range tests { 198 | t.Run(tt.name, func(t *testing.T) { 199 | Start(tt.args.fileFullPaths, tt.args.userRecode, tt.args.userPassword, tt.args.userDownCount, tt.args.userExpireTime) 200 | }) 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /get/download.go: -------------------------------------------------------------------------------- 1 | package get 2 | 3 | import ( 4 | "crypto/sha256" 5 | "encoding/hex" 6 | "encoding/json" 7 | "fmt" 8 | "io" 9 | "io/ioutil" 10 | "net/http" 11 | "os" 12 | "strings" 13 | 14 | "github.com/NUAA-Open-Source/safeu-cli/util" 15 | ) 16 | 17 | func (dm *DownloadModel) getCsrf() error { 18 | client := &http.Client{} 19 | req, err := http.NewRequest("GET", util.SAFEU_BASE_URL+"/csrf", nil) 20 | resp, err := client.Do(req) 21 | if err != nil { 22 | return err 23 | } 24 | dm.Csrf = resp.Header.Get("X-Csrf-Token") 25 | dm.Cookie = resp.Header.Get("Set-Cookie") 26 | return nil 27 | } 28 | 29 | func (dm *DownloadModel) validation() (err error) { 30 | // validate recode & password 31 | var valiReq ValidationRequest 32 | 33 | hasher := sha256.New() 34 | hasher.Write([]byte(dm.UserPassword)) 35 | sha256Pass := hex.EncodeToString(hasher.Sum(nil)) 36 | valiReq.Password = sha256Pass 37 | 38 | jsonStr, err := json.Marshal(valiReq) 39 | if err != nil { 40 | fmt.Println("validation json marshal error", err) 41 | return err 42 | } 43 | 44 | client := &http.Client{} 45 | req, err := http.NewRequest("POST", util.SAFEU_BASE_URL+"/v1/validation/"+dm.UserRecode, strings.NewReader(string(jsonStr))) 46 | 47 | if err != nil { 48 | fmt.Println("validation request create NewRequest failed", err) 49 | return err 50 | } 51 | req.Header.Set("x-csrf-token", dm.Csrf) 52 | req.Header.Set("cookie", dm.Cookie) 53 | 54 | resp, err := client.Do(req) 55 | if err != nil { 56 | fmt.Println("validation request call failed", err) 57 | return err 58 | } 59 | 60 | respBody, _ := ioutil.ReadAll(resp.Body) 61 | 62 | if resp.StatusCode != http.StatusOK { 63 | if resp.StatusCode == http.StatusUnauthorized { 64 | fmt.Println("password needed / invalid password for this recode, validation failed") 65 | return fmt.Errorf("invalid password") 66 | } 67 | fmt.Println("validation requset response is not 200") 68 | return fmt.Errorf("validation request response code: %d, content: %s", resp.StatusCode, respBody) 69 | } 70 | 71 | var valiRes ValidationResponse 72 | err = json.Unmarshal(respBody, &valiRes) 73 | if err != nil { 74 | fmt.Println("validation json unmarshal failed", err) 75 | return err 76 | } 77 | 78 | dm.Token = valiRes.Token 79 | dm.Items = valiRes.Items 80 | return nil 81 | } 82 | 83 | func (dm *DownloadModel) getDownloadURL(isPrint bool) error { 84 | // download logics 85 | var downReq DownloadRequest 86 | downReq.Full = true 87 | downReq.Items = dm.Items 88 | 89 | jsonReq, err := json.Marshal(&downReq) 90 | if err != nil { 91 | fmt.Println("download json marshal error", err) 92 | return err 93 | } 94 | 95 | client := &http.Client{} 96 | req, err := http.NewRequest("POST", util.SAFEU_BASE_URL+"/v1/item/"+dm.UserRecode, strings.NewReader(string(jsonReq))) 97 | if err != nil { 98 | fmt.Println("download create NewRequest failed", err) 99 | return err 100 | } 101 | req.Header.Set("x-csrf-token", dm.Csrf) 102 | req.Header.Set("cookie", dm.Cookie) 103 | req.Header.Set("token", dm.Token) // auth token for this recode 104 | 105 | resp, err := client.Do(req) 106 | if err != nil { 107 | fmt.Println("download request call failed", err) 108 | return err 109 | } 110 | 111 | respBody, _ := ioutil.ReadAll(resp.Body) 112 | 113 | if resp.StatusCode != http.StatusOK { 114 | fmt.Println("download request response status is not 200") 115 | return fmt.Errorf("download request response return code: %d, content: %s", resp.StatusCode, respBody) 116 | } 117 | 118 | var downRes DownloadResponse 119 | err = json.Unmarshal(respBody, &downRes) 120 | if err != nil { 121 | fmt.Println("download json unmarshal failed") 122 | return err 123 | } 124 | 125 | if isPrint { 126 | fmt.Println(downRes.URL) 127 | os.Exit(0) 128 | } 129 | 130 | dm.URL = downRes.URL 131 | return nil 132 | } 133 | 134 | func (dm *DownloadModel) downloadFile() error { 135 | // actual download file 136 | // and rename the file 137 | if dm.Dir == "" { 138 | // default: get the current working directory 139 | dir, err := os.Getwd() 140 | if err != nil { 141 | fmt.Println("cannot get current directory, please try `--dir` option instead.") 142 | return fmt.Errorf("cannot get current directory") 143 | } 144 | dm.Dir = dir 145 | } 146 | 147 | tmpFilename := dm.Dir + string(os.PathSeparator) + string(dm.UserRecode) + ".tmp" 148 | filename := dm.Dir + string(os.PathSeparator) + string(dm.UserRecode) + ".zip" 149 | if len(dm.Items) == 1 { 150 | tmpFilename = dm.Dir + string(os.PathSeparator) + dm.Items[0].OriginalName + ".tmp" 151 | filename = dm.Dir + string(os.PathSeparator) + dm.Items[0].OriginalName 152 | } 153 | 154 | out, err := os.Create(tmpFilename) 155 | if err != nil { 156 | fmt.Println("cannot create file", tmpFilename, ", error: ", err) 157 | return fmt.Errorf("create file error: %s", err) 158 | } 159 | 160 | resp, err := http.Get(dm.URL) 161 | if err != nil { 162 | out.Close() 163 | return fmt.Errorf("cannot get file %s, error: %s", dm.URL, err) 164 | } 165 | defer resp.Body.Close() 166 | 167 | // Create our progress reporter and pass it to be used alongside our writer 168 | counter := &WriteCounter{} 169 | if _, err = io.Copy(out, io.TeeReader(resp.Body, counter)); err != nil { 170 | out.Close() 171 | return err 172 | } 173 | 174 | // The progress use the same line so print a new line once it's finished downloading 175 | fmt.Print("\n") 176 | 177 | // Close the file without defer so it can happen before Rename() 178 | out.Close() 179 | 180 | if err = os.Rename(tmpFilename, filename); err != nil { 181 | return fmt.Errorf("cannot rename file %s to %s", tmpFilename, filename) 182 | } 183 | 184 | // print for debug 185 | // fmt.Printf("tmp file: %s, file: %s\n", tmpFilename, filename) 186 | dm.Filepath = filename 187 | return nil 188 | } 189 | 190 | func (dm *DownloadModel) minusDownCount() error { 191 | // minus download counter 192 | // download logics 193 | var minusReq MinusDownCountRequest 194 | client := &http.Client{} 195 | 196 | for _, item := range dm.Items { 197 | minusReq.Bucket = item.Bucket 198 | minusReq.Path = item.Path 199 | 200 | jsonReq, err := json.Marshal(&minusReq) 201 | if err != nil { 202 | return err 203 | } 204 | 205 | req, err := http.NewRequest("POST", util.SAFEU_BASE_URL+"/v1/minusDownCount/"+dm.UserRecode, strings.NewReader(string(jsonReq))) 206 | if err != nil { 207 | return err 208 | } 209 | req.Header.Set("x-csrf-token", dm.Csrf) 210 | req.Header.Set("cookie", dm.Cookie) 211 | 212 | // minus download counter, ignore any return content 213 | _, err = client.Do(req) 214 | if err != nil { 215 | return err 216 | } 217 | } 218 | 219 | return nil 220 | } 221 | 222 | func Start(recode string, password string, targetDir string, isPrint bool) { 223 | var dm DownloadModel 224 | 225 | dm.UserRecode = recode 226 | dm.UserPassword = password 227 | dm.Dir = targetDir 228 | 229 | // start := time.Now() 230 | err := dm.getCsrf() 231 | if err != nil { 232 | fmt.Println("get CSRF token error", err) 233 | os.Exit(1) 234 | } 235 | // elapsed := time.Since(start) 236 | // fmt.Println("get csrf cost:", elapsed) 237 | 238 | // start = time.Now() 239 | err = dm.validation() 240 | if err != nil { 241 | fmt.Println("validation error", err) 242 | os.Exit(1) 243 | } 244 | // elapsed = time.Since(start) 245 | // fmt.Println("validation cost:", elapsed) 246 | 247 | // start = time.Now() 248 | err = dm.getDownloadURL(isPrint) 249 | if err != nil { 250 | fmt.Println("download error", err) 251 | os.Exit(1) 252 | } 253 | // elapsed = time.Since(start) 254 | // fmt.Println("get download url cost:", elapsed) 255 | 256 | // start = time.Now() 257 | err = dm.downloadFile() 258 | if err != nil { 259 | fmt.Println("download file error", err) 260 | os.Exit(1) 261 | } 262 | // elapsed = time.Since(start) 263 | // fmt.Println("download file cost:", elapsed) 264 | 265 | // TODO: can be a goroutine 266 | // start = time.Now() 267 | err = dm.minusDownCount() 268 | if err != nil { 269 | // do nothing... 270 | fmt.Println("download minus counter error", err) 271 | os.Exit(1) 272 | } 273 | // elapsed = time.Since(start) 274 | // fmt.Println("minus downcount cost:", elapsed) 275 | 276 | // print for test 277 | // fmt.Printf("Recode: %s, Token: %s, URL: %s\n", dm.UserRecode, dm.Token, dm.URL) 278 | 279 | fmt.Println("Download complete! You can get your file at: " + dm.Filepath) 280 | 281 | } 282 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= 3 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 4 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 5 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 6 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 7 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 8 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 9 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 10 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 11 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 12 | github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 13 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 14 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 15 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 16 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 17 | github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 18 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 19 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 20 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 21 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= 22 | github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= 23 | github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 24 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= 25 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 26 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 27 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 28 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 29 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 30 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 31 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 32 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 33 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 34 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 35 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 36 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 37 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 38 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 39 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 40 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 41 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 42 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 43 | github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 44 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 45 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 46 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 47 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 48 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 49 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 50 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 51 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 52 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 53 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 54 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 55 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 56 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 57 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 58 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 59 | github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= 60 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 61 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 62 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 63 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 64 | github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= 65 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 66 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 67 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 68 | github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= 69 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 70 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 71 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 72 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 73 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 74 | github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= 75 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 76 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 77 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 78 | github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 79 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 80 | github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 81 | github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= 82 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 83 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 84 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 85 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 86 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 87 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 88 | github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= 89 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 90 | github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= 91 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 92 | github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= 93 | github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= 94 | github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= 95 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 96 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= 97 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 98 | github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU= 99 | github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= 100 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 101 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 102 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 103 | github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= 104 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 105 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= 106 | go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 107 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 108 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 109 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 110 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 111 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 112 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 113 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 114 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 115 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 116 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 117 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 118 | golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 119 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 120 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 121 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 122 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 123 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 124 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 125 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 126 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 127 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= 128 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 129 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 130 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 131 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= 132 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 133 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 134 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 135 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 136 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 137 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 138 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 139 | google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 140 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 141 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 142 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 143 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 144 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 145 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 146 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 147 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 148 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 149 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 150 | -------------------------------------------------------------------------------- /upload/instance.go: -------------------------------------------------------------------------------- 1 | package upload 2 | 3 | import ( 4 | "bytes" 5 | "crypto/sha256" 6 | "encoding/hex" 7 | "encoding/json" 8 | "fmt" 9 | "io/ioutil" 10 | "net/http" 11 | "os" 12 | "strings" 13 | "time" 14 | 15 | "github.com/NUAA-Open-Source/safeu-cli/util" 16 | ) 17 | 18 | // 第 0 步 获取CSRF Token 19 | func (u *Instance) getCSRF() error { 20 | client := &http.Client{} 21 | req, err := http.NewRequest("GET", util.SAFEU_BASE_URL+"/csrf", nil) 22 | resp, err := client.Do(req) 23 | if err != nil { 24 | return err 25 | } 26 | u.CSRF = resp.Header.Get("X-Csrf-Token") 27 | u.Cookie = resp.Header.Get("Set-Cookie") 28 | return nil 29 | } 30 | 31 | // 第一步 获取上传需要的策略参数 32 | func (u *Instance) getUploadPolicy() error { 33 | var uploadPolicy UploadPolicy 34 | resp, err := requestUploadPolicy() 35 | if err != nil { 36 | fmt.Println("getUploadPolicy requestUploadPolicy failed", err) 37 | } 38 | respBody, _ := ioutil.ReadAll(resp.Body) 39 | err = json.Unmarshal(respBody, &uploadPolicy) 40 | if err != nil { 41 | fmt.Println("getUploadPolicy json unmarshal failed", err) 42 | return err 43 | } 44 | u.UploadPolicy = uploadPolicy 45 | return nil 46 | } 47 | 48 | // 第二步 上传文件准备 49 | // fileFullPaths 文件完全路径 50 | func (u *Instance) ready(fileFullPaths []string) error { 51 | for _, fileFullPath := range fileFullPaths { 52 | var uploadFile UploadFile 53 | uploadFile.StatusCode = UploadFileReadyCode 54 | err := uploadFile.buildUploadRequest(u.UploadPolicy, fileFullPath) 55 | if err != nil { 56 | return err 57 | } 58 | u.UploadFiles = append(u.UploadFiles, uploadFile) 59 | } 60 | return nil 61 | } 62 | 63 | // 第三步 开始上传文件 64 | // TODO: 并发 65 | func (u *Instance) run() []error { 66 | var errors []error 67 | for key, file := range u.UploadFiles { 68 | if file.StatusCode == UploadFileReadyCode { 69 | err := file.upload() 70 | if err != nil { 71 | fmt.Println("upload File failed", err) 72 | file.StatusCode = UploadFileFailedCode 73 | errors = append(errors, err) 74 | } else { 75 | file.StatusCode = UploadFileSuccessCode 76 | } 77 | u.UploadFiles[key] = file 78 | } 79 | } 80 | return errors 81 | } 82 | 83 | // 第四步汇总上传结果中的 uuid 发送 finish 函数 84 | func (u *Instance) finish() error { 85 | var finishRequest FinishRequest 86 | for _, file := range u.UploadFiles { 87 | if file.StatusCode == UploadFileSuccessCode { 88 | finishRequest.Files = append(finishRequest.Files, file.UploadResponse.UUID) 89 | } 90 | } 91 | jsonStr, err := json.Marshal(finishRequest) 92 | if err != nil { 93 | fmt.Println("finish json marshal error", err) 94 | return err 95 | } 96 | 97 | resp, err := requestFinish(string(jsonStr), u.CSRF, u.Cookie) 98 | 99 | if err != nil { 100 | fmt.Println("finish request post failed", err) 101 | return err 102 | } 103 | 104 | respBody, _ := ioutil.ReadAll(resp.Body) 105 | 106 | if resp.StatusCode != http.StatusOK { 107 | fmt.Println("finish request reponse not 200") 108 | return fmt.Errorf("finish request reponse return code: %d ,content %s", resp.StatusCode, respBody) 109 | } 110 | 111 | // 回填auth token 以及 提取码 112 | var finishResponse FinishResponse 113 | err = json.Unmarshal(respBody, &finishResponse) 114 | if err != nil { 115 | fmt.Println("finish json unmarshal failed", err) 116 | return err 117 | } 118 | u.Owner = finishResponse.Owner 119 | u.Recode = finishResponse.Recode 120 | return nil 121 | } 122 | 123 | // 自定义修改 124 | func (u *Instance) modify(newRecode string) []error { 125 | var errors []error 126 | if newRecode != "" { 127 | var changeRecode ChangeRecode 128 | changeRecode.Auth = u.Password 129 | changeRecode.NewReCode = newRecode 130 | changeRecode.UserToken = u.Owner 131 | err := requestChangeRecode(u.Recode, changeRecode, u.CSRF, u.Cookie) 132 | if err != nil { 133 | fmt.Println("modify your recode failed. use random recode instead") 134 | errors = append(errors, fmt.Errorf("modify user recode failed")) 135 | } else { 136 | // 成功修改值 137 | u.Recode = newRecode 138 | } 139 | } 140 | 141 | if u.Password != "" { 142 | var changePassword ChangePassword 143 | changePassword.UserToken = u.Owner 144 | changePassword.Auth = getSha256(u.Password) 145 | err := requestChangePassword(u.Recode, changePassword, u.CSRF, u.Cookie) 146 | if err != nil { 147 | fmt.Println("modify your password failed. use empty password instead") 148 | errors = append(errors, fmt.Errorf("modify your password failed")) 149 | u.Password = util.DEFAULT_PASSWORD //设定失败重置为默认值 150 | } 151 | } else { 152 | u.Password = util.DEFAULT_PASSWORD 153 | } 154 | if u.DownCount != 0 { 155 | var changeDownCount ChangeDownCount 156 | changeDownCount.UserToken = u.Owner 157 | changeDownCount.NewDownCount = u.DownCount 158 | err := requestDownCount(u.Recode, changeDownCount, u.CSRF, u.Cookie) 159 | if err != nil { 160 | fmt.Println("modify your down count failed. use default down count (10 times) instead") 161 | errors = append(errors, fmt.Errorf("modify your down count")) 162 | u.DownCount = util.DEFAULT_DOWN_COUNT 163 | } 164 | 165 | } else { 166 | u.DownCount = util.DEFAULT_DOWN_COUNT 167 | } 168 | if u.ExpireTime != 0 { 169 | var changeExpireTime ChangeExpireTime 170 | changeExpireTime.NewExpireTime = u.ExpireTime 171 | changeExpireTime.UserToken = u.Owner 172 | err := requestExpireTime(u.Recode, changeExpireTime, u.CSRF, u.Cookie) 173 | if err != nil { 174 | fmt.Println("modify your expire time failed. use default expire time (8 hour) instead") 175 | errors = append(errors, fmt.Errorf("modify your expire time")) 176 | u.ExpireTime = util.DEFAULT_EXPIRE_TIME 177 | } 178 | } else { 179 | u.ExpireTime = util.DEFAULT_EXPIRE_TIME 180 | } 181 | return errors 182 | } 183 | 184 | // 辅助函数 185 | 186 | // 获取上传需要的策略参数 187 | func requestUploadPolicy() (*http.Response, error) { 188 | client := &http.Client{} 189 | req, err := http.NewRequest("GET", util.SAFEU_BASE_URL+"/v1/upload/policy", nil) 190 | resp, err := client.Do(req) 191 | if err != nil { 192 | fmt.Println("requestUploadPolicy error", err) 193 | return nil, err 194 | } 195 | return resp, nil 196 | } 197 | 198 | func requestFinish(body string, csrfToken string, cookie string) (response *http.Response, err error) { 199 | client := &http.Client{} 200 | req, err := http.NewRequest("POST", fmt.Sprintf("%s%s", util.SAFEU_BASE_URL, "/v1/upload/finish"), strings.NewReader(body)) 201 | 202 | if err != nil { 203 | fmt.Println("finish request create NewRequest failed", err) 204 | return response, err 205 | } 206 | req.Header.Set("x-csrf-token", csrfToken) 207 | req.Header.Set("cookie", cookie) 208 | req.Header.Set("sec-fetch-dest", "empty") 209 | req.Header.Set("sec-fetch-mode", "cors") 210 | req.Header.Set("sec-fetch-site", "same-site") 211 | req.Header.Set("origin", "https://safeu.a2os.club") 212 | resp, err := client.Do(req) 213 | if err != nil { 214 | fmt.Println("finish request call failed", err) 215 | return response, err 216 | } 217 | return resp, nil 218 | } 219 | 220 | func requestChangeRecode(oldRecode string, changeRecode ChangeRecode, csrfToken string, cookie string) (err error) { 221 | 222 | jsonStr, err := json.Marshal(changeRecode) 223 | if err != nil { 224 | fmt.Println("requestChangeRecode json marshal error", err) 225 | } 226 | 227 | client := &http.Client{} 228 | req, err := http.NewRequest("POST", fmt.Sprintf("%s%s%s", util.SAFEU_BASE_URL, "/v1/recode/", oldRecode), bytes.NewReader(jsonStr)) 229 | if err != nil { 230 | fmt.Println("requestChangeRecode request create NewRequest failed", err) 231 | return err 232 | } 233 | req.Header.Set("x-csrf-token", csrfToken) 234 | req.Header.Set("cookie", cookie) 235 | req.Header.Set("sec-fetch-dest", "empty") 236 | req.Header.Set("sec-fetch-mode", "cors") 237 | req.Header.Set("sec-fetch-site", "same-site") 238 | req.Header.Set("origin", "https://safeu.a2os.club") 239 | resp, err := client.Do(req) 240 | if err != nil { 241 | fmt.Println("requestChangeRecode call failed", err) 242 | return err 243 | } 244 | 245 | respBody, _ := ioutil.ReadAll(resp.Body) 246 | if resp.StatusCode != http.StatusOK { 247 | // 解析错误码和错误信息 248 | var changeRequestErrorResponse ChangeRequestErrorResponse 249 | err = json.Unmarshal(respBody, &changeRequestErrorResponse) 250 | if err != nil { 251 | fmt.Println("requestChangeRecode changeRequestErrorResponse json unmarshal failed", err) 252 | } 253 | fmt.Println("requestChangeRecode response show some problem : ", changeRequestErrorResponse.Message) 254 | return fmt.Errorf("requestChangeRecode reponse error %s", respBody) 255 | 256 | } 257 | 258 | var changeRequestResponse ChangeRequestResponse 259 | err = json.Unmarshal(respBody, &changeRequestResponse) 260 | if err != nil { 261 | fmt.Println("requestChangeRecode changeRequestResponse json unmarshal failed", err) 262 | return err 263 | } 264 | if changeRequestResponse.Message != "ok" { 265 | fmt.Println("requestChangeRecode response show some problem : ", respBody) 266 | return fmt.Errorf("requestChangeRecode reponse error %s", respBody) 267 | } 268 | return nil 269 | } 270 | 271 | func requestChangePassword(recode string, changePassword ChangePassword, csrfToken string, cookie string) (err error) { 272 | 273 | jsonStr, err := json.Marshal(changePassword) 274 | if err != nil { 275 | fmt.Println("requestChangePassword json marshal error", err) 276 | } 277 | 278 | client := &http.Client{} 279 | req, err := http.NewRequest("POST", fmt.Sprintf("%s%s%s", util.SAFEU_BASE_URL, "/v1/password/", recode), bytes.NewReader(jsonStr)) 280 | if err != nil { 281 | fmt.Println("requestChangePassword request create NewRequest failed", err) 282 | return err 283 | } 284 | req.Header.Set("x-csrf-token", csrfToken) 285 | req.Header.Set("cookie", cookie) 286 | req.Header.Set("sec-fetch-dest", "empty") 287 | req.Header.Set("sec-fetch-mode", "cors") 288 | req.Header.Set("sec-fetch-site", "same-site") 289 | req.Header.Set("origin", "https://safeu.a2os.club") 290 | resp, err := client.Do(req) 291 | if err != nil { 292 | fmt.Println("requestChangePassword call failed", err) 293 | return err 294 | } 295 | 296 | respBody, _ := ioutil.ReadAll(resp.Body) 297 | if resp.StatusCode != http.StatusOK { 298 | // 解析错误码和错误信息 299 | var changeRequestErrorResponse ChangeRequestErrorResponse 300 | err = json.Unmarshal(respBody, &changeRequestErrorResponse) 301 | if err != nil { 302 | fmt.Println("requestChangePassword changeRequestErrorResponse json unmarshal failed", err) 303 | } 304 | fmt.Println("requestChangePassword response show some problem : ", changeRequestErrorResponse.Message) 305 | return fmt.Errorf("requestChangePassword reponse error %s", respBody) 306 | 307 | } 308 | 309 | var changeRequestResponse ChangeRequestResponse 310 | err = json.Unmarshal(respBody, &changeRequestResponse) 311 | if err != nil { 312 | fmt.Println("requestChangePassword changeRequestResponse json unmarshal failed", err) 313 | return err 314 | } 315 | if changeRequestResponse.Message != "ok" { 316 | fmt.Println("requestChangePassword response show some problem : ", respBody) 317 | return fmt.Errorf("requestChangePassword reponse error %s", respBody) 318 | } 319 | return nil 320 | } 321 | 322 | func requestExpireTime(recode string, changeExpireTime ChangeExpireTime, csrfToken string, cookie string) (err error) { 323 | 324 | jsonStr, err := json.Marshal(changeExpireTime) 325 | if err != nil { 326 | fmt.Println("requestExpireTime json marshal error", err) 327 | } 328 | 329 | client := &http.Client{} 330 | req, err := http.NewRequest("POST", fmt.Sprintf("%s%s%s", util.SAFEU_BASE_URL, "/v1/expireTime/", recode), bytes.NewReader(jsonStr)) 331 | if err != nil { 332 | fmt.Println("requestExpireTime request create NewRequest failed", err) 333 | return err 334 | } 335 | req.Header.Set("x-csrf-token", csrfToken) 336 | req.Header.Set("cookie", cookie) 337 | req.Header.Set("sec-fetch-dest", "empty") 338 | req.Header.Set("sec-fetch-mode", "cors") 339 | req.Header.Set("sec-fetch-site", "same-site") 340 | req.Header.Set("origin", "https://safeu.a2os.club") 341 | resp, err := client.Do(req) 342 | if err != nil { 343 | fmt.Println("requestExpireTime call failed", err) 344 | return err 345 | } 346 | 347 | respBody, _ := ioutil.ReadAll(resp.Body) 348 | if resp.StatusCode != http.StatusOK { 349 | // 解析错误码和错误信息 350 | var changeRequestErrorResponse ChangeRequestErrorResponse 351 | err = json.Unmarshal(respBody, &changeRequestErrorResponse) 352 | if err != nil { 353 | fmt.Println("requestExpireTime changeRequestErrorResponse json unmarshal failed", err) 354 | } 355 | fmt.Println("requestExpireTime response show some problem : ", changeRequestErrorResponse.Message) 356 | return fmt.Errorf("requestExpireTime reponse error %s", string(respBody)) 357 | 358 | } 359 | 360 | var changeRequestResponse ChangeRequestResponse 361 | err = json.Unmarshal(respBody, &changeRequestResponse) 362 | if err != nil { 363 | fmt.Println("requestExpireTime changeRequestResponse json unmarshal failed", err) 364 | return err 365 | } 366 | // 修改时间正确返回内容是文件过期时间的时间戳 2020-06-08T20:53:41+08:00 367 | t, err := time.Parse(time.RFC3339, changeRequestResponse.Message) 368 | if err != nil { 369 | fmt.Println("requestExpireTime response show some problem : ", respBody) 370 | return fmt.Errorf("requestExpireTime reponse error %s", respBody) 371 | } 372 | fmt.Println(fmt.Sprintf("Change file ExpireTime Success.File expire time will be :%s", t.Format("Mon Jan _2 15:04:05 2006"))) 373 | return nil 374 | } 375 | 376 | func requestDownCount(recode string, changeDownCount ChangeDownCount, csrfToken string, cookie string) (err error) { 377 | 378 | jsonStr, err := json.Marshal(changeDownCount) 379 | if err != nil { 380 | fmt.Println("requestDownCount json marshal error", err) 381 | } 382 | 383 | client := &http.Client{} 384 | req, err := http.NewRequest("POST", fmt.Sprintf("%s%s%s", util.SAFEU_BASE_URL, "/v1/downCount/", recode), bytes.NewReader(jsonStr)) 385 | if err != nil { 386 | fmt.Println("requestDownCount request create NewRequest failed", err) 387 | return err 388 | } 389 | req.Header.Set("x-csrf-token", csrfToken) 390 | req.Header.Set("cookie", cookie) 391 | req.Header.Set("sec-fetch-dest", "empty") 392 | req.Header.Set("sec-fetch-mode", "cors") 393 | req.Header.Set("sec-fetch-site", "same-site") 394 | req.Header.Set("origin", "https://safeu.a2os.club") 395 | resp, err := client.Do(req) 396 | if err != nil { 397 | fmt.Println("requestDownCount call failed", err) 398 | return err 399 | } 400 | 401 | respBody, _ := ioutil.ReadAll(resp.Body) 402 | if resp.StatusCode != http.StatusOK { 403 | // 解析错误码和错误信息 404 | var changeRequestErrorResponse ChangeRequestErrorResponse 405 | err = json.Unmarshal(respBody, &changeRequestErrorResponse) 406 | if err != nil { 407 | fmt.Println("requestDownCount changeRequestErrorResponse json unmarshal failed", err) 408 | } 409 | fmt.Println("requestDownCount response show some problem : ", changeRequestErrorResponse.Message) 410 | return fmt.Errorf("requestDownCount reponse error %s", string(respBody)) 411 | 412 | } 413 | 414 | var changeRequestResponse ChangeRequestResponse 415 | err = json.Unmarshal(respBody, &changeRequestResponse) 416 | if err != nil { 417 | fmt.Println("requestDownCount changeRequestResponse json unmarshal failed", err) 418 | return err 419 | } 420 | if changeRequestResponse.Message != "ok" { 421 | fmt.Println("requestDownCount response show some problem : ", respBody) 422 | return fmt.Errorf("requestDownCount reponse error %s", string(respBody)) 423 | } 424 | return nil 425 | } 426 | 427 | func getSha256(text string) string { 428 | bv := []byte(text) 429 | hasher := sha256.New() 430 | hasher.Write(bv) 431 | sha := hex.EncodeToString(hasher.Sum(nil)) 432 | return sha 433 | } 434 | 435 | // 入口函数 436 | func Start(fileFullPaths []string, userRecode string, userPassword string, userDownCount int, userExpireTime int) { 437 | var u Instance 438 | u.Password = userPassword 439 | u.DownCount = userDownCount 440 | u.ExpireTime = userExpireTime 441 | 442 | err := u.getCSRF() 443 | if err != nil { 444 | fmt.Println("getCSRF error", err) 445 | os.Exit(0) 446 | } 447 | err = u.getUploadPolicy() 448 | if err != nil { 449 | fmt.Println("getUploadPolicy error", err) 450 | os.Exit(0) 451 | } 452 | err = u.ready(fileFullPaths) 453 | if err != nil { 454 | fmt.Println("ready error", err) 455 | os.Exit(0) 456 | } 457 | errors := u.run() 458 | if len(errors) > 0 { 459 | for _, err := range errors { 460 | fmt.Println("file upload error", err) 461 | } 462 | os.Exit(0) 463 | } 464 | err = u.finish() 465 | if err != nil { 466 | fmt.Println("finish error", err) 467 | os.Exit(0) 468 | } 469 | errors = u.modify(userRecode) 470 | if len(errors) > 0 { 471 | for _, err := range errors { 472 | fmt.Println("modify error", err) 473 | } 474 | } 475 | fmt.Println("Upload Finish") 476 | fmt.Println("") 477 | 478 | fmt.Println("Recode :", u.Recode) 479 | fmt.Println("Owner :", u.Owner) 480 | fmt.Println("Password :", u.Password) 481 | fmt.Println("DownCount :", u.DownCount) 482 | fmt.Println("ExpireTime :", u.ExpireTime) 483 | } 484 | --------------------------------------------------------------------------------