From 6928ca2324391fd96cfbe8766f40067cba8821f6 Mon Sep 17 00:00:00 2001 From: Janos Guljas Date: Fri, 1 Dec 2017 10:27:17 +0100 Subject: [PATCH 1/7] swarm/api: url scheme bzzh for getting hashes of swarm content (#15238) Update URI to support bzzh scheme and handle such HTTP requests by responding with hash of the content as a text/plain response. --- swarm/api/http/server.go | 36 +++++++++++++++++++++++------------- swarm/api/uri.go | 8 +++++++- swarm/api/uri_test.go | 14 ++++++++++++++ 3 files changed, 44 insertions(+), 14 deletions(-) diff --git a/swarm/api/http/server.go b/swarm/api/http/server.go index 65f6afab72..0b685b0534 100644 --- a/swarm/api/http/server.go +++ b/swarm/api/http/server.go @@ -290,9 +290,12 @@ func (s *Server) HandleDelete(w http.ResponseWriter, r *Request) { fmt.Fprint(w, newKey) } -// 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) { +// HandleGet handles a GET request to +// - bzzr:// and responds with the raw content stored at the +// given storage key +// - bzzh:// and responds with the hash of the content stored +// at the given storage key as a text/plain response +func (s *Server) HandleGet(w http.ResponseWriter, r *Request) { key, err := s.api.Resolve(r.uri) if err != nil { s.Error(w, r, fmt.Errorf("error resolving %s: %s", r.uri.Addr, err)) @@ -345,15 +348,22 @@ func (s *Server) HandleGetRaw(w http.ResponseWriter, r *Request) { 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) + switch { + case r.uri.Raw(): + // 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) + http.ServeContent(w, &r.Request, "", time.Now(), reader) + case r.uri.Hash(): + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, key) + } } // HandleGetFiles handles a GET request to bzz:/ with an Accept @@ -616,8 +626,8 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { s.HandleDelete(w, req) case "GET": - if uri.Raw() { - s.HandleGetRaw(w, req) + if uri.Raw() || uri.Hash() { + s.HandleGet(w, req) return } diff --git a/swarm/api/uri.go b/swarm/api/uri.go index caed4212d5..117ecf5d25 100644 --- a/swarm/api/uri.go +++ b/swarm/api/uri.go @@ -30,6 +30,8 @@ type URI struct { // * bzzr - raw swarm content // * bzzi - immutable URI of an entry in a swarm manifest // (address is not resolved) + // * bzzh - hash of swarm content + // Scheme string // Addr is either a hexadecimal storage key or it an address which @@ -60,7 +62,7 @@ func Parse(rawuri string) (*URI, error) { // check the scheme is valid switch uri.Scheme { - case "bzz", "bzzi", "bzzr": + case "bzz", "bzzi", "bzzr", "bzzh": default: return nil, fmt.Errorf("unknown scheme %q", u.Scheme) } @@ -91,6 +93,10 @@ func (u *URI) Immutable() bool { return u.Scheme == "bzzi" } +func (u *URI) Hash() bool { + return u.Scheme == "bzzh" +} + func (u *URI) String() string { return u.Scheme + ":/" + u.Addr + "/" + u.Path } diff --git a/swarm/api/uri_test.go b/swarm/api/uri_test.go index 7d4160601d..b858bd7d58 100644 --- a/swarm/api/uri_test.go +++ b/swarm/api/uri_test.go @@ -28,6 +28,7 @@ func TestParseURI(t *testing.T) { expectErr bool expectRaw bool expectImmutable bool + expectHash bool } tests := []test{ { @@ -95,6 +96,16 @@ func TestParseURI(t *testing.T) { uri: "bzz://abc123/path/to/entry", expectURI: &URI{Scheme: "bzz", Addr: "abc123", Path: "path/to/entry"}, }, + { + uri: "bzzh:", + expectURI: &URI{Scheme: "bzzh"}, + expectHash: true, + }, + { + uri: "bzzh:/", + expectURI: &URI{Scheme: "bzzh"}, + expectHash: true, + }, } for _, x := range tests { actual, err := Parse(x.uri) @@ -116,5 +127,8 @@ func TestParseURI(t *testing.T) { if actual.Immutable() != x.expectImmutable { t.Fatalf("expected %s immutable to be %t, got %t", x.uri, x.expectImmutable, actual.Immutable()) } + if actual.Hash() != x.expectHash { + t.Fatalf("expected %s hash to be %t, got %t", x.uri, x.expectHash, actual.Hash()) + } } } From 745e593028b5bc2e3114603f3b2a94064e267743 Mon Sep 17 00:00:00 2001 From: Janos Guljas Date: Fri, 1 Dec 2017 10:28:08 +0100 Subject: [PATCH 2/7] swarm/api: add tests for bzzh get path --- swarm/api/http/server_test.go | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/swarm/api/http/server_test.go b/swarm/api/http/server_test.go index ffeaf6e0d8..c60ed611c8 100644 --- a/swarm/api/http/server_test.go +++ b/swarm/api/http/server_test.go @@ -33,7 +33,7 @@ import ( "github.com/ethereum/go-ethereum/swarm/testutil" ) -func TestBzzrGetPath(t *testing.T) { +func TestBzzGetPath(t *testing.T) { var err error @@ -104,16 +104,47 @@ func TestBzzrGetPath(t *testing.T) { } } + for k, v := range testrequests { + var resp *http.Response + var respbody []byte + + url := srv.URL + "/bzzh:/" + if k[:] != "" { + url += common.ToHex(key[0])[2:] + "/" + k[1:] + } + 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)) + } + } + } + nonhashtests := []string{ srv.URL + "/bzz:/name", srv.URL + "/bzzi:/nonhash", srv.URL + "/bzzr:/nonhash", + srv.URL + "/bzzh:/nonhash", } nonhashresponses := []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"", } for i, url := range nonhashtests { From d82ecbac22c4ffdbf29193d03fde87776886e84e Mon Sep 17 00:00:00 2001 From: Janos Guljas Date: Mon, 4 Dec 2017 11:23:49 +0100 Subject: [PATCH 3/7] Revert bzzh related commits Revert "swarm/api: add tests for bzzh get path" Revert "swarm/api: url scheme bzzh for getting hashes of swarm content (#15238)" --- swarm/api/http/server.go | 36 +++++++++++++---------------------- swarm/api/http/server_test.go | 33 +------------------------------- swarm/api/uri.go | 8 +------- swarm/api/uri_test.go | 14 -------------- 4 files changed, 15 insertions(+), 76 deletions(-) diff --git a/swarm/api/http/server.go b/swarm/api/http/server.go index 0b685b0534..65f6afab72 100644 --- a/swarm/api/http/server.go +++ b/swarm/api/http/server.go @@ -290,12 +290,9 @@ func (s *Server) HandleDelete(w http.ResponseWriter, r *Request) { fmt.Fprint(w, newKey) } -// HandleGet handles a GET request to -// - bzzr:// and responds with the raw content stored at the -// given storage key -// - bzzh:// and responds with the hash of the content stored -// at the given storage key as a text/plain response -func (s *Server) HandleGet(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) { key, err := s.api.Resolve(r.uri) if err != nil { s.Error(w, r, fmt.Errorf("error resolving %s: %s", r.uri.Addr, err)) @@ -348,22 +345,15 @@ func (s *Server) HandleGet(w http.ResponseWriter, r *Request) { return } - switch { - case r.uri.Raw(): - // 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) - case r.uri.Hash(): - w.Header().Set("Content-Type", "text/plain") - w.WriteHeader(http.StatusOK) - fmt.Fprint(w, key) + // 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) } // HandleGetFiles handles a GET request to bzz:/ with an Accept @@ -626,8 +616,8 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { s.HandleDelete(w, req) case "GET": - if uri.Raw() || uri.Hash() { - s.HandleGet(w, req) + if uri.Raw() { + s.HandleGetRaw(w, req) return } diff --git a/swarm/api/http/server_test.go b/swarm/api/http/server_test.go index c60ed611c8..ffeaf6e0d8 100644 --- a/swarm/api/http/server_test.go +++ b/swarm/api/http/server_test.go @@ -33,7 +33,7 @@ import ( "github.com/ethereum/go-ethereum/swarm/testutil" ) -func TestBzzGetPath(t *testing.T) { +func TestBzzrGetPath(t *testing.T) { var err error @@ -104,47 +104,16 @@ func TestBzzGetPath(t *testing.T) { } } - for k, v := range testrequests { - var resp *http.Response - var respbody []byte - - url := srv.URL + "/bzzh:/" - if k[:] != "" { - url += common.ToHex(key[0])[2:] + "/" + k[1:] - } - 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)) - } - } - } - nonhashtests := []string{ srv.URL + "/bzz:/name", srv.URL + "/bzzi:/nonhash", srv.URL + "/bzzr:/nonhash", - srv.URL + "/bzzh:/nonhash", } nonhashresponses := []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"", } for i, url := range nonhashtests { diff --git a/swarm/api/uri.go b/swarm/api/uri.go index 117ecf5d25..caed4212d5 100644 --- a/swarm/api/uri.go +++ b/swarm/api/uri.go @@ -30,8 +30,6 @@ type URI struct { // * bzzr - raw swarm content // * bzzi - immutable URI of an entry in a swarm manifest // (address is not resolved) - // * bzzh - hash of swarm content - // Scheme string // Addr is either a hexadecimal storage key or it an address which @@ -62,7 +60,7 @@ func Parse(rawuri string) (*URI, error) { // check the scheme is valid switch uri.Scheme { - case "bzz", "bzzi", "bzzr", "bzzh": + case "bzz", "bzzi", "bzzr": default: return nil, fmt.Errorf("unknown scheme %q", u.Scheme) } @@ -93,10 +91,6 @@ func (u *URI) Immutable() bool { return u.Scheme == "bzzi" } -func (u *URI) Hash() bool { - return u.Scheme == "bzzh" -} - func (u *URI) String() string { return u.Scheme + ":/" + u.Addr + "/" + u.Path } diff --git a/swarm/api/uri_test.go b/swarm/api/uri_test.go index b858bd7d58..7d4160601d 100644 --- a/swarm/api/uri_test.go +++ b/swarm/api/uri_test.go @@ -28,7 +28,6 @@ func TestParseURI(t *testing.T) { expectErr bool expectRaw bool expectImmutable bool - expectHash bool } tests := []test{ { @@ -96,16 +95,6 @@ func TestParseURI(t *testing.T) { uri: "bzz://abc123/path/to/entry", expectURI: &URI{Scheme: "bzz", Addr: "abc123", Path: "path/to/entry"}, }, - { - uri: "bzzh:", - expectURI: &URI{Scheme: "bzzh"}, - expectHash: true, - }, - { - uri: "bzzh:/", - expectURI: &URI{Scheme: "bzzh"}, - expectHash: true, - }, } for _, x := range tests { actual, err := Parse(x.uri) @@ -127,8 +116,5 @@ func TestParseURI(t *testing.T) { if actual.Immutable() != x.expectImmutable { t.Fatalf("expected %s immutable to be %t, got %t", x.uri, x.expectImmutable, actual.Immutable()) } - if actual.Hash() != x.expectHash { - t.Fatalf("expected %s hash to be %t, got %t", x.uri, x.expectHash, actual.Hash()) - } } } From e9f38d726eebd059540b78c6899f1cfe1b18046f Mon Sep 17 00:00:00 2001 From: Janos Guljas Date: Mon, 4 Dec 2017 11:27:24 +0100 Subject: [PATCH 4/7] swarm/api: get hash of swarm content with swarm.hash=true query param (#15238) The response to request bzz request with swarm.hash=true query parameter will return the hash of the content that will be otherwise returned without the query parameter. Response Content-Type is application/bzz-hash. All possible error responses are the same with or without the query parameter. --- swarm/api/http/server.go | 72 +++++++++++++++++++++++++++-------- swarm/api/http/server_test.go | 44 ++++++++++++++++++--- 2 files changed, 96 insertions(+), 20 deletions(-) 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)) } } From b18003d99a382ead6d165ceb0419ba8329ea7e66 Mon Sep 17 00:00:00 2001 From: Janos Guljas Date: Tue, 12 Dec 2017 10:56:57 +0100 Subject: [PATCH 5/7] Revert swarm.hash query parameter related changes Revert changes to the original bzzh:// schema solution. --- swarm/api/http/server.go | 92 ++++++++++++----------------------- swarm/api/http/server_test.go | 21 ++++---- swarm/api/uri.go | 8 ++- swarm/api/uri_test.go | 14 ++++++ 4 files changed, 60 insertions(+), 75 deletions(-) diff --git a/swarm/api/http/server.go b/swarm/api/http/server.go index 03a54307f8..0b685b0534 100644 --- a/swarm/api/http/server.go +++ b/swarm/api/http/server.go @@ -290,51 +290,16 @@ func (s *Server) HandleDelete(w http.ResponseWriter, r *Request) { fmt.Fprint(w, newKey) } -// 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) { +// HandleGet handles a GET request to +// - bzzr:// and responds with the raw content stored at the +// given storage key +// - bzzh:// and responds with the hash of the content stored +// at the given storage key as a text/plain response +func (s *Server) HandleGet(w http.ResponseWriter, r *Request) { 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 nil, nil, false + return } // if path is set, interpret as a manifest and return the @@ -343,7 +308,7 @@ func (s *Server) handleGet(w http.ResponseWriter, r *Request) (key storage.Key, walker, err := s.api.NewManifestWalker(key, nil) if err != nil { s.BadRequest(w, r, fmt.Sprintf("%s is not a manifest", key)) - return nil, nil, false + return } var entry *api.ManifestEntry walker.Walk(func(e *api.ManifestEntry) error { @@ -371,18 +336,34 @@ func (s *Server) handleGet(w http.ResponseWriter, r *Request) (key storage.Key, }) if entry == nil { s.NotFound(w, r, fmt.Errorf("Manifest entry could not be loaded")) - return nil, nil, false + return } 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 } - return key, reader, true + + switch { + case r.uri.Raw(): + // 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) + case r.uri.Hash(): + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, key) + } } // HandleGetFiles handles a GET request to bzz:/ with an Accept @@ -645,8 +626,8 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { s.HandleDelete(w, req) case "GET": - if uri.Raw() { - s.HandleGetRaw(w, req) + if uri.Raw() || uri.Hash() { + s.HandleGet(w, req) return } @@ -655,24 +636,11 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - 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 { + if r.URL.Query().Get("list") == "true" { 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 5bca1b212a..c60ed611c8 100644 --- a/swarm/api/http/server_test.go +++ b/swarm/api/http/server_test.go @@ -33,7 +33,7 @@ import ( "github.com/ethereum/go-ethereum/swarm/testutil" ) -func TestBzzrGetPath(t *testing.T) { +func TestBzzGetPath(t *testing.T) { var err error @@ -104,14 +104,13 @@ func TestBzzrGetPath(t *testing.T) { } } - // test hash requests for k, v := range testrequests { var resp *http.Response var respbody []byte - url := srv.URL + "/bzz:/" + url := srv.URL + "/bzzh:/" if k[:] != "" { - url += common.ToHex(key[0])[2:] + "/" + k[1:] + "?swarm.hash=true" + url += common.ToHex(key[0])[2:] + "/" + k[1:] } resp, err = http.Get(url) if err != nil { @@ -134,23 +133,21 @@ func TestBzzrGetPath(t *testing.T) { } } - errorTests := []string{ + nonhashtests := []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", + srv.URL + "/bzzh:/nonhash", } - errorResponses := []string{ + nonhashresponses := []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 errorTests { + for i, url := range nonhashtests { var resp *http.Response var respbody []byte @@ -164,8 +161,8 @@ func TestBzzrGetPath(t *testing.T) { if err != nil { t.Fatalf("ReadAll failed: %v", err) } - if !strings.Contains(string(respbody), errorResponses[i]) { - t.Fatalf("Non-Hash response body does not match, expected: %v, got: %v", errorResponses[i], string(respbody)) + if !strings.Contains(string(respbody), nonhashresponses[i]) { + t.Fatalf("Non-Hash response body does not match, expected: %v, got: %v", nonhashresponses[i], string(respbody)) } } diff --git a/swarm/api/uri.go b/swarm/api/uri.go index caed4212d5..117ecf5d25 100644 --- a/swarm/api/uri.go +++ b/swarm/api/uri.go @@ -30,6 +30,8 @@ type URI struct { // * bzzr - raw swarm content // * bzzi - immutable URI of an entry in a swarm manifest // (address is not resolved) + // * bzzh - hash of swarm content + // Scheme string // Addr is either a hexadecimal storage key or it an address which @@ -60,7 +62,7 @@ func Parse(rawuri string) (*URI, error) { // check the scheme is valid switch uri.Scheme { - case "bzz", "bzzi", "bzzr": + case "bzz", "bzzi", "bzzr", "bzzh": default: return nil, fmt.Errorf("unknown scheme %q", u.Scheme) } @@ -91,6 +93,10 @@ func (u *URI) Immutable() bool { return u.Scheme == "bzzi" } +func (u *URI) Hash() bool { + return u.Scheme == "bzzh" +} + func (u *URI) String() string { return u.Scheme + ":/" + u.Addr + "/" + u.Path } diff --git a/swarm/api/uri_test.go b/swarm/api/uri_test.go index 7d4160601d..b858bd7d58 100644 --- a/swarm/api/uri_test.go +++ b/swarm/api/uri_test.go @@ -28,6 +28,7 @@ func TestParseURI(t *testing.T) { expectErr bool expectRaw bool expectImmutable bool + expectHash bool } tests := []test{ { @@ -95,6 +96,16 @@ func TestParseURI(t *testing.T) { uri: "bzz://abc123/path/to/entry", expectURI: &URI{Scheme: "bzz", Addr: "abc123", Path: "path/to/entry"}, }, + { + uri: "bzzh:", + expectURI: &URI{Scheme: "bzzh"}, + expectHash: true, + }, + { + uri: "bzzh:/", + expectURI: &URI{Scheme: "bzzh"}, + expectHash: true, + }, } for _, x := range tests { actual, err := Parse(x.uri) @@ -116,5 +127,8 @@ func TestParseURI(t *testing.T) { if actual.Immutable() != x.expectImmutable { t.Fatalf("expected %s immutable to be %t, got %t", x.uri, x.expectImmutable, actual.Immutable()) } + if actual.Hash() != x.expectHash { + t.Fatalf("expected %s hash to be %t, got %t", x.uri, x.expectHash, actual.Hash()) + } } } From 379040e22d31e8a23a4b568e3e194b1a741a21f2 Mon Sep 17 00:00:00 2001 From: Janos Guljas Date: Tue, 12 Dec 2017 11:01:16 +0100 Subject: [PATCH 6/7] Update swarm/api.Parse comment to include bzzh scheme --- swarm/api/uri.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/swarm/api/uri.go b/swarm/api/uri.go index 117ecf5d25..d6951076e7 100644 --- a/swarm/api/uri.go +++ b/swarm/api/uri.go @@ -52,7 +52,7 @@ type URI struct { // * :// // * :/// // -// with scheme one of bzz, bzzr or bzzi +// with scheme one of bzz, bzzr, bzzi or bzzh func Parse(rawuri string) (*URI, error) { u, err := url.Parse(rawuri) if err != nil { From 75c8a1835da1c977d175a0f9a35aa93fcb6c07c9 Mon Sep 17 00:00:00 2001 From: Janos Guljas Date: Wed, 13 Dec 2017 12:02:15 +0100 Subject: [PATCH 7/7] swarm/api: rename bzzh URL scheme to bzz-hash --- swarm/api/http/server.go | 2 +- swarm/api/http/server_test.go | 4 ++-- swarm/api/uri.go | 8 ++++---- swarm/api/uri_test.go | 8 ++++---- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/swarm/api/http/server.go b/swarm/api/http/server.go index 0b685b0534..86450ed6f6 100644 --- a/swarm/api/http/server.go +++ b/swarm/api/http/server.go @@ -293,7 +293,7 @@ func (s *Server) HandleDelete(w http.ResponseWriter, r *Request) { // HandleGet handles a GET request to // - bzzr:// and responds with the raw content stored at the // given storage key -// - bzzh:// and responds with the hash of the content stored +// - bzz-hash:// and responds with the hash of the content stored // at the given storage key as a text/plain response func (s *Server) HandleGet(w http.ResponseWriter, r *Request) { key, err := s.api.Resolve(r.uri) diff --git a/swarm/api/http/server_test.go b/swarm/api/http/server_test.go index c60ed611c8..9acfd40979 100644 --- a/swarm/api/http/server_test.go +++ b/swarm/api/http/server_test.go @@ -108,7 +108,7 @@ func TestBzzGetPath(t *testing.T) { var resp *http.Response var respbody []byte - url := srv.URL + "/bzzh:/" + url := srv.URL + "/bzz-hash:/" if k[:] != "" { url += common.ToHex(key[0])[2:] + "/" + k[1:] } @@ -137,7 +137,7 @@ func TestBzzGetPath(t *testing.T) { srv.URL + "/bzz:/name", srv.URL + "/bzzi:/nonhash", srv.URL + "/bzzr:/nonhash", - srv.URL + "/bzzh:/nonhash", + srv.URL + "/bzz-hash:/nonhash", } nonhashresponses := []string{ diff --git a/swarm/api/uri.go b/swarm/api/uri.go index d6951076e7..c28faf7c77 100644 --- a/swarm/api/uri.go +++ b/swarm/api/uri.go @@ -30,7 +30,7 @@ type URI struct { // * bzzr - raw swarm content // * bzzi - immutable URI of an entry in a swarm manifest // (address is not resolved) - // * bzzh - hash of swarm content + // * bzz-hash - hash of swarm content // Scheme string @@ -52,7 +52,7 @@ type URI struct { // * :// // * :/// // -// with scheme one of bzz, bzzr, bzzi or bzzh +// with scheme one of bzz, bzzr, bzzi or bzz-hash func Parse(rawuri string) (*URI, error) { u, err := url.Parse(rawuri) if err != nil { @@ -62,7 +62,7 @@ func Parse(rawuri string) (*URI, error) { // check the scheme is valid switch uri.Scheme { - case "bzz", "bzzi", "bzzr", "bzzh": + case "bzz", "bzzi", "bzzr", "bzz-hash": default: return nil, fmt.Errorf("unknown scheme %q", u.Scheme) } @@ -94,7 +94,7 @@ func (u *URI) Immutable() bool { } func (u *URI) Hash() bool { - return u.Scheme == "bzzh" + return u.Scheme == "bzz-hash" } func (u *URI) String() string { diff --git a/swarm/api/uri_test.go b/swarm/api/uri_test.go index b858bd7d58..0859e78fe8 100644 --- a/swarm/api/uri_test.go +++ b/swarm/api/uri_test.go @@ -97,13 +97,13 @@ func TestParseURI(t *testing.T) { expectURI: &URI{Scheme: "bzz", Addr: "abc123", Path: "path/to/entry"}, }, { - uri: "bzzh:", - expectURI: &URI{Scheme: "bzzh"}, + uri: "bzz-hash:", + expectURI: &URI{Scheme: "bzz-hash"}, expectHash: true, }, { - uri: "bzzh:/", - expectURI: &URI{Scheme: "bzzh"}, + uri: "bzz-hash:/", + expectURI: &URI{Scheme: "bzz-hash"}, expectHash: true, }, }