github.com/janotchain/janota@v0.0.0-20220824112012-93ea4c5dee78/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 "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/ethereum/go-ethereum/common" 39 "github.com/ethereum/go-ethereum/log" 40 "github.com/ethereum/go-ethereum/swarm/api" 41 "github.com/ethereum/go-ethereum/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 bzzr:/ 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 // HandleGetRaw handles a GET request to bzzr://<key> and responds with 294 // the raw content stored at the given storage key 295 func (s *Server) HandleGetRaw(w http.ResponseWriter, r *Request) { 296 key, err := s.api.Resolve(r.uri) 297 if err != nil { 298 s.Error(w, r, fmt.Errorf("error resolving %s: %s", r.uri.Addr, err)) 299 return 300 } 301 302 // if path is set, interpret <key> as a manifest and return the 303 // raw entry at the given path 304 if r.uri.Path != "" { 305 walker, err := s.api.NewManifestWalker(key, nil) 306 if err != nil { 307 s.BadRequest(w, r, fmt.Sprintf("%s is not a manifest", key)) 308 return 309 } 310 var entry *api.ManifestEntry 311 walker.Walk(func(e *api.ManifestEntry) error { 312 // if the entry matches the path, set entry and stop 313 // the walk 314 if e.Path == r.uri.Path { 315 entry = e 316 // return an error to cancel the walk 317 return errors.New("found") 318 } 319 320 // ignore non-manifest files 321 if e.ContentType != api.ManifestType { 322 return nil 323 } 324 325 // if the manifest's path is a prefix of the 326 // requested path, recurse into it by returning 327 // nil and continuing the walk 328 if strings.HasPrefix(r.uri.Path, e.Path) { 329 return nil 330 } 331 332 return api.SkipManifest 333 }) 334 if entry == nil { 335 s.NotFound(w, r, fmt.Errorf("Manifest entry could not be loaded")) 336 return 337 } 338 key = storage.Key(common.Hex2Bytes(entry.Hash)) 339 } 340 341 // check the root chunk exists by retrieving the file's size 342 reader := s.api.Retrieve(key) 343 if _, err := reader.Size(nil); err != nil { 344 s.NotFound(w, r, fmt.Errorf("Root chunk not found %s: %s", key, err)) 345 return 346 } 347 348 // allow the request to overwrite the content type using a query 349 // parameter 350 contentType := "application/octet-stream" 351 if typ := r.URL.Query().Get("content_type"); typ != "" { 352 contentType = typ 353 } 354 w.Header().Set("Content-Type", contentType) 355 356 http.ServeContent(w, &r.Request, "", time.Now(), reader) 357 } 358 359 // HandleGetFiles handles a GET request to bzz:/<manifest> with an Accept 360 // header of "application/x-tar" and returns a tar stream of all files 361 // contained in the manifest 362 func (s *Server) HandleGetFiles(w http.ResponseWriter, r *Request) { 363 if r.uri.Path != "" { 364 s.BadRequest(w, r, "files request cannot contain a path") 365 return 366 } 367 368 key, err := s.api.Resolve(r.uri) 369 if err != nil { 370 s.Error(w, r, fmt.Errorf("error resolving %s: %s", r.uri.Addr, err)) 371 return 372 } 373 374 walker, err := s.api.NewManifestWalker(key, nil) 375 if err != nil { 376 s.Error(w, r, err) 377 return 378 } 379 380 tw := tar.NewWriter(w) 381 defer tw.Close() 382 w.Header().Set("Content-Type", "application/x-tar") 383 w.WriteHeader(http.StatusOK) 384 385 err = walker.Walk(func(entry *api.ManifestEntry) error { 386 // ignore manifests (walk will recurse into them) 387 if entry.ContentType == api.ManifestType { 388 return nil 389 } 390 391 // retrieve the entry's key and size 392 reader := s.api.Retrieve(storage.Key(common.Hex2Bytes(entry.Hash))) 393 size, err := reader.Size(nil) 394 if err != nil { 395 return err 396 } 397 398 // write a tar header for the entry 399 hdr := &tar.Header{ 400 Name: entry.Path, 401 Mode: entry.Mode, 402 Size: size, 403 ModTime: entry.ModTime, 404 Xattrs: map[string]string{ 405 "user.swarm.content-type": entry.ContentType, 406 }, 407 } 408 if err := tw.WriteHeader(hdr); err != nil { 409 return err 410 } 411 412 // copy the file into the tar stream 413 n, err := io.Copy(tw, io.LimitReader(reader, hdr.Size)) 414 if err != nil { 415 return err 416 } else if n != size { 417 return fmt.Errorf("error writing %s: expected %d bytes but sent %d", entry.Path, size, n) 418 } 419 420 return nil 421 }) 422 if err != nil { 423 s.logError("error generating tar stream: %s", err) 424 } 425 } 426 427 // HandleGetList handles a GET request to bzz:/<manifest>/<path> which has 428 // the "list" query parameter set to "true" and returns a list of all files 429 // contained in <manifest> under <path> grouped into common prefixes using 430 // "/" as a delimiter 431 func (s *Server) HandleGetList(w http.ResponseWriter, r *Request) { 432 // ensure the root path has a trailing slash so that relative URLs work 433 if r.uri.Path == "" && !strings.HasSuffix(r.URL.Path, "/") { 434 http.Redirect(w, &r.Request, r.URL.Path+"/?list=true", http.StatusMovedPermanently) 435 return 436 } 437 438 key, err := s.api.Resolve(r.uri) 439 if err != nil { 440 s.Error(w, r, fmt.Errorf("error resolving %s: %s", r.uri.Addr, err)) 441 return 442 } 443 444 list, err := s.getManifestList(key, r.uri.Path) 445 446 if err != nil { 447 s.Error(w, r, err) 448 return 449 } 450 451 // if the client wants HTML (e.g. a browser) then render the list as a 452 // HTML index with relative URLs 453 if strings.Contains(r.Header.Get("Accept"), "text/html") { 454 w.Header().Set("Content-Type", "text/html") 455 err := htmlListTemplate.Execute(w, &htmlListData{ 456 URI: r.uri, 457 List: &list, 458 }) 459 if err != nil { 460 s.logError("error rendering list HTML: %s", err) 461 } 462 return 463 } 464 465 w.Header().Set("Content-Type", "application/json") 466 json.NewEncoder(w).Encode(&list) 467 } 468 469 func (s *Server) getManifestList(key storage.Key, prefix string) (list api.ManifestList, err error) { 470 walker, err := s.api.NewManifestWalker(key, nil) 471 if err != nil { 472 return 473 } 474 475 err = walker.Walk(func(entry *api.ManifestEntry) error { 476 // handle non-manifest files 477 if entry.ContentType != api.ManifestType { 478 // ignore the file if it doesn't have the specified prefix 479 if !strings.HasPrefix(entry.Path, prefix) { 480 return nil 481 } 482 483 // if the path after the prefix contains a slash, add a 484 // common prefix to the list, otherwise add the entry 485 suffix := strings.TrimPrefix(entry.Path, prefix) 486 if index := strings.Index(suffix, "/"); index > -1 { 487 list.CommonPrefixes = append(list.CommonPrefixes, prefix+suffix[:index+1]) 488 return nil 489 } 490 if entry.Path == "" { 491 entry.Path = "/" 492 } 493 list.Entries = append(list.Entries, entry) 494 return nil 495 } 496 497 // if the manifest's path is a prefix of the specified prefix 498 // then just recurse into the manifest by returning nil and 499 // continuing the walk 500 if strings.HasPrefix(prefix, entry.Path) { 501 return nil 502 } 503 504 // if the manifest's path has the specified prefix, then if the 505 // path after the prefix contains a slash, add a common prefix 506 // to the list and skip the manifest, otherwise recurse into 507 // the manifest by returning nil and continuing the walk 508 if strings.HasPrefix(entry.Path, prefix) { 509 suffix := strings.TrimPrefix(entry.Path, prefix) 510 if index := strings.Index(suffix, "/"); index > -1 { 511 list.CommonPrefixes = append(list.CommonPrefixes, prefix+suffix[:index+1]) 512 return api.SkipManifest 513 } 514 return nil 515 } 516 517 // the manifest neither has the prefix or needs recursing in to 518 // so just skip it 519 return api.SkipManifest 520 }) 521 522 return list, nil 523 } 524 525 // HandleGetFile handles a GET request to bzz://<manifest>/<path> and responds 526 // with the content of the file at <path> from the given <manifest> 527 func (s *Server) HandleGetFile(w http.ResponseWriter, r *Request) { 528 // ensure the root path has a trailing slash so that relative URLs work 529 if r.uri.Path == "" && !strings.HasSuffix(r.URL.Path, "/") { 530 http.Redirect(w, &r.Request, r.URL.Path+"/", http.StatusMovedPermanently) 531 return 532 } 533 534 key, err := s.api.Resolve(r.uri) 535 if err != nil { 536 s.Error(w, r, fmt.Errorf("error resolving %s: %s", r.uri.Addr, err)) 537 return 538 } 539 540 reader, contentType, status, err := s.api.Get(key, r.uri.Path) 541 if err != nil { 542 switch status { 543 case http.StatusNotFound: 544 s.NotFound(w, r, err) 545 default: 546 s.Error(w, r, err) 547 } 548 return 549 } 550 551 //the request results in ambiguous files 552 //e.g. /read with readme.md and readinglist.txt available in manifest 553 if status == http.StatusMultipleChoices { 554 list, err := s.getManifestList(key, r.uri.Path) 555 556 if err != nil { 557 s.Error(w, r, err) 558 return 559 } 560 561 s.logDebug(fmt.Sprintf("Multiple choices! --> %v", list)) 562 //show a nice page links to available entries 563 ShowMultipleChoices(w, &r.Request, list) 564 return 565 } 566 567 // check the root chunk exists by retrieving the file's size 568 if _, err := reader.Size(nil); err != nil { 569 s.NotFound(w, r, fmt.Errorf("File not found %s: %s", r.uri, err)) 570 return 571 } 572 573 w.Header().Set("Content-Type", contentType) 574 575 http.ServeContent(w, &r.Request, "", time.Now(), reader) 576 } 577 578 func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { 579 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")) 580 581 uri, err := api.Parse(strings.TrimLeft(r.URL.Path, "/")) 582 req := &Request{Request: *r, uri: uri} 583 if err != nil { 584 s.logError("Invalid URI %q: %s", r.URL.Path, err) 585 s.BadRequest(w, req, fmt.Sprintf("Invalid URI %q: %s", r.URL.Path, err)) 586 return 587 } 588 s.logDebug("%s request received for %s", r.Method, uri) 589 590 switch r.Method { 591 case "POST": 592 if uri.Raw() { 593 s.HandlePostRaw(w, req) 594 } else { 595 s.HandlePostFiles(w, req) 596 } 597 598 case "PUT": 599 // DEPRECATED: 600 // clients should send a POST request (the request creates a 601 // new manifest leaving the existing one intact, so it isn't 602 // strictly a traditional PUT request which replaces content 603 // at a URI, and POST is more ubiquitous) 604 if uri.Raw() { 605 ShowError(w, r, fmt.Sprintf("No PUT to %s allowed.", uri), http.StatusBadRequest) 606 return 607 } else { 608 s.HandlePostFiles(w, req) 609 } 610 611 case "DELETE": 612 if uri.Raw() { 613 ShowError(w, r, fmt.Sprintf("No DELETE to %s allowed.", uri), http.StatusBadRequest) 614 return 615 } 616 s.HandleDelete(w, req) 617 618 case "GET": 619 if uri.Raw() { 620 s.HandleGetRaw(w, req) 621 return 622 } 623 624 if r.Header.Get("Accept") == "application/x-tar" { 625 s.HandleGetFiles(w, req) 626 return 627 } 628 629 if r.URL.Query().Get("list") == "true" { 630 s.HandleGetList(w, req) 631 return 632 } 633 634 s.HandleGetFile(w, req) 635 636 default: 637 ShowError(w, r, fmt.Sprintf("Method "+r.Method+" is not supported.", uri), http.StatusMethodNotAllowed) 638 639 } 640 } 641 642 func (s *Server) updateManifest(key storage.Key, update func(mw *api.ManifestWriter) error) (storage.Key, error) { 643 mw, err := s.api.NewManifestWriter(key, nil) 644 if err != nil { 645 return nil, err 646 } 647 648 if err := update(mw); err != nil { 649 return nil, err 650 } 651 652 key, err = mw.Store() 653 if err != nil { 654 return nil, err 655 } 656 s.logDebug("generated manifest %s", key) 657 return key, nil 658 } 659 660 func (s *Server) logDebug(format string, v ...interface{}) { 661 log.Debug(fmt.Sprintf("[BZZ] HTTP: "+format, v...)) 662 } 663 664 func (s *Server) logError(format string, v ...interface{}) { 665 log.Error(fmt.Sprintf("[BZZ] HTTP: "+format, v...)) 666 } 667 668 func (s *Server) BadRequest(w http.ResponseWriter, r *Request, reason string) { 669 ShowError(w, &r.Request, fmt.Sprintf("Bad request %s %s: %s", r.Method, r.uri, reason), http.StatusBadRequest) 670 } 671 672 func (s *Server) Error(w http.ResponseWriter, r *Request, err error) { 673 ShowError(w, &r.Request, fmt.Sprintf("Error serving %s %s: %s", r.Method, r.uri, err), http.StatusInternalServerError) 674 } 675 676 func (s *Server) NotFound(w http.ResponseWriter, r *Request, err error) { 677 ShowError(w, &r.Request, fmt.Sprintf("NOT FOUND error serving %s %s: %s", r.Method, r.uri, err), http.StatusNotFound) 678 }