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