github.com/devfans/go-ethereum@v1.5.10-0.20170326212234-7419d0c38291/cmd/swarm/upload.go (about) 1 // Copyright 2016 The go-ethereum Authors 2 // This file is part of go-ethereum. 3 // 4 // go-ethereum is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // go-ethereum is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>. 16 17 // Command bzzup uploads files to the swarm HTTP API. 18 package main 19 20 import ( 21 "bytes" 22 "encoding/json" 23 "fmt" 24 "io" 25 "io/ioutil" 26 "mime" 27 "net/http" 28 "os" 29 "os/user" 30 "path" 31 "path/filepath" 32 "strings" 33 34 "github.com/ethereum/go-ethereum/cmd/utils" 35 "github.com/ethereum/go-ethereum/log" 36 "gopkg.in/urfave/cli.v1" 37 ) 38 39 func upload(ctx *cli.Context) { 40 args := ctx.Args() 41 var ( 42 bzzapi = strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/") 43 recursive = ctx.GlobalBool(SwarmRecursiveUploadFlag.Name) 44 wantManifest = ctx.GlobalBoolT(SwarmWantManifestFlag.Name) 45 defaultPath = ctx.GlobalString(SwarmUploadDefaultPath.Name) 46 ) 47 if len(args) != 1 { 48 utils.Fatalf("Need filename as the first and only argument") 49 } 50 51 var ( 52 file = args[0] 53 client = &client{api: bzzapi} 54 ) 55 fi, err := os.Stat(expandPath(file)) 56 if err != nil { 57 utils.Fatalf("Failed to stat file: %v", err) 58 } 59 if fi.IsDir() { 60 if !recursive { 61 utils.Fatalf("Argument is a directory and recursive upload is disabled") 62 } 63 if !wantManifest { 64 utils.Fatalf("Manifest is required for directory uploads") 65 } 66 mhash, err := client.uploadDirectory(file, defaultPath) 67 if err != nil { 68 utils.Fatalf("Failed to upload directory: %v", err) 69 } 70 fmt.Println(mhash) 71 return 72 } 73 entry, err := client.uploadFile(file, fi) 74 if err != nil { 75 utils.Fatalf("Upload failed: %v", err) 76 } 77 mroot := manifest{[]manifestEntry{entry}} 78 if !wantManifest { 79 // Print the manifest. This is the only output to stdout. 80 mrootJSON, _ := json.MarshalIndent(mroot, "", " ") 81 fmt.Println(string(mrootJSON)) 82 return 83 } 84 hash, err := client.uploadManifest(mroot) 85 if err != nil { 86 utils.Fatalf("Manifest upload failed: %v", err) 87 } 88 fmt.Println(hash) 89 } 90 91 // Expands a file path 92 // 1. replace tilde with users home dir 93 // 2. expands embedded environment variables 94 // 3. cleans the path, e.g. /a/b/../c -> /a/c 95 // Note, it has limitations, e.g. ~someuser/tmp will not be expanded 96 func expandPath(p string) string { 97 if strings.HasPrefix(p, "~/") || strings.HasPrefix(p, "~\\") { 98 if home := homeDir(); home != "" { 99 p = home + p[1:] 100 } 101 } 102 return path.Clean(os.ExpandEnv(p)) 103 } 104 105 func homeDir() string { 106 if home := os.Getenv("HOME"); home != "" { 107 return home 108 } 109 if usr, err := user.Current(); err == nil { 110 return usr.HomeDir 111 } 112 return "" 113 } 114 115 // client wraps interaction with the swarm HTTP gateway. 116 type client struct { 117 api string 118 } 119 120 // manifest is the JSON representation of a swarm manifest. 121 type manifestEntry struct { 122 Hash string `json:"hash,omitempty"` 123 ContentType string `json:"contentType,omitempty"` 124 Path string `json:"path,omitempty"` 125 } 126 127 // manifest is the JSON representation of a swarm manifest. 128 type manifest struct { 129 Entries []manifestEntry `json:"entries,omitempty"` 130 } 131 132 func (c *client) uploadDirectory(dir string, defaultPath string) (string, error) { 133 mhash, err := c.postRaw("application/json", 2, ioutil.NopCloser(bytes.NewReader([]byte("{}")))) 134 if err != nil { 135 return "", fmt.Errorf("failed to upload empty manifest") 136 } 137 if len(defaultPath) > 0 { 138 fi, err := os.Stat(defaultPath) 139 if err != nil { 140 return "", err 141 } 142 mhash, err = c.uploadToManifest(mhash, "", defaultPath, fi) 143 if err != nil { 144 return "", err 145 } 146 } 147 prefix := filepath.ToSlash(filepath.Clean(dir)) + "/" 148 err = filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error { 149 if err != nil || fi.IsDir() { 150 return err 151 } 152 if !strings.HasPrefix(path, dir) { 153 return fmt.Errorf("path %s outside directory %s", path, dir) 154 } 155 uripath := strings.TrimPrefix(filepath.ToSlash(filepath.Clean(path)), prefix) 156 mhash, err = c.uploadToManifest(mhash, uripath, path, fi) 157 return err 158 }) 159 return mhash, err 160 } 161 162 func (c *client) uploadFile(file string, fi os.FileInfo) (manifestEntry, error) { 163 hash, err := c.uploadFileContent(file, fi) 164 m := manifestEntry{ 165 Hash: hash, 166 ContentType: mime.TypeByExtension(filepath.Ext(fi.Name())), 167 } 168 return m, err 169 } 170 171 func (c *client) uploadFileContent(file string, fi os.FileInfo) (string, error) { 172 fd, err := os.Open(file) 173 if err != nil { 174 return "", err 175 } 176 defer fd.Close() 177 log.Info("Uploading swarm content", "file", file, "bytes", fi.Size()) 178 return c.postRaw("application/octet-stream", fi.Size(), fd) 179 } 180 181 func (c *client) uploadManifest(m manifest) (string, error) { 182 jsm, err := json.Marshal(m) 183 if err != nil { 184 panic(err) 185 } 186 log.Info("Uploading swarm manifest") 187 return c.postRaw("application/json", int64(len(jsm)), ioutil.NopCloser(bytes.NewReader(jsm))) 188 } 189 190 func (c *client) uploadToManifest(mhash string, path string, fpath string, fi os.FileInfo) (string, error) { 191 fd, err := os.Open(fpath) 192 if err != nil { 193 return "", err 194 } 195 defer fd.Close() 196 log.Info("Uploading swarm content and path", "file", fpath, "bytes", fi.Size(), "path", path) 197 req, err := http.NewRequest("PUT", c.api+"/bzz:/"+mhash+"/"+path, fd) 198 if err != nil { 199 return "", err 200 } 201 req.Header.Set("content-type", mime.TypeByExtension(filepath.Ext(fi.Name()))) 202 req.ContentLength = fi.Size() 203 resp, err := http.DefaultClient.Do(req) 204 if err != nil { 205 return "", err 206 } 207 defer resp.Body.Close() 208 if resp.StatusCode >= 400 { 209 return "", fmt.Errorf("bad status: %s", resp.Status) 210 } 211 content, err := ioutil.ReadAll(resp.Body) 212 return string(content), err 213 } 214 215 func (c *client) postRaw(mimetype string, size int64, body io.ReadCloser) (string, error) { 216 req, err := http.NewRequest("POST", c.api+"/bzzr:/", body) 217 if err != nil { 218 return "", err 219 } 220 req.Header.Set("content-type", mimetype) 221 req.ContentLength = size 222 resp, err := http.DefaultClient.Do(req) 223 if err != nil { 224 return "", err 225 } 226 defer resp.Body.Close() 227 if resp.StatusCode >= 400 { 228 return "", fmt.Errorf("bad status: %s", resp.Status) 229 } 230 content, err := ioutil.ReadAll(resp.Body) 231 return string(content), err 232 } 233 234 func (c *client) downloadManifest(mhash string) (manifest, error) { 235 236 mroot := manifest{} 237 req, err := http.NewRequest("GET", c.api+"/bzzr:/"+mhash, nil) 238 if err != nil { 239 return mroot, err 240 } 241 resp, err := http.DefaultClient.Do(req) 242 if err != nil { 243 return mroot, err 244 } 245 defer resp.Body.Close() 246 247 if resp.StatusCode >= 400 { 248 return mroot, fmt.Errorf("bad status: %s", resp.Status) 249 250 } 251 content, err := ioutil.ReadAll(resp.Body) 252 253 err = json.Unmarshal(content, &mroot) 254 if err != nil { 255 return mroot, fmt.Errorf("Manifest %v is malformed: %v", mhash, err) 256 } 257 return mroot, err 258 }