server.go 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937
  1. // Copyright 2016 The go-ethereum Authors
  2. // This file is part of the go-ethereum library.
  3. //
  4. // The go-ethereum library is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Lesser 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. // The go-ethereum library 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 Lesser General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Lesser General Public License
  15. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
  16. /*
  17. A simple http server interface to Swarm
  18. */
  19. package http
  20. import (
  21. "bufio"
  22. "bytes"
  23. "encoding/json"
  24. "fmt"
  25. "io"
  26. "io/ioutil"
  27. "math"
  28. "mime"
  29. "mime/multipart"
  30. "net/http"
  31. "os"
  32. "path"
  33. "strconv"
  34. "strings"
  35. "time"
  36. "github.com/ethereum/go-ethereum/common"
  37. "github.com/ethereum/go-ethereum/metrics"
  38. "github.com/ethereum/go-ethereum/swarm/api"
  39. "github.com/ethereum/go-ethereum/swarm/chunk"
  40. "github.com/ethereum/go-ethereum/swarm/log"
  41. "github.com/ethereum/go-ethereum/swarm/sctx"
  42. "github.com/ethereum/go-ethereum/swarm/storage"
  43. "github.com/ethereum/go-ethereum/swarm/storage/feed"
  44. "github.com/rs/cors"
  45. )
  46. var (
  47. postRawCount = metrics.NewRegisteredCounter("api.http.post.raw.count", nil)
  48. postRawFail = metrics.NewRegisteredCounter("api.http.post.raw.fail", nil)
  49. postFilesCount = metrics.NewRegisteredCounter("api.http.post.files.count", nil)
  50. postFilesFail = metrics.NewRegisteredCounter("api.http.post.files.fail", nil)
  51. deleteCount = metrics.NewRegisteredCounter("api.http.delete.count", nil)
  52. deleteFail = metrics.NewRegisteredCounter("api.http.delete.fail", nil)
  53. getCount = metrics.NewRegisteredCounter("api.http.get.count", nil)
  54. getFail = metrics.NewRegisteredCounter("api.http.get.fail", nil)
  55. getFileCount = metrics.NewRegisteredCounter("api.http.get.file.count", nil)
  56. getFileNotFound = metrics.NewRegisteredCounter("api.http.get.file.notfound", nil)
  57. getFileFail = metrics.NewRegisteredCounter("api.http.get.file.fail", nil)
  58. getListCount = metrics.NewRegisteredCounter("api.http.get.list.count", nil)
  59. getListFail = metrics.NewRegisteredCounter("api.http.get.list.fail", nil)
  60. )
  61. const SwarmTagHeaderName = "x-swarm-tag"
  62. type methodHandler map[string]http.Handler
  63. func (m methodHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
  64. v, ok := m[r.Method]
  65. if ok {
  66. v.ServeHTTP(rw, r)
  67. return
  68. }
  69. rw.WriteHeader(http.StatusMethodNotAllowed)
  70. }
  71. func NewServer(api *api.API, corsString string) *Server {
  72. var allowedOrigins []string
  73. for _, domain := range strings.Split(corsString, ",") {
  74. allowedOrigins = append(allowedOrigins, strings.TrimSpace(domain))
  75. }
  76. c := cors.New(cors.Options{
  77. AllowedOrigins: allowedOrigins,
  78. AllowedMethods: []string{http.MethodPost, http.MethodGet, http.MethodDelete, http.MethodPatch, http.MethodPut},
  79. MaxAge: 600,
  80. AllowedHeaders: []string{"*"},
  81. })
  82. server := &Server{api: api}
  83. defaultMiddlewares := []Adapter{
  84. RecoverPanic,
  85. SetRequestID,
  86. SetRequestHost,
  87. InitLoggingResponseWriter,
  88. ParseURI,
  89. InstrumentOpenTracing,
  90. }
  91. tagAdapter := Adapter(func(h http.Handler) http.Handler {
  92. return InitUploadTag(h, api.Tags)
  93. })
  94. defaultPostMiddlewares := append(defaultMiddlewares, tagAdapter)
  95. mux := http.NewServeMux()
  96. mux.Handle("/bzz:/", methodHandler{
  97. "GET": Adapt(
  98. http.HandlerFunc(server.HandleBzzGet),
  99. defaultMiddlewares...,
  100. ),
  101. "POST": Adapt(
  102. http.HandlerFunc(server.HandlePostFiles),
  103. defaultPostMiddlewares...,
  104. ),
  105. "DELETE": Adapt(
  106. http.HandlerFunc(server.HandleDelete),
  107. defaultMiddlewares...,
  108. ),
  109. })
  110. mux.Handle("/bzz-raw:/", methodHandler{
  111. "GET": Adapt(
  112. http.HandlerFunc(server.HandleGet),
  113. defaultMiddlewares...,
  114. ),
  115. "POST": Adapt(
  116. http.HandlerFunc(server.HandlePostRaw),
  117. defaultPostMiddlewares...,
  118. ),
  119. })
  120. mux.Handle("/bzz-immutable:/", methodHandler{
  121. "GET": Adapt(
  122. http.HandlerFunc(server.HandleBzzGet),
  123. defaultMiddlewares...,
  124. ),
  125. })
  126. mux.Handle("/bzz-hash:/", methodHandler{
  127. "GET": Adapt(
  128. http.HandlerFunc(server.HandleGet),
  129. defaultMiddlewares...,
  130. ),
  131. })
  132. mux.Handle("/bzz-list:/", methodHandler{
  133. "GET": Adapt(
  134. http.HandlerFunc(server.HandleGetList),
  135. defaultMiddlewares...,
  136. ),
  137. })
  138. mux.Handle("/bzz-feed:/", methodHandler{
  139. "GET": Adapt(
  140. http.HandlerFunc(server.HandleGetFeed),
  141. defaultMiddlewares...,
  142. ),
  143. "POST": Adapt(
  144. http.HandlerFunc(server.HandlePostFeed),
  145. defaultMiddlewares...,
  146. ),
  147. })
  148. mux.Handle("/", methodHandler{
  149. "GET": Adapt(
  150. http.HandlerFunc(server.HandleRootPaths),
  151. SetRequestID,
  152. InitLoggingResponseWriter,
  153. ),
  154. })
  155. server.Handler = c.Handler(mux)
  156. return server
  157. }
  158. func (s *Server) ListenAndServe(addr string) error {
  159. s.listenAddr = addr
  160. return http.ListenAndServe(addr, s)
  161. }
  162. // browser API for registering bzz url scheme handlers:
  163. // https://developer.mozilla.org/en/docs/Web-based_protocol_handlers
  164. // electron (chromium) api for registering bzz url scheme handlers:
  165. // https://github.com/atom/electron/blob/master/docs/api/protocol.md
  166. type Server struct {
  167. http.Handler
  168. api *api.API
  169. listenAddr string
  170. }
  171. func (s *Server) HandleBzzGet(w http.ResponseWriter, r *http.Request) {
  172. log.Debug("handleBzzGet", "ruid", GetRUID(r.Context()), "uri", r.RequestURI)
  173. if r.Header.Get("Accept") == "application/x-tar" {
  174. uri := GetURI(r.Context())
  175. _, credentials, _ := r.BasicAuth()
  176. reader, err := s.api.GetDirectoryTar(r.Context(), s.api.Decryptor(r.Context(), credentials), uri)
  177. if err != nil {
  178. if isDecryptError(err) {
  179. w.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%q", uri.Address().String()))
  180. respondError(w, r, err.Error(), http.StatusUnauthorized)
  181. return
  182. }
  183. respondError(w, r, fmt.Sprintf("Had an error building the tarball: %v", err), http.StatusInternalServerError)
  184. return
  185. }
  186. defer reader.Close()
  187. w.Header().Set("Content-Type", "application/x-tar")
  188. fileName := uri.Addr
  189. if found := path.Base(uri.Path); found != "" && found != "." && found != "/" {
  190. fileName = found
  191. }
  192. w.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=\"%s.tar\"", fileName))
  193. w.WriteHeader(http.StatusOK)
  194. io.Copy(w, reader)
  195. return
  196. }
  197. s.HandleGetFile(w, r)
  198. }
  199. func (s *Server) HandleRootPaths(w http.ResponseWriter, r *http.Request) {
  200. switch r.RequestURI {
  201. case "/":
  202. respondTemplate(w, r, "landing-page", "Swarm: Please request a valid ENS or swarm hash with the appropriate bzz scheme", 200)
  203. return
  204. case "/robots.txt":
  205. w.Header().Set("Last-Modified", time.Now().Format(http.TimeFormat))
  206. fmt.Fprintf(w, "User-agent: *\nDisallow: /")
  207. case "/favicon.ico":
  208. w.WriteHeader(http.StatusOK)
  209. w.Write(faviconBytes)
  210. default:
  211. respondError(w, r, "Not Found", http.StatusNotFound)
  212. }
  213. }
  214. // HandlePostRaw handles a POST request to a raw bzz-raw:/ URI, stores the request
  215. // body in swarm and returns the resulting storage address as a text/plain response
  216. func (s *Server) HandlePostRaw(w http.ResponseWriter, r *http.Request) {
  217. ruid := GetRUID(r.Context())
  218. log.Debug("handle.post.raw", "ruid", ruid)
  219. tagUid := sctx.GetTag(r.Context())
  220. tag, err := s.api.Tags.Get(tagUid)
  221. if err != nil {
  222. log.Error("handle post raw got an error retrieving tag for DoneSplit", "tagUid", tagUid, "err", err)
  223. }
  224. postRawCount.Inc(1)
  225. toEncrypt := false
  226. uri := GetURI(r.Context())
  227. if uri.Addr == "encrypt" {
  228. toEncrypt = true
  229. }
  230. if uri.Path != "" {
  231. postRawFail.Inc(1)
  232. respondError(w, r, "raw POST request cannot contain a path", http.StatusBadRequest)
  233. return
  234. }
  235. if uri.Addr != "" && uri.Addr != "encrypt" {
  236. postRawFail.Inc(1)
  237. respondError(w, r, "raw POST request addr can only be empty or \"encrypt\"", http.StatusBadRequest)
  238. return
  239. }
  240. if r.Header.Get("Content-Length") == "" {
  241. postRawFail.Inc(1)
  242. respondError(w, r, "missing Content-Length header in request", http.StatusBadRequest)
  243. return
  244. }
  245. addr, wait, err := s.api.Store(r.Context(), r.Body, r.ContentLength, toEncrypt)
  246. if err != nil {
  247. postRawFail.Inc(1)
  248. respondError(w, r, err.Error(), http.StatusInternalServerError)
  249. return
  250. }
  251. wait(r.Context())
  252. tag.DoneSplit(addr)
  253. log.Debug("stored content", "ruid", ruid, "key", addr)
  254. w.Header().Set("Content-Type", "text/plain")
  255. w.WriteHeader(http.StatusOK)
  256. fmt.Fprint(w, addr)
  257. }
  258. // HandlePostFiles handles a POST request to
  259. // bzz:/<hash>/<path> which contains either a single file or multiple files
  260. // (either a tar archive or multipart form), adds those files either to an
  261. // existing manifest or to a new manifest under <path> and returns the
  262. // resulting manifest hash as a text/plain response
  263. func (s *Server) HandlePostFiles(w http.ResponseWriter, r *http.Request) {
  264. ruid := GetRUID(r.Context())
  265. log.Debug("handle.post.files", "ruid", ruid)
  266. postFilesCount.Inc(1)
  267. contentType, params, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
  268. if err != nil {
  269. postFilesFail.Inc(1)
  270. respondError(w, r, err.Error(), http.StatusBadRequest)
  271. return
  272. }
  273. toEncrypt := false
  274. uri := GetURI(r.Context())
  275. if uri.Addr == "encrypt" {
  276. toEncrypt = true
  277. }
  278. var addr storage.Address
  279. if uri.Addr != "" && uri.Addr != "encrypt" {
  280. addr, err = s.api.Resolve(r.Context(), uri.Addr)
  281. if err != nil {
  282. postFilesFail.Inc(1)
  283. respondError(w, r, fmt.Sprintf("cannot resolve %s: %s", uri.Addr, err), http.StatusInternalServerError)
  284. return
  285. }
  286. log.Debug("resolved key", "ruid", ruid, "key", addr)
  287. } else {
  288. addr, err = s.api.NewManifest(r.Context(), toEncrypt)
  289. if err != nil {
  290. postFilesFail.Inc(1)
  291. respondError(w, r, err.Error(), http.StatusInternalServerError)
  292. return
  293. }
  294. log.Debug("new manifest", "ruid", ruid, "key", addr)
  295. }
  296. newAddr, err := s.api.UpdateManifest(r.Context(), addr, func(mw *api.ManifestWriter) error {
  297. switch contentType {
  298. case "application/x-tar":
  299. _, err := s.handleTarUpload(r, mw)
  300. if err != nil {
  301. respondError(w, r, fmt.Sprintf("error uploading tarball: %v", err), http.StatusInternalServerError)
  302. return err
  303. }
  304. return nil
  305. case "multipart/form-data":
  306. return s.handleMultipartUpload(r, params["boundary"], mw)
  307. default:
  308. return s.handleDirectUpload(r, mw)
  309. }
  310. })
  311. if err != nil {
  312. postFilesFail.Inc(1)
  313. respondError(w, r, fmt.Sprintf("cannot create manifest: %s", err), http.StatusInternalServerError)
  314. return
  315. }
  316. tagUid := sctx.GetTag(r.Context())
  317. tag, err := s.api.Tags.Get(tagUid)
  318. if err != nil {
  319. log.Error("got an error retrieving tag for DoneSplit", "tagUid", tagUid, "err", err)
  320. }
  321. log.Debug("done splitting, setting tag total", "SPLIT", tag.Get(chunk.StateSplit), "TOTAL", tag.Total())
  322. tag.DoneSplit(newAddr)
  323. log.Debug("stored content", "ruid", ruid, "key", newAddr)
  324. w.Header().Set("Content-Type", "text/plain")
  325. w.WriteHeader(http.StatusOK)
  326. fmt.Fprint(w, newAddr)
  327. }
  328. func (s *Server) handleTarUpload(r *http.Request, mw *api.ManifestWriter) (storage.Address, error) {
  329. log.Debug("handle.tar.upload", "ruid", GetRUID(r.Context()), "tag", sctx.GetTag(r.Context()))
  330. defaultPath := r.URL.Query().Get("defaultpath")
  331. key, err := s.api.UploadTar(r.Context(), r.Body, GetURI(r.Context()).Path, defaultPath, mw)
  332. if err != nil {
  333. return nil, err
  334. }
  335. return key, nil
  336. }
  337. func (s *Server) handleMultipartUpload(r *http.Request, boundary string, mw *api.ManifestWriter) error {
  338. ruid := GetRUID(r.Context())
  339. log.Debug("handle.multipart.upload", "ruid", ruid)
  340. mr := multipart.NewReader(r.Body, boundary)
  341. for {
  342. part, err := mr.NextPart()
  343. if err == io.EOF {
  344. return nil
  345. } else if err != nil {
  346. return fmt.Errorf("error reading multipart form: %s", err)
  347. }
  348. var size int64
  349. var reader io.Reader
  350. if contentLength := part.Header.Get("Content-Length"); contentLength != "" {
  351. size, err = strconv.ParseInt(contentLength, 10, 64)
  352. if err != nil {
  353. return fmt.Errorf("error parsing multipart content length: %s", err)
  354. }
  355. reader = part
  356. } else {
  357. // copy the part to a tmp file to get its size
  358. tmp, err := ioutil.TempFile("", "swarm-multipart")
  359. if err != nil {
  360. return err
  361. }
  362. defer os.Remove(tmp.Name())
  363. defer tmp.Close()
  364. size, err = io.Copy(tmp, part)
  365. if err != nil {
  366. return fmt.Errorf("error copying multipart content: %s", err)
  367. }
  368. if _, err := tmp.Seek(0, io.SeekStart); err != nil {
  369. return fmt.Errorf("error copying multipart content: %s", err)
  370. }
  371. reader = tmp
  372. }
  373. // add the entry under the path from the request
  374. name := part.FileName()
  375. if name == "" {
  376. name = part.FormName()
  377. }
  378. uri := GetURI(r.Context())
  379. path := path.Join(uri.Path, name)
  380. entry := &api.ManifestEntry{
  381. Path: path,
  382. ContentType: part.Header.Get("Content-Type"),
  383. Size: size,
  384. }
  385. log.Debug("adding path to new manifest", "ruid", ruid, "bytes", entry.Size, "path", entry.Path)
  386. contentKey, err := mw.AddEntry(r.Context(), reader, entry)
  387. if err != nil {
  388. return fmt.Errorf("error adding manifest entry from multipart form: %s", err)
  389. }
  390. log.Debug("stored content", "ruid", ruid, "key", contentKey)
  391. }
  392. }
  393. func (s *Server) handleDirectUpload(r *http.Request, mw *api.ManifestWriter) error {
  394. ruid := GetRUID(r.Context())
  395. log.Debug("handle.direct.upload", "ruid", ruid)
  396. key, err := mw.AddEntry(r.Context(), r.Body, &api.ManifestEntry{
  397. Path: GetURI(r.Context()).Path,
  398. ContentType: r.Header.Get("Content-Type"),
  399. Mode: 0644,
  400. Size: r.ContentLength,
  401. })
  402. if err != nil {
  403. return err
  404. }
  405. log.Debug("stored content", "ruid", ruid, "key", key)
  406. return nil
  407. }
  408. // HandleDelete handles a DELETE request to bzz:/<manifest>/<path>, removes
  409. // <path> from <manifest> and returns the resulting manifest hash as a
  410. // text/plain response
  411. func (s *Server) HandleDelete(w http.ResponseWriter, r *http.Request) {
  412. ruid := GetRUID(r.Context())
  413. uri := GetURI(r.Context())
  414. log.Debug("handle.delete", "ruid", ruid)
  415. deleteCount.Inc(1)
  416. newKey, err := s.api.Delete(r.Context(), uri.Addr, uri.Path)
  417. if err != nil {
  418. deleteFail.Inc(1)
  419. respondError(w, r, fmt.Sprintf("could not delete from manifest: %v", err), http.StatusInternalServerError)
  420. return
  421. }
  422. w.Header().Set("Content-Type", "text/plain")
  423. w.WriteHeader(http.StatusOK)
  424. fmt.Fprint(w, newKey)
  425. }
  426. // Handles feed manifest creation and feed updates
  427. // The POST request admits a JSON structure as defined in the feeds package: `feed.updateRequestJSON`
  428. // The requests can be to a) create a feed manifest, b) update a feed or c) both a+b: create a feed manifest and publish a first update
  429. func (s *Server) HandlePostFeed(w http.ResponseWriter, r *http.Request) {
  430. ruid := GetRUID(r.Context())
  431. uri := GetURI(r.Context())
  432. log.Debug("handle.post.feed", "ruid", ruid)
  433. var err error
  434. // Creation and update must send feed.updateRequestJSON JSON structure
  435. body, err := ioutil.ReadAll(r.Body)
  436. if err != nil {
  437. respondError(w, r, err.Error(), http.StatusInternalServerError)
  438. return
  439. }
  440. fd, err := s.api.ResolveFeed(r.Context(), uri, r.URL.Query())
  441. if err != nil { // couldn't parse query string or retrieve manifest
  442. getFail.Inc(1)
  443. httpStatus := http.StatusBadRequest
  444. if err == api.ErrCannotLoadFeedManifest || err == api.ErrCannotResolveFeedURI {
  445. httpStatus = http.StatusNotFound
  446. }
  447. respondError(w, r, fmt.Sprintf("cannot retrieve feed from manifest: %s", err), httpStatus)
  448. return
  449. }
  450. var updateRequest feed.Request
  451. updateRequest.Feed = *fd
  452. query := r.URL.Query()
  453. if err := updateRequest.FromValues(query, body); err != nil { // decodes request from query parameters
  454. respondError(w, r, err.Error(), http.StatusBadRequest)
  455. return
  456. }
  457. switch {
  458. case updateRequest.IsUpdate():
  459. // Verify that the signature is intact and that the signer is authorized
  460. // to update this feed
  461. // Check this early, to avoid creating a feed and then not being able to set its first update.
  462. if err = updateRequest.Verify(); err != nil {
  463. respondError(w, r, err.Error(), http.StatusForbidden)
  464. return
  465. }
  466. _, err = s.api.FeedsUpdate(r.Context(), &updateRequest)
  467. if err != nil {
  468. respondError(w, r, err.Error(), http.StatusInternalServerError)
  469. return
  470. }
  471. fallthrough
  472. case query.Get("manifest") == "1":
  473. // we create a manifest so we can retrieve feed updates with bzz:// later
  474. // this manifest has a special "feed type" manifest, and saves the
  475. // feed identification used to retrieve feed updates later
  476. m, err := s.api.NewFeedManifest(r.Context(), &updateRequest.Feed)
  477. if err != nil {
  478. respondError(w, r, fmt.Sprintf("failed to create feed manifest: %v", err), http.StatusInternalServerError)
  479. return
  480. }
  481. // the key to the manifest will be passed back to the client
  482. // the client can access the feed directly through its Feed member
  483. // the manifest key can be set as content in the resolver of the ENS name
  484. outdata, err := json.Marshal(m)
  485. if err != nil {
  486. respondError(w, r, fmt.Sprintf("failed to create json response: %s", err), http.StatusInternalServerError)
  487. return
  488. }
  489. fmt.Fprint(w, string(outdata))
  490. w.Header().Add("Content-type", "application/json")
  491. default:
  492. respondError(w, r, "Missing signature in feed update request", http.StatusBadRequest)
  493. }
  494. }
  495. // HandleGetFeed retrieves Swarm feeds updates:
  496. // bzz-feed://<manifest address or ENS name> - get latest feed update, given a manifest address
  497. // - or -
  498. // specify user + topic (optional), subtopic name (optional) directly, without manifest:
  499. // bzz-feed://?user=0x...&topic=0x...&name=subtopic name
  500. // topic defaults to 0x000... if not specified.
  501. // name defaults to empty string if not specified.
  502. // thus, empty name and topic refers to the user's default feed.
  503. //
  504. // Optional parameters:
  505. // time=xx - get the latest update before time (in epoch seconds)
  506. // hint.time=xx - hint the lookup algorithm looking for updates at around that time
  507. // hint.level=xx - hint the lookup algorithm looking for updates at around this frequency level
  508. // meta=1 - get feed metadata and status information instead of performing a feed query
  509. // NOTE: meta=1 will be deprecated in the near future
  510. func (s *Server) HandleGetFeed(w http.ResponseWriter, r *http.Request) {
  511. ruid := GetRUID(r.Context())
  512. uri := GetURI(r.Context())
  513. log.Debug("handle.get.feed", "ruid", ruid)
  514. var err error
  515. fd, err := s.api.ResolveFeed(r.Context(), uri, r.URL.Query())
  516. if err != nil { // couldn't parse query string or retrieve manifest
  517. getFail.Inc(1)
  518. httpStatus := http.StatusBadRequest
  519. if err == api.ErrCannotLoadFeedManifest || err == api.ErrCannotResolveFeedURI {
  520. httpStatus = http.StatusNotFound
  521. }
  522. respondError(w, r, fmt.Sprintf("cannot retrieve feed information from manifest: %s", err), httpStatus)
  523. return
  524. }
  525. // determine if the query specifies period and version or it is a metadata query
  526. if r.URL.Query().Get("meta") == "1" {
  527. unsignedUpdateRequest, err := s.api.FeedsNewRequest(r.Context(), fd)
  528. if err != nil {
  529. getFail.Inc(1)
  530. respondError(w, r, fmt.Sprintf("cannot retrieve feed metadata for feed=%s: %s", fd.Hex(), err), http.StatusNotFound)
  531. return
  532. }
  533. rawResponse, err := unsignedUpdateRequest.MarshalJSON()
  534. if err != nil {
  535. respondError(w, r, fmt.Sprintf("cannot encode unsigned feed update request: %v", err), http.StatusInternalServerError)
  536. return
  537. }
  538. w.Header().Add("Content-type", "application/json")
  539. w.WriteHeader(http.StatusOK)
  540. fmt.Fprint(w, string(rawResponse))
  541. return
  542. }
  543. lookupParams := &feed.Query{Feed: *fd}
  544. if err = lookupParams.FromValues(r.URL.Query()); err != nil { // parse period, version
  545. respondError(w, r, fmt.Sprintf("invalid feed update request:%s", err), http.StatusBadRequest)
  546. return
  547. }
  548. data, err := s.api.FeedsLookup(r.Context(), lookupParams)
  549. // any error from the switch statement will end up here
  550. if err != nil {
  551. code, err2 := s.translateFeedError(w, r, "feed lookup fail", err)
  552. respondError(w, r, err2.Error(), code)
  553. return
  554. }
  555. // All ok, serve the retrieved update
  556. log.Debug("Found update", "feed", fd.Hex(), "ruid", ruid)
  557. w.Header().Set("Content-Type", api.MimeOctetStream)
  558. http.ServeContent(w, r, "", time.Now(), bytes.NewReader(data))
  559. }
  560. func (s *Server) translateFeedError(w http.ResponseWriter, r *http.Request, supErr string, err error) (int, error) {
  561. code := 0
  562. defaultErr := fmt.Errorf("%s: %v", supErr, err)
  563. rsrcErr, ok := err.(*feed.Error)
  564. if !ok && rsrcErr != nil {
  565. code = rsrcErr.Code()
  566. }
  567. switch code {
  568. case storage.ErrInvalidValue:
  569. return http.StatusBadRequest, defaultErr
  570. case storage.ErrNotFound, storage.ErrNotSynced, storage.ErrNothingToReturn, storage.ErrInit:
  571. return http.StatusNotFound, defaultErr
  572. case storage.ErrUnauthorized, storage.ErrInvalidSignature:
  573. return http.StatusUnauthorized, defaultErr
  574. case storage.ErrDataOverflow:
  575. return http.StatusRequestEntityTooLarge, defaultErr
  576. }
  577. return http.StatusInternalServerError, defaultErr
  578. }
  579. // HandleGet handles a GET request to
  580. // - bzz-raw://<key> and responds with the raw content stored at the
  581. // given storage key
  582. // - bzz-hash://<key> and responds with the hash of the content stored
  583. // at the given storage key as a text/plain response
  584. func (s *Server) HandleGet(w http.ResponseWriter, r *http.Request) {
  585. ruid := GetRUID(r.Context())
  586. uri := GetURI(r.Context())
  587. log.Debug("handle.get", "ruid", ruid, "uri", uri)
  588. getCount.Inc(1)
  589. _, pass, _ := r.BasicAuth()
  590. addr, err := s.api.ResolveURI(r.Context(), uri, pass)
  591. if err != nil {
  592. getFail.Inc(1)
  593. respondError(w, r, fmt.Sprintf("cannot resolve %s: %s", uri.Addr, err), http.StatusNotFound)
  594. return
  595. }
  596. w.Header().Set("Cache-Control", "max-age=2147483648, immutable") // url was of type bzz://<hex key>/path, so we are sure it is immutable.
  597. log.Debug("handle.get: resolved", "ruid", ruid, "key", addr)
  598. // if path is set, interpret <key> as a manifest and return the
  599. // raw entry at the given path
  600. etag := common.Bytes2Hex(addr)
  601. noneMatchEtag := r.Header.Get("If-None-Match")
  602. w.Header().Set("ETag", fmt.Sprintf("%q", etag)) // set etag to manifest key or raw entry key.
  603. if noneMatchEtag != "" {
  604. if bytes.Equal(storage.Address(common.Hex2Bytes(noneMatchEtag)), addr) {
  605. w.WriteHeader(http.StatusNotModified)
  606. return
  607. }
  608. }
  609. // check the root chunk exists by retrieving the file's size
  610. reader, isEncrypted := s.api.Retrieve(r.Context(), addr)
  611. if _, err := reader.Size(r.Context(), nil); err != nil {
  612. getFail.Inc(1)
  613. respondError(w, r, fmt.Sprintf("root chunk not found %s: %s", addr, err), http.StatusNotFound)
  614. return
  615. }
  616. w.Header().Set("X-Decrypted", fmt.Sprintf("%v", isEncrypted))
  617. switch {
  618. case uri.Raw():
  619. // allow the request to overwrite the content type using a query
  620. // parameter
  621. if typ := r.URL.Query().Get("content_type"); typ != "" {
  622. w.Header().Set("Content-Type", typ)
  623. }
  624. http.ServeContent(w, r, "", time.Now(), reader)
  625. case uri.Hash():
  626. w.Header().Set("Content-Type", "text/plain")
  627. w.WriteHeader(http.StatusOK)
  628. fmt.Fprint(w, addr)
  629. }
  630. }
  631. // HandleGetList handles a GET request to bzz-list:/<manifest>/<path> and returns
  632. // a list of all files contained in <manifest> under <path> grouped into
  633. // common prefixes using "/" as a delimiter
  634. func (s *Server) HandleGetList(w http.ResponseWriter, r *http.Request) {
  635. ruid := GetRUID(r.Context())
  636. uri := GetURI(r.Context())
  637. _, credentials, _ := r.BasicAuth()
  638. log.Debug("handle.get.list", "ruid", ruid, "uri", uri)
  639. getListCount.Inc(1)
  640. // ensure the root path has a trailing slash so that relative URLs work
  641. if uri.Path == "" && !strings.HasSuffix(r.URL.Path, "/") {
  642. http.Redirect(w, r, r.URL.Path+"/", http.StatusMovedPermanently)
  643. return
  644. }
  645. addr, err := s.api.Resolve(r.Context(), uri.Addr)
  646. if err != nil {
  647. getListFail.Inc(1)
  648. respondError(w, r, fmt.Sprintf("cannot resolve %s: %s", uri.Addr, err), http.StatusNotFound)
  649. return
  650. }
  651. log.Debug("handle.get.list: resolved", "ruid", ruid, "key", addr)
  652. list, err := s.api.GetManifestList(r.Context(), s.api.Decryptor(r.Context(), credentials), addr, uri.Path)
  653. if err != nil {
  654. getListFail.Inc(1)
  655. if isDecryptError(err) {
  656. w.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%q", addr.String()))
  657. respondError(w, r, err.Error(), http.StatusUnauthorized)
  658. return
  659. }
  660. respondError(w, r, err.Error(), http.StatusInternalServerError)
  661. return
  662. }
  663. // if the client wants HTML (e.g. a browser) then render the list as a
  664. // HTML index with relative URLs
  665. if strings.Contains(r.Header.Get("Accept"), "text/html") {
  666. w.Header().Set("Content-Type", "text/html")
  667. err := TemplatesMap["bzz-list"].Execute(w, &htmlListData{
  668. URI: &api.URI{
  669. Scheme: "bzz",
  670. Addr: uri.Addr,
  671. Path: uri.Path,
  672. },
  673. List: &list,
  674. })
  675. if err != nil {
  676. getListFail.Inc(1)
  677. log.Error(fmt.Sprintf("error rendering list HTML: %s", err))
  678. }
  679. return
  680. }
  681. w.Header().Set("Content-Type", "application/json")
  682. json.NewEncoder(w).Encode(&list)
  683. }
  684. // HandleGetFile handles a GET request to bzz://<manifest>/<path> and responds
  685. // with the content of the file at <path> from the given <manifest>
  686. func (s *Server) HandleGetFile(w http.ResponseWriter, r *http.Request) {
  687. ruid := GetRUID(r.Context())
  688. uri := GetURI(r.Context())
  689. _, credentials, _ := r.BasicAuth()
  690. log.Debug("handle.get.file", "ruid", ruid, "uri", r.RequestURI)
  691. getFileCount.Inc(1)
  692. // ensure the root path has a trailing slash so that relative URLs work
  693. if uri.Path == "" && !strings.HasSuffix(r.URL.Path, "/") {
  694. http.Redirect(w, r, r.URL.Path+"/", http.StatusMovedPermanently)
  695. return
  696. }
  697. var err error
  698. manifestAddr := uri.Address()
  699. if manifestAddr == nil {
  700. manifestAddr, err = s.api.Resolve(r.Context(), uri.Addr)
  701. if err != nil {
  702. getFileFail.Inc(1)
  703. respondError(w, r, fmt.Sprintf("cannot resolve %s: %s", uri.Addr, err), http.StatusNotFound)
  704. return
  705. }
  706. } else {
  707. w.Header().Set("Cache-Control", "max-age=2147483648, immutable") // url was of type bzz://<hex key>/path, so we are sure it is immutable.
  708. }
  709. log.Debug("handle.get.file: resolved", "ruid", ruid, "key", manifestAddr)
  710. reader, contentType, status, contentKey, err := s.api.Get(r.Context(), s.api.Decryptor(r.Context(), credentials), manifestAddr, uri.Path)
  711. etag := common.Bytes2Hex(contentKey)
  712. noneMatchEtag := r.Header.Get("If-None-Match")
  713. w.Header().Set("ETag", fmt.Sprintf("%q", etag)) // set etag to actual content key.
  714. if noneMatchEtag != "" {
  715. if bytes.Equal(storage.Address(common.Hex2Bytes(noneMatchEtag)), contentKey) {
  716. w.WriteHeader(http.StatusNotModified)
  717. return
  718. }
  719. }
  720. if err != nil {
  721. if isDecryptError(err) {
  722. w.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%q", manifestAddr))
  723. respondError(w, r, err.Error(), http.StatusUnauthorized)
  724. return
  725. }
  726. switch status {
  727. case http.StatusNotFound:
  728. getFileNotFound.Inc(1)
  729. respondError(w, r, err.Error(), http.StatusNotFound)
  730. default:
  731. getFileFail.Inc(1)
  732. respondError(w, r, err.Error(), http.StatusInternalServerError)
  733. }
  734. return
  735. }
  736. //the request results in ambiguous files
  737. //e.g. /read with readme.md and readinglist.txt available in manifest
  738. if status == http.StatusMultipleChoices {
  739. list, err := s.api.GetManifestList(r.Context(), s.api.Decryptor(r.Context(), credentials), manifestAddr, uri.Path)
  740. if err != nil {
  741. getFileFail.Inc(1)
  742. if isDecryptError(err) {
  743. w.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%q", manifestAddr))
  744. respondError(w, r, err.Error(), http.StatusUnauthorized)
  745. return
  746. }
  747. respondError(w, r, err.Error(), http.StatusInternalServerError)
  748. return
  749. }
  750. log.Debug(fmt.Sprintf("Multiple choices! --> %v", list), "ruid", ruid)
  751. //show a nice page links to available entries
  752. ShowMultipleChoices(w, r, list)
  753. return
  754. }
  755. // check the root chunk exists by retrieving the file's size
  756. if _, err := reader.Size(r.Context(), nil); err != nil {
  757. getFileNotFound.Inc(1)
  758. respondError(w, r, fmt.Sprintf("file not found %s: %s", uri, err), http.StatusNotFound)
  759. return
  760. }
  761. if contentType != "" {
  762. w.Header().Set("Content-Type", contentType)
  763. }
  764. fileName := uri.Addr
  765. if found := path.Base(uri.Path); found != "" && found != "." && found != "/" {
  766. fileName = found
  767. }
  768. w.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=\"%s\"", fileName))
  769. http.ServeContent(w, r, fileName, time.Now(), newBufferedReadSeeker(reader, getFileBufferSize))
  770. }
  771. // calculateNumberOfChunks calculates the number of chunks in an arbitrary content length
  772. func calculateNumberOfChunks(contentLength int64, isEncrypted bool) int64 {
  773. if contentLength < 4096 {
  774. return 1
  775. }
  776. branchingFactor := 128
  777. if isEncrypted {
  778. branchingFactor = 64
  779. }
  780. dataChunks := math.Ceil(float64(contentLength) / float64(4096))
  781. totalChunks := dataChunks
  782. intermediate := dataChunks / float64(branchingFactor)
  783. for intermediate > 1 {
  784. totalChunks += math.Ceil(intermediate)
  785. intermediate = intermediate / float64(branchingFactor)
  786. }
  787. return int64(totalChunks) + 1
  788. }
  789. // The size of buffer used for bufio.Reader on LazyChunkReader passed to
  790. // http.ServeContent in HandleGetFile.
  791. // Warning: This value influences the number of chunk requests and chunker join goroutines
  792. // per file request.
  793. // Recommended value is 4 times the io.Copy default buffer value which is 32kB.
  794. const getFileBufferSize = 4 * 32 * 1024
  795. // bufferedReadSeeker wraps bufio.Reader to expose Seek method
  796. // from the provied io.ReadSeeker in newBufferedReadSeeker.
  797. type bufferedReadSeeker struct {
  798. r io.Reader
  799. s io.Seeker
  800. }
  801. // newBufferedReadSeeker creates a new instance of bufferedReadSeeker,
  802. // out of io.ReadSeeker. Argument `size` is the size of the read buffer.
  803. func newBufferedReadSeeker(readSeeker io.ReadSeeker, size int) bufferedReadSeeker {
  804. return bufferedReadSeeker{
  805. r: bufio.NewReaderSize(readSeeker, size),
  806. s: readSeeker,
  807. }
  808. }
  809. func (b bufferedReadSeeker) Read(p []byte) (n int, err error) {
  810. return b.r.Read(p)
  811. }
  812. func (b bufferedReadSeeker) Seek(offset int64, whence int) (int64, error) {
  813. return b.s.Seek(offset, whence)
  814. }
  815. type loggingResponseWriter struct {
  816. http.ResponseWriter
  817. statusCode int
  818. }
  819. func newLoggingResponseWriter(w http.ResponseWriter) *loggingResponseWriter {
  820. return &loggingResponseWriter{w, http.StatusOK}
  821. }
  822. func (lrw *loggingResponseWriter) WriteHeader(code int) {
  823. lrw.statusCode = code
  824. lrw.ResponseWriter.WriteHeader(code)
  825. }
  826. func isDecryptError(err error) bool {
  827. return strings.Contains(err.Error(), api.ErrDecrypt.Error())
  828. }