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  }