github.com/SmartMeshFoundation/Spectrum@v0.0.0-20220621030607-452a266fee1e/swarm/api/http/server.go (about) 1 // Copyright 2016 The Spectrum Authors 2 // This file is part of the Spectrum library. 3 // 4 // The Spectrum 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 Spectrum 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 Spectrum 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 "archive/tar" 24 "encoding/json" 25 "errors" 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/SmartMeshFoundation/Spectrum/common" 39 "github.com/SmartMeshFoundation/Spectrum/log" 40 "github.com/SmartMeshFoundation/Spectrum/swarm/api" 41 "github.com/SmartMeshFoundation/Spectrum/swarm/storage" 42 "github.com/rs/cors" 43 ) 44 45 // ServerConfig is the basic configuration needed for the HTTP server and also 46 // includes CORS settings. 47 type ServerConfig struct { 48 Addr string 49 CorsString string 50 } 51 52 // browser API for registering bzz url scheme handlers: 53 // https://developer.mozilla.org/en/docs/Web-based_protocol_handlers 54 // electron (chromium) api for registering bzz url scheme handlers: 55 // https://github.com/atom/electron/blob/master/docs/api/protocol.md 56 57 // starts up http server 58 func StartHttpServer(api *api.Api, config *ServerConfig) { 59 var allowedOrigins []string 60 for _, domain := range strings.Split(config.CorsString, ",") { 61 allowedOrigins = append(allowedOrigins, strings.TrimSpace(domain)) 62 } 63 c := cors.New(cors.Options{ 64 AllowedOrigins: allowedOrigins, 65 AllowedMethods: []string{"POST", "GET", "DELETE", "PATCH", "PUT"}, 66 MaxAge: 600, 67 AllowedHeaders: []string{"*"}, 68 }) 69 hdlr := c.Handler(NewServer(api)) 70 71 go http.ListenAndServe(config.Addr, hdlr) 72 } 73 74 func NewServer(api *api.Api) *Server { 75 return &Server{api} 76 } 77 78 type Server struct { 79 api *api.Api 80 } 81 82 // Request wraps http.Request and also includes the parsed bzz URI 83 type Request struct { 84 http.Request 85 86 uri *api.URI 87 } 88 89 // HandlePostRaw handles a POST request to a raw bzz-raw:/ URI, stores the request 90 // body in swarm and returns the resulting storage key as a text/plain response 91 func (s *Server) HandlePostRaw(w http.ResponseWriter, r *Request) { 92 if r.uri.Path != "" { 93 s.BadRequest(w, r, "raw POST request cannot contain a path") 94 return 95 } 96 97 if r.Header.Get("Content-Length") == "" { 98 s.BadRequest(w, r, "missing Content-Length header in request") 99 return 100 } 101 102 key, err := s.api.Store(r.Body, r.ContentLength, nil) 103 if err != nil { 104 s.Error(w, r, err) 105 return 106 } 107 s.logDebug("content for %s stored", key.Log()) 108 109 w.Header().Set("Content-Type", "text/plain") 110 w.WriteHeader(http.StatusOK) 111 fmt.Fprint(w, key) 112 } 113 114 // HandlePostFiles handles a POST request (or deprecated PUT request) to 115 // bzz:/<hash>/<path> which contains either a single file or multiple files 116 // (either a tar archive or multipart form), adds those files either to an 117 // existing manifest or to a new manifest under <path> and returns the 118 // resulting manifest hash as a text/plain response 119 func (s *Server) HandlePostFiles(w http.ResponseWriter, r *Request) { 120 contentType, params, err := mime.ParseMediaType(r.Header.Get("Content-Type")) 121 if err != nil { 122 s.BadRequest(w, r, err.Error()) 123 return 124 } 125 126 var key storage.Key 127 if r.uri.Addr != "" { 128 key, err = s.api.Resolve(r.uri) 129 if err != nil { 130 s.Error(w, r, fmt.Errorf("error resolving %s: %s", r.uri.Addr, err)) 131 return 132 } 133 } else { 134 key, err = s.api.NewManifest() 135 if err != nil { 136 s.Error(w, r, err) 137 return 138 } 139 } 140 141 newKey, err := s.updateManifest(key, func(mw *api.ManifestWriter) error { 142 switch contentType { 143 144 case "application/x-tar": 145 return s.handleTarUpload(r, mw) 146 147 case "multipart/form-data": 148 return s.handleMultipartUpload(r, params["boundary"], mw) 149 150 default: 151 return s.handleDirectUpload(r, mw) 152 } 153 }) 154 if err != nil { 155 s.Error(w, r, fmt.Errorf("error creating manifest: %s", err)) 156 return 157 } 158 159 w.Header().Set("Content-Type", "text/plain") 160 w.WriteHeader(http.StatusOK) 161 fmt.Fprint(w, newKey) 162 } 163 164 func (s *Server) handleTarUpload(req *Request, mw *api.ManifestWriter) error { 165 tr := tar.NewReader(req.Body) 166 for { 167 hdr, err := tr.Next() 168 if err == io.EOF { 169 return nil 170 } else if err != nil { 171 return fmt.Errorf("error reading tar stream: %s", err) 172 } 173 174 // only store regular files 175 if !hdr.FileInfo().Mode().IsRegular() { 176 continue 177 } 178 179 // add the entry under the path from the request 180 path := path.Join(req.uri.Path, hdr.Name) 181 entry := &api.ManifestEntry{ 182 Path: path, 183 ContentType: hdr.Xattrs["user.swarm.content-type"], 184 Mode: hdr.Mode, 185 Size: hdr.Size, 186 ModTime: hdr.ModTime, 187 } 188 s.logDebug("adding %s (%d bytes) to new manifest", entry.Path, entry.Size) 189 contentKey, err := mw.AddEntry(tr, entry) 190 if err != nil { 191 return fmt.Errorf("error adding manifest entry from tar stream: %s", err) 192 } 193 s.logDebug("content for %s stored", contentKey.Log()) 194 } 195 } 196 197 func (s *Server) handleMultipartUpload(req *Request, boundary string, mw *api.ManifestWriter) error { 198 mr := multipart.NewReader(req.Body, boundary) 199 for { 200 part, err := mr.NextPart() 201 if err == io.EOF { 202 return nil 203 } else if err != nil { 204 return fmt.Errorf("error reading multipart form: %s", err) 205 } 206 207 var size int64 208 var reader io.Reader = part 209 if contentLength := part.Header.Get("Content-Length"); contentLength != "" { 210 size, err = strconv.ParseInt(contentLength, 10, 64) 211 if err != nil { 212 return fmt.Errorf("error parsing multipart content length: %s", err) 213 } 214 reader = part 215 } else { 216 // copy the part to a tmp file to get its size 217 tmp, err := ioutil.TempFile("", "swarm-multipart") 218 if err != nil { 219 return err 220 } 221 defer os.Remove(tmp.Name()) 222 defer tmp.Close() 223 size, err = io.Copy(tmp, part) 224 if err != nil { 225 return fmt.Errorf("error copying multipart content: %s", err) 226 } 227 if _, err := tmp.Seek(0, io.SeekStart); err != nil { 228 return fmt.Errorf("error copying multipart content: %s", err) 229 } 230 reader = tmp 231 } 232 233 // add the entry under the path from the request 234 name := part.FileName() 235 if name == "" { 236 name = part.FormName() 237 } 238 path := path.Join(req.uri.Path, name) 239 entry := &api.ManifestEntry{ 240 Path: path, 241 ContentType: part.Header.Get("Content-Type"), 242 Size: size, 243 ModTime: time.Now(), 244 } 245 s.logDebug("adding %s (%d bytes) to new manifest", entry.Path, entry.Size) 246 contentKey, err := mw.AddEntry(reader, entry) 247 if err != nil { 248 return fmt.Errorf("error adding manifest entry from multipart form: %s", err) 249 } 250 s.logDebug("content for %s stored", contentKey.Log()) 251 } 252 } 253 254 func (s *Server) handleDirectUpload(req *Request, mw *api.ManifestWriter) error { 255 key, err := mw.AddEntry(req.Body, &api.ManifestEntry{ 256 Path: req.uri.Path, 257 ContentType: req.Header.Get("Content-Type"), 258 Mode: 0644, 259 Size: req.ContentLength, 260 ModTime: time.Now(), 261 }) 262 if err != nil { 263 return err 264 } 265 s.logDebug("content for %s stored", key.Log()) 266 return nil 267 } 268 269 // HandleDelete handles a DELETE request to bzz:/<manifest>/<path>, removes 270 // <path> from <manifest> and returns the resulting manifest hash as a 271 // text/plain response 272 func (s *Server) HandleDelete(w http.ResponseWriter, r *Request) { 273 key, err := s.api.Resolve(r.uri) 274 if err != nil { 275 s.Error(w, r, fmt.Errorf("error resolving %s: %s", r.uri.Addr, err)) 276 return 277 } 278 279 newKey, err := s.updateManifest(key, func(mw *api.ManifestWriter) error { 280 s.logDebug("removing %s from manifest %s", r.uri.Path, key.Log()) 281 return mw.RemoveEntry(r.uri.Path) 282 }) 283 if err != nil { 284 s.Error(w, r, fmt.Errorf("error updating manifest: %s", err)) 285 return 286 } 287 288 w.Header().Set("Content-Type", "text/plain") 289 w.WriteHeader(http.StatusOK) 290 fmt.Fprint(w, newKey) 291 } 292 293 // HandleGet handles a GET request to 294 // - bzz-raw://<key> and responds with the raw content stored at the 295 // given storage key 296 // - bzz-hash://<key> and responds with the hash of the content stored 297 // at the given storage key as a text/plain response 298 func (s *Server) HandleGet(w http.ResponseWriter, r *Request) { 299 key, err := s.api.Resolve(r.uri) 300 if err != nil { 301 s.Error(w, r, fmt.Errorf("error resolving %s: %s", r.uri.Addr, err)) 302 return 303 } 304 305 // if path is set, interpret <key> as a manifest and return the 306 // raw entry at the given path 307 if r.uri.Path != "" { 308 walker, err := s.api.NewManifestWalker(key, nil) 309 if err != nil { 310 s.BadRequest(w, r, fmt.Sprintf("%s is not a manifest", key)) 311 return 312 } 313 var entry *api.ManifestEntry 314 walker.Walk(func(e *api.ManifestEntry) error { 315 // if the entry matches the path, set entry and stop 316 // the walk 317 if e.Path == r.uri.Path { 318 entry = e 319 // return an error to cancel the walk 320 return errors.New("found") 321 } 322 323 // ignore non-manifest files 324 if e.ContentType != api.ManifestType { 325 return nil 326 } 327 328 // if the manifest's path is a prefix of the 329 // requested path, recurse into it by returning 330 // nil and continuing the walk 331 if strings.HasPrefix(r.uri.Path, e.Path) { 332 return nil 333 } 334 335 return api.SkipManifest 336 }) 337 if entry == nil { 338 s.NotFound(w, r, fmt.Errorf("Manifest entry could not be loaded")) 339 return 340 } 341 key = storage.Key(common.Hex2Bytes(entry.Hash)) 342 } 343 344 // check the root chunk exists by retrieving the file's size 345 reader := s.api.Retrieve(key) 346 if _, err := reader.Size(nil); err != nil { 347 s.NotFound(w, r, fmt.Errorf("Root chunk not found %s: %s", key, err)) 348 return 349 } 350 351 switch { 352 case r.uri.Raw(): 353 // allow the request to overwrite the content type using a query 354 // parameter 355 contentType := "application/octet-stream" 356 if typ := r.URL.Query().Get("content_type"); typ != "" { 357 contentType = typ 358 } 359 w.Header().Set("Content-Type", contentType) 360 361 http.ServeContent(w, &r.Request, "", time.Now(), reader) 362 case r.uri.Hash(): 363 w.Header().Set("Content-Type", "text/plain") 364 w.WriteHeader(http.StatusOK) 365 fmt.Fprint(w, key) 366 } 367 } 368 369 // HandleGetFiles handles a GET request to bzz:/<manifest> with an Accept 370 // header of "application/x-tar" and returns a tar stream of all files 371 // contained in the manifest 372 func (s *Server) HandleGetFiles(w http.ResponseWriter, r *Request) { 373 if r.uri.Path != "" { 374 s.BadRequest(w, r, "files request cannot contain a path") 375 return 376 } 377 378 key, err := s.api.Resolve(r.uri) 379 if err != nil { 380 s.Error(w, r, fmt.Errorf("error resolving %s: %s", r.uri.Addr, err)) 381 return 382 } 383 384 walker, err := s.api.NewManifestWalker(key, nil) 385 if err != nil { 386 s.Error(w, r, err) 387 return 388 } 389 390 tw := tar.NewWriter(w) 391 defer tw.Close() 392 w.Header().Set("Content-Type", "application/x-tar") 393 w.WriteHeader(http.StatusOK) 394 395 err = walker.Walk(func(entry *api.ManifestEntry) error { 396 // ignore manifests (walk will recurse into them) 397 if entry.ContentType == api.ManifestType { 398 return nil 399 } 400 401 // retrieve the entry's key and size 402 reader := s.api.Retrieve(storage.Key(common.Hex2Bytes(entry.Hash))) 403 size, err := reader.Size(nil) 404 if err != nil { 405 return err 406 } 407 408 // write a tar header for the entry 409 hdr := &tar.Header{ 410 Name: entry.Path, 411 Mode: entry.Mode, 412 Size: size, 413 ModTime: entry.ModTime, 414 Xattrs: map[string]string{ 415 "user.swarm.content-type": entry.ContentType, 416 }, 417 } 418 if err := tw.WriteHeader(hdr); err != nil { 419 return err 420 } 421 422 // copy the file into the tar stream 423 n, err := io.Copy(tw, io.LimitReader(reader, hdr.Size)) 424 if err != nil { 425 return err 426 } else if n != size { 427 return fmt.Errorf("error writing %s: expected %d bytes but sent %d", entry.Path, size, n) 428 } 429 430 return nil 431 }) 432 if err != nil { 433 s.logError("error generating tar stream: %s", err) 434 } 435 } 436 437 // HandleGetList handles a GET request to bzz-list:/<manifest>/<path> and returns 438 // a list of all files contained in <manifest> under <path> grouped into 439 // common prefixes using "/" as a delimiter 440 func (s *Server) HandleGetList(w http.ResponseWriter, r *Request) { 441 // ensure the root path has a trailing slash so that relative URLs work 442 if r.uri.Path == "" && !strings.HasSuffix(r.URL.Path, "/") { 443 http.Redirect(w, &r.Request, r.URL.Path+"/", http.StatusMovedPermanently) 444 return 445 } 446 447 key, err := s.api.Resolve(r.uri) 448 if err != nil { 449 s.Error(w, r, fmt.Errorf("error resolving %s: %s", r.uri.Addr, err)) 450 return 451 } 452 453 list, err := s.getManifestList(key, r.uri.Path) 454 455 if err != nil { 456 s.Error(w, r, err) 457 return 458 } 459 460 // if the client wants HTML (e.g. a browser) then render the list as a 461 // HTML index with relative URLs 462 if strings.Contains(r.Header.Get("Accept"), "text/html") { 463 w.Header().Set("Content-Type", "text/html") 464 err := htmlListTemplate.Execute(w, &htmlListData{ 465 URI: &api.URI{ 466 Scheme: "bzz", 467 Addr: r.uri.Addr, 468 Path: r.uri.Path, 469 }, 470 List: &list, 471 }) 472 if err != nil { 473 s.logError("error rendering list HTML: %s", err) 474 } 475 return 476 } 477 478 w.Header().Set("Content-Type", "application/json") 479 json.NewEncoder(w).Encode(&list) 480 } 481 482 func (s *Server) getManifestList(key storage.Key, prefix string) (list api.ManifestList, err error) { 483 walker, err := s.api.NewManifestWalker(key, nil) 484 if err != nil { 485 return 486 } 487 488 err = walker.Walk(func(entry *api.ManifestEntry) error { 489 // handle non-manifest files 490 if entry.ContentType != api.ManifestType { 491 // ignore the file if it doesn't have the specified prefix 492 if !strings.HasPrefix(entry.Path, prefix) { 493 return nil 494 } 495 496 // if the path after the prefix contains a slash, add a 497 // common prefix to the list, otherwise add the entry 498 suffix := strings.TrimPrefix(entry.Path, prefix) 499 if index := strings.Index(suffix, "/"); index > -1 { 500 list.CommonPrefixes = append(list.CommonPrefixes, prefix+suffix[:index+1]) 501 return nil 502 } 503 if entry.Path == "" { 504 entry.Path = "/" 505 } 506 list.Entries = append(list.Entries, entry) 507 return nil 508 } 509 510 // if the manifest's path is a prefix of the specified prefix 511 // then just recurse into the manifest by returning nil and 512 // continuing the walk 513 if strings.HasPrefix(prefix, entry.Path) { 514 return nil 515 } 516 517 // if the manifest's path has the specified prefix, then if the 518 // path after the prefix contains a slash, add a common prefix 519 // to the list and skip the manifest, otherwise recurse into 520 // the manifest by returning nil and continuing the walk 521 if strings.HasPrefix(entry.Path, prefix) { 522 suffix := strings.TrimPrefix(entry.Path, prefix) 523 if index := strings.Index(suffix, "/"); index > -1 { 524 list.CommonPrefixes = append(list.CommonPrefixes, prefix+suffix[:index+1]) 525 return api.SkipManifest 526 } 527 return nil 528 } 529 530 // the manifest neither has the prefix or needs recursing in to 531 // so just skip it 532 return api.SkipManifest 533 }) 534 535 return list, nil 536 } 537 538 // HandleGetFile handles a GET request to bzz://<manifest>/<path> and responds 539 // with the content of the file at <path> from the given <manifest> 540 func (s *Server) HandleGetFile(w http.ResponseWriter, r *Request) { 541 // ensure the root path has a trailing slash so that relative URLs work 542 if r.uri.Path == "" && !strings.HasSuffix(r.URL.Path, "/") { 543 http.Redirect(w, &r.Request, r.URL.Path+"/", http.StatusMovedPermanently) 544 return 545 } 546 547 key, err := s.api.Resolve(r.uri) 548 if err != nil { 549 s.Error(w, r, fmt.Errorf("error resolving %s: %s", r.uri.Addr, err)) 550 return 551 } 552 553 reader, contentType, status, err := s.api.Get(key, r.uri.Path) 554 if err != nil { 555 switch status { 556 case http.StatusNotFound: 557 s.NotFound(w, r, err) 558 default: 559 s.Error(w, r, err) 560 } 561 return 562 } 563 564 //the request results in ambiguous files 565 //e.g. /read with readme.md and readinglist.txt available in manifest 566 if status == http.StatusMultipleChoices { 567 list, err := s.getManifestList(key, r.uri.Path) 568 569 if err != nil { 570 s.Error(w, r, err) 571 return 572 } 573 574 s.logDebug(fmt.Sprintf("Multiple choices! --> %v", list)) 575 //show a nice page links to available entries 576 ShowMultipleChoices(w, &r.Request, list) 577 return 578 } 579 580 // check the root chunk exists by retrieving the file's size 581 if _, err := reader.Size(nil); err != nil { 582 s.NotFound(w, r, fmt.Errorf("File not found %s: %s", r.uri, err)) 583 return 584 } 585 586 w.Header().Set("Content-Type", contentType) 587 588 http.ServeContent(w, &r.Request, "", time.Now(), reader) 589 } 590 591 func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { 592 s.logDebug("HTTP %s request URL: '%s', Host: '%s', Path: '%s', Referer: '%s', Accept: '%s'", r.Method, r.RequestURI, r.URL.Host, r.URL.Path, r.Referer(), r.Header.Get("Accept")) 593 594 uri, err := api.Parse(strings.TrimLeft(r.URL.Path, "/")) 595 req := &Request{Request: *r, uri: uri} 596 if err != nil { 597 s.logError("Invalid URI %q: %s", r.URL.Path, err) 598 s.BadRequest(w, req, fmt.Sprintf("Invalid URI %q: %s", r.URL.Path, err)) 599 return 600 } 601 s.logDebug("%s request received for %s", r.Method, uri) 602 603 switch r.Method { 604 case "POST": 605 if uri.Raw() || uri.DeprecatedRaw() { 606 s.HandlePostRaw(w, req) 607 } else { 608 s.HandlePostFiles(w, req) 609 } 610 611 case "PUT": 612 // DEPRECATED: 613 // clients should send a POST request (the request creates a 614 // new manifest leaving the existing one intact, so it isn't 615 // strictly a traditional PUT request which replaces content 616 // at a URI, and POST is more ubiquitous) 617 if uri.Raw() || uri.DeprecatedRaw() { 618 ShowError(w, r, fmt.Sprintf("No PUT to %s allowed.", uri), http.StatusBadRequest) 619 return 620 } else { 621 s.HandlePostFiles(w, req) 622 } 623 624 case "DELETE": 625 if uri.Raw() || uri.DeprecatedRaw() { 626 ShowError(w, r, fmt.Sprintf("No DELETE to %s allowed.", uri), http.StatusBadRequest) 627 return 628 } 629 s.HandleDelete(w, req) 630 631 case "GET": 632 if uri.Raw() || uri.Hash() || uri.DeprecatedRaw() { 633 s.HandleGet(w, req) 634 return 635 } 636 637 if uri.List() { 638 s.HandleGetList(w, req) 639 return 640 } 641 642 if r.Header.Get("Accept") == "application/x-tar" { 643 s.HandleGetFiles(w, req) 644 return 645 } 646 647 s.HandleGetFile(w, req) 648 649 default: 650 ShowError(w, r, fmt.Sprintf("Method "+r.Method+" is not supported.", uri), http.StatusMethodNotAllowed) 651 652 } 653 } 654 655 func (s *Server) updateManifest(key storage.Key, update func(mw *api.ManifestWriter) error) (storage.Key, error) { 656 mw, err := s.api.NewManifestWriter(key, nil) 657 if err != nil { 658 return nil, err 659 } 660 661 if err := update(mw); err != nil { 662 return nil, err 663 } 664 665 key, err = mw.Store() 666 if err != nil { 667 return nil, err 668 } 669 s.logDebug("generated manifest %s", key) 670 return key, nil 671 } 672 673 func (s *Server) logDebug(format string, v ...interface{}) { 674 log.Debug(fmt.Sprintf("[BZZ] HTTP: "+format, v...)) 675 } 676 677 func (s *Server) logError(format string, v ...interface{}) { 678 log.Error(fmt.Sprintf("[BZZ] HTTP: "+format, v...)) 679 } 680 681 func (s *Server) BadRequest(w http.ResponseWriter, r *Request, reason string) { 682 ShowError(w, &r.Request, fmt.Sprintf("Bad request %s %s: %s", r.Method, r.uri, reason), http.StatusBadRequest) 683 } 684 685 func (s *Server) Error(w http.ResponseWriter, r *Request, err error) { 686 ShowError(w, &r.Request, fmt.Sprintf("Error serving %s %s: %s", r.Method, r.uri, err), http.StatusInternalServerError) 687 } 688 689 func (s *Server) NotFound(w http.ResponseWriter, r *Request, err error) { 690 ShowError(w, &r.Request, fmt.Sprintf("NOT FOUND error serving %s %s: %s", r.Method, r.uri, err), http.StatusNotFound) 691 }