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