diff --git a/swarm/api/http/server.go b/swarm/api/http/server.go index 65f6afab72..03a54307f8 100644 --- a/swarm/api/http/server.go +++ b/swarm/api/http/server.go @@ -293,10 +293,48 @@ func (s *Server) HandleDelete(w http.ResponseWriter, r *Request) { // HandleGetRaw handles a GET request to bzzr:// and responds with // the raw content stored at the given storage key func (s *Server) HandleGetRaw(w http.ResponseWriter, r *Request) { + _, reader, ok := s.handleGet(w, r) + if !ok { + return + } + + // allow the request to overwrite the content type using a query + // parameter + contentType := "application/octet-stream" + if typ := r.URL.Query().Get("content_type"); typ != "" { + contentType = typ + } + w.Header().Set("Content-Type", contentType) + + http.ServeContent(w, &r.Request, "", time.Now(), reader) +} + +// HandleGetHash handles a GET request to bzz:// with query parameter +// hash=true, and responds with the hash of the content stored +// at the given storage key as a application/bzz-hash response +func (s *Server) HandleGetHash(w http.ResponseWriter, r *Request) { + key, _, ok := s.handleGet(w, r) + if !ok { + return + } + + w.Header().Set("Content-Type", "application/bzz-hash") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, key) +} + +// handleGet is a handler that is used in HandleGetRaw and HandleGetHash methods +// to provide storage Key and LazySectionReader for the requested path. +// +// This method accepts http.ResponseWriter to respond errors and in case of +// errors, the third returned value will be false, indicating that the request +// is not valid, error is written to the response and nothing more should be +// written. +func (s *Server) handleGet(w http.ResponseWriter, r *Request) (key storage.Key, reader storage.LazySectionReader, ok bool) { key, err := s.api.Resolve(r.uri) if err != nil { s.Error(w, r, fmt.Errorf("error resolving %s: %s", r.uri.Addr, err)) - return + return nil, nil, false } // if path is set, interpret as a manifest and return the @@ -305,7 +343,7 @@ func (s *Server) HandleGetRaw(w http.ResponseWriter, r *Request) { walker, err := s.api.NewManifestWalker(key, nil) if err != nil { s.BadRequest(w, r, fmt.Sprintf("%s is not a manifest", key)) - return + return nil, nil, false } var entry *api.ManifestEntry walker.Walk(func(e *api.ManifestEntry) error { @@ -333,27 +371,18 @@ func (s *Server) HandleGetRaw(w http.ResponseWriter, r *Request) { }) if entry == nil { s.NotFound(w, r, fmt.Errorf("Manifest entry could not be loaded")) - return + return nil, nil, false } key = storage.Key(common.Hex2Bytes(entry.Hash)) } // check the root chunk exists by retrieving the file's size - reader := s.api.Retrieve(key) + reader = s.api.Retrieve(key) if _, err := reader.Size(nil); err != nil { s.NotFound(w, r, fmt.Errorf("Root chunk not found %s: %s", key, err)) return } - - // allow the request to overwrite the content type using a query - // parameter - contentType := "application/octet-stream" - if typ := r.URL.Query().Get("content_type"); typ != "" { - contentType = typ - } - w.Header().Set("Content-Type", contentType) - - http.ServeContent(w, &r.Request, "", time.Now(), reader) + return key, reader, true } // HandleGetFiles handles a GET request to bzz:/ with an Accept @@ -626,11 +655,24 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - if r.URL.Query().Get("list") == "true" { + getList := r.URL.Query().Get("list") == "true" + getHash := r.URL.Query().Get("swarm.hash") == "true" + + if getList && getHash { + s.BadRequest(w, req, "query parameters list and hash can not be requested at the same time") + return + } + + if getList { s.HandleGetList(w, req) return } + if getHash { + s.HandleGetHash(w, req) + return + } + s.HandleGetFile(w, req) default: diff --git a/swarm/api/http/server_test.go b/swarm/api/http/server_test.go index ffeaf6e0d8..5bca1b212a 100644 --- a/swarm/api/http/server_test.go +++ b/swarm/api/http/server_test.go @@ -104,19 +104,53 @@ func TestBzzrGetPath(t *testing.T) { } } - nonhashtests := []string{ + // test hash requests + for k, v := range testrequests { + var resp *http.Response + var respbody []byte + + url := srv.URL + "/bzz:/" + if k[:] != "" { + url += common.ToHex(key[0])[2:] + "/" + k[1:] + "?swarm.hash=true" + } + resp, err = http.Get(url) + if err != nil { + t.Fatalf("Request failed: %v", err) + } + defer resp.Body.Close() + respbody, err = ioutil.ReadAll(resp.Body) + + if string(respbody) != key[v].String() { + isexpectedfailrequest := false + + for _, r := range expectedfailrequests { + if k[:] == r { + isexpectedfailrequest = true + } + } + if !isexpectedfailrequest { + t.Fatalf("Response body does not match, expected: %v, got %v", key[v], string(respbody)) + } + } + } + + errorTests := []string{ srv.URL + "/bzz:/name", srv.URL + "/bzzi:/nonhash", srv.URL + "/bzzr:/nonhash", + srv.URL + "/bzz:/nonhash?swarm.hash=true", + srv.URL + "/bzz:/a?swarm.hash=true&list=true", } - nonhashresponses := []string{ + errorResponses := []string{ "error resolving name: no DNS to resolve name: "name"", "error resolving nonhash: immutable address not a content hash: "nonhash"", "error resolving nonhash: no DNS to resolve name: "nonhash"", + "error resolving nonhash: no DNS to resolve name: "nonhash"", + "query parameters list and hash can not be requested at the same time", } - for i, url := range nonhashtests { + for i, url := range errorTests { var resp *http.Response var respbody []byte @@ -130,8 +164,8 @@ func TestBzzrGetPath(t *testing.T) { if err != nil { t.Fatalf("ReadAll failed: %v", err) } - if !strings.Contains(string(respbody), nonhashresponses[i]) { - t.Fatalf("Non-Hash response body does not match, expected: %v, got: %v", nonhashresponses[i], string(respbody)) + if !strings.Contains(string(respbody), errorResponses[i]) { + t.Fatalf("Non-Hash response body does not match, expected: %v, got: %v", errorResponses[i], string(respbody)) } }