client.go 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  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. package client
  17. import (
  18. "bytes"
  19. "encoding/json"
  20. "fmt"
  21. "io"
  22. "io/ioutil"
  23. "mime"
  24. "net/http"
  25. "os"
  26. "path/filepath"
  27. "strings"
  28. "github.com/ethereum/go-ethereum/log"
  29. )
  30. var (
  31. DefaultGateway = "http://localhost:8500"
  32. DefaultClient = NewClient(DefaultGateway)
  33. )
  34. // Manifest represents a swarm manifest.
  35. type Manifest struct {
  36. Entries []ManifestEntry `json:"entries,omitempty"`
  37. }
  38. // ManifestEntry represents an entry in a swarm manifest.
  39. type ManifestEntry struct {
  40. Hash string `json:"hash,omitempty"`
  41. ContentType string `json:"contentType,omitempty"`
  42. Path string `json:"path,omitempty"`
  43. }
  44. func NewClient(gateway string) *Client {
  45. return &Client{
  46. Gateway: gateway,
  47. }
  48. }
  49. // Client wraps interaction with a swarm HTTP gateway.
  50. type Client struct {
  51. Gateway string
  52. }
  53. func (c *Client) UploadDirectory(dir string, defaultPath string) (string, error) {
  54. mhash, err := c.postRaw("application/json", 2, ioutil.NopCloser(bytes.NewReader([]byte("{}"))))
  55. if err != nil {
  56. return "", fmt.Errorf("failed to upload empty manifest")
  57. }
  58. if len(defaultPath) > 0 {
  59. fi, err := os.Stat(defaultPath)
  60. if err != nil {
  61. return "", err
  62. }
  63. mhash, err = c.uploadToManifest(mhash, "", defaultPath, fi)
  64. if err != nil {
  65. return "", err
  66. }
  67. }
  68. prefix := filepath.ToSlash(filepath.Clean(dir)) + "/"
  69. err = filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
  70. if err != nil || fi.IsDir() {
  71. return err
  72. }
  73. if !strings.HasPrefix(path, dir) {
  74. return fmt.Errorf("path %s outside directory %s", path, dir)
  75. }
  76. uripath := strings.TrimPrefix(filepath.ToSlash(filepath.Clean(path)), prefix)
  77. mhash, err = c.uploadToManifest(mhash, uripath, path, fi)
  78. return err
  79. })
  80. return mhash, err
  81. }
  82. func (c *Client) UploadFile(file string, fi os.FileInfo) (ManifestEntry, error) {
  83. hash, err := c.uploadFileContent(file, fi)
  84. m := ManifestEntry{
  85. Hash: hash,
  86. ContentType: mime.TypeByExtension(filepath.Ext(fi.Name())),
  87. }
  88. return m, err
  89. }
  90. func (c *Client) uploadFileContent(file string, fi os.FileInfo) (string, error) {
  91. fd, err := os.Open(file)
  92. if err != nil {
  93. return "", err
  94. }
  95. defer fd.Close()
  96. log.Info("Uploading swarm content", "file", file, "bytes", fi.Size())
  97. return c.postRaw("application/octet-stream", fi.Size(), fd)
  98. }
  99. func (c *Client) UploadManifest(m Manifest) (string, error) {
  100. jsm, err := json.Marshal(m)
  101. if err != nil {
  102. panic(err)
  103. }
  104. log.Info("Uploading swarm manifest")
  105. return c.postRaw("application/json", int64(len(jsm)), ioutil.NopCloser(bytes.NewReader(jsm)))
  106. }
  107. func (c *Client) uploadToManifest(mhash string, path string, fpath string, fi os.FileInfo) (string, error) {
  108. fd, err := os.Open(fpath)
  109. if err != nil {
  110. return "", err
  111. }
  112. defer fd.Close()
  113. log.Info("Uploading swarm content and path", "file", fpath, "bytes", fi.Size(), "path", path)
  114. req, err := http.NewRequest("PUT", c.Gateway+"/bzz:/"+mhash+"/"+path, fd)
  115. if err != nil {
  116. return "", err
  117. }
  118. req.Header.Set("content-type", mime.TypeByExtension(filepath.Ext(fi.Name())))
  119. req.ContentLength = fi.Size()
  120. resp, err := http.DefaultClient.Do(req)
  121. if err != nil {
  122. return "", err
  123. }
  124. defer resp.Body.Close()
  125. if resp.StatusCode >= 400 {
  126. return "", fmt.Errorf("bad status: %s", resp.Status)
  127. }
  128. content, err := ioutil.ReadAll(resp.Body)
  129. return string(content), err
  130. }
  131. func (c *Client) postRaw(mimetype string, size int64, body io.ReadCloser) (string, error) {
  132. req, err := http.NewRequest("POST", c.Gateway+"/bzzr:/", body)
  133. if err != nil {
  134. return "", err
  135. }
  136. req.Header.Set("content-type", mimetype)
  137. req.ContentLength = size
  138. resp, err := http.DefaultClient.Do(req)
  139. if err != nil {
  140. return "", err
  141. }
  142. defer resp.Body.Close()
  143. if resp.StatusCode >= 400 {
  144. return "", fmt.Errorf("bad status: %s", resp.Status)
  145. }
  146. content, err := ioutil.ReadAll(resp.Body)
  147. return string(content), err
  148. }
  149. func (c *Client) DownloadManifest(mhash string) (Manifest, error) {
  150. mroot := Manifest{}
  151. req, err := http.NewRequest("GET", c.Gateway+"/bzzr:/"+mhash, nil)
  152. if err != nil {
  153. return mroot, err
  154. }
  155. resp, err := http.DefaultClient.Do(req)
  156. if err != nil {
  157. return mroot, err
  158. }
  159. defer resp.Body.Close()
  160. if resp.StatusCode >= 400 {
  161. return mroot, fmt.Errorf("bad status: %s", resp.Status)
  162. }
  163. content, err := ioutil.ReadAll(resp.Body)
  164. err = json.Unmarshal(content, &mroot)
  165. if err != nil {
  166. return mroot, fmt.Errorf("Manifest %v is malformed: %v", mhash, err)
  167. }
  168. return mroot, err
  169. }
  170. // ManifestFileList downloads the manifest with the given hash and generates a
  171. // list of files and directory prefixes which have the specified prefix.
  172. //
  173. // For example, if the manifest represents the following directory structure:
  174. //
  175. // file1.txt
  176. // file2.txt
  177. // dir1/file3.txt
  178. // dir1/dir2/file4.txt
  179. //
  180. // Then:
  181. //
  182. // - a prefix of "" would return [dir1/, file1.txt, file2.txt]
  183. // - a prefix of "file" would return [file1.txt, file2.txt]
  184. // - a prefix of "dir1/" would return [dir1/dir2/, dir1/file3.txt]
  185. func (c *Client) ManifestFileList(hash, prefix string) (entries []ManifestEntry, err error) {
  186. manifest, err := c.DownloadManifest(hash)
  187. if err != nil {
  188. return nil, err
  189. }
  190. // handleFile handles a manifest entry which is a direct reference to a
  191. // file (i.e. it is not a swarm manifest)
  192. handleFile := func(entry ManifestEntry) {
  193. // ignore the file if it doesn't have the specified prefix
  194. if !strings.HasPrefix(entry.Path, prefix) {
  195. return
  196. }
  197. // if the path after the prefix contains a directory separator,
  198. // add a directory prefix to the entries, otherwise add the
  199. // file
  200. suffix := strings.TrimPrefix(entry.Path, prefix)
  201. if sepIndex := strings.Index(suffix, "/"); sepIndex > -1 {
  202. entries = append(entries, ManifestEntry{
  203. Path: prefix + suffix[:sepIndex+1],
  204. ContentType: "DIR",
  205. })
  206. } else {
  207. if entry.Path == "" {
  208. entry.Path = "/"
  209. }
  210. entries = append(entries, entry)
  211. }
  212. }
  213. // handleManifest handles a manifest entry which is a reference to
  214. // another swarm manifest.
  215. handleManifest := func(entry ManifestEntry) error {
  216. // if the manifest's path is a prefix of the specified prefix
  217. // then just recurse into the manifest by stripping its path
  218. // from the prefix
  219. if strings.HasPrefix(prefix, entry.Path) {
  220. subPrefix := strings.TrimPrefix(prefix, entry.Path)
  221. subEntries, err := c.ManifestFileList(entry.Hash, subPrefix)
  222. if err != nil {
  223. return err
  224. }
  225. // prefix the manifest's path to the sub entries and
  226. // add them to the returned entries
  227. for i, subEntry := range subEntries {
  228. subEntry.Path = entry.Path + subEntry.Path
  229. subEntries[i] = subEntry
  230. }
  231. entries = append(entries, subEntries...)
  232. return nil
  233. }
  234. // if the manifest's path has the specified prefix, then if the
  235. // path after the prefix contains a directory separator, add a
  236. // directory prefix to the entries, otherwise recurse into the
  237. // manifest
  238. if strings.HasPrefix(entry.Path, prefix) {
  239. suffix := strings.TrimPrefix(entry.Path, prefix)
  240. sepIndex := strings.Index(suffix, "/")
  241. if sepIndex > -1 {
  242. entries = append(entries, ManifestEntry{
  243. Path: prefix + suffix[:sepIndex+1],
  244. ContentType: "DIR",
  245. })
  246. return nil
  247. }
  248. subEntries, err := c.ManifestFileList(entry.Hash, "")
  249. if err != nil {
  250. return err
  251. }
  252. // prefix the manifest's path to the sub entries and
  253. // add them to the returned entries
  254. for i, subEntry := range subEntries {
  255. subEntry.Path = entry.Path + subEntry.Path
  256. subEntries[i] = subEntry
  257. }
  258. entries = append(entries, subEntries...)
  259. return nil
  260. }
  261. return nil
  262. }
  263. for _, entry := range manifest.Entries {
  264. if entry.ContentType == "application/bzz-manifest+json" {
  265. if err := handleManifest(entry); err != nil {
  266. return nil, err
  267. }
  268. } else {
  269. handleFile(entry)
  270. }
  271. }
  272. return
  273. }