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  }