Răsfoiți Sursa

swarm/api: fixed 404 handling on missing default entry (#15139)

holisticode 8 ani în urmă
părinte
comite
1ae0411d41

+ 7 - 3
swarm/api/api.go

@@ -144,9 +144,13 @@ func (self *Api) Get(key storage.Key, path string) (reader storage.LazySectionRe
 	if entry != nil {
 		key = common.Hex2Bytes(entry.Hash)
 		status = entry.Status
-		mimeType = entry.ContentType
-		log.Trace(fmt.Sprintf("content lookup key: '%v' (%v)", key, mimeType))
-		reader = self.dpa.Retrieve(key)
+		if status == http.StatusMultipleChoices {
+			return
+		} else {
+			mimeType = entry.ContentType
+			log.Trace(fmt.Sprintf("content lookup key: '%v' (%v)", key, mimeType))
+			reader = self.dpa.Retrieve(key)
+		}
 	} else {
 		status = http.StatusNotFound
 		err = fmt.Errorf("manifest entry for '%s' not found", path)

+ 42 - 4
swarm/api/http/error.go

@@ -25,9 +25,11 @@ import (
 	"fmt"
 	"html/template"
 	"net/http"
+	"strings"
 	"time"
 
 	"github.com/ethereum/go-ethereum/log"
+	"github.com/ethereum/go-ethereum/swarm/api"
 )
 
 //templateMap holds a mapping of an HTTP error code to a template
@@ -51,12 +53,14 @@ func initErrHandling() {
 	//pages are saved as strings - get these strings
 	genErrPage := GetGenericErrorPage()
 	notFoundPage := GetNotFoundErrorPage()
+	multipleChoicesPage := GetMultipleChoicesErrorPage()
 	//map the codes to the available pages
 	tnames := map[int]string{
-		0:   genErrPage, //default
-		400: genErrPage,
-		404: notFoundPage,
-		500: genErrPage,
+		0: genErrPage, //default
+		http.StatusBadRequest:          genErrPage,
+		http.StatusNotFound:            notFoundPage,
+		http.StatusMultipleChoices:     multipleChoicesPage,
+		http.StatusInternalServerError: genErrPage,
 	}
 	templateMap = make(map[int]*template.Template)
 	for code, tname := range tnames {
@@ -65,6 +69,40 @@ func initErrHandling() {
 	}
 }
 
+//ShowMultipeChoices is used when a user requests a resource in a manifest which results
+//in ambiguous results. It returns a HTML page with clickable links of each of the entry
+//in the manifest which fits the request URI ambiguity.
+//For example, if the user requests bzz:/<hash>/read and that manifest containes entries
+//"readme.md" and "readinglist.txt", a HTML page is returned with this two links.
+//This only applies if the manifest has no default entry
+func ShowMultipleChoices(w http.ResponseWriter, r *http.Request, list api.ManifestList) {
+	msg := ""
+	if list.Entries == nil {
+		ShowError(w, r, "Internal Server Error", http.StatusInternalServerError)
+		return
+	}
+	//make links relative
+	//requestURI comes with the prefix of the ambiguous path, e.g. "read" for "readme.md" and "readinglist.txt"
+	//to get clickable links, need to remove the ambiguous path, i.e. "read"
+	idx := strings.LastIndex(r.RequestURI, "/")
+	if idx == -1 {
+		ShowError(w, r, "Internal Server Error", http.StatusInternalServerError)
+		return
+	}
+	//remove ambiguous part
+	base := r.RequestURI[:idx+1]
+	for _, e := range list.Entries {
+		//create clickable link for each entry
+		msg += "<a href='" + base + e.Path + "'>" + e.Path + "</a><br/>"
+	}
+	respond(w, r, &ErrorParams{
+		Code:      http.StatusMultipleChoices,
+		Details:   template.HTML(msg),
+		Timestamp: time.Now().Format(time.RFC1123),
+		template:  getTemplate(http.StatusMultipleChoices),
+	})
+}
+
 //ShowError is used to show an HTML error page to a client.
 //If there is an `Accept` header of `application/json`, JSON will be returned instead
 //The function just takes a string message which will be displayed in the error page.

Fișier diff suprimat deoarece este prea mare
+ 152 - 2
swarm/api/http/error_templates.go


+ 43 - 23
swarm/api/http/server.go

@@ -441,14 +441,37 @@ func (s *Server) HandleGetList(w http.ResponseWriter, r *Request) {
 		return
 	}
 
-	walker, err := s.api.NewManifestWalker(key, nil)
+	list, err := s.getManifestList(key, r.uri.Path)
+
 	if err != nil {
 		s.Error(w, r, err)
 		return
 	}
 
-	var list api.ManifestList
-	prefix := r.uri.Path
+	// if the client wants HTML (e.g. a browser) then render the list as a
+	// HTML index with relative URLs
+	if strings.Contains(r.Header.Get("Accept"), "text/html") {
+		w.Header().Set("Content-Type", "text/html")
+		err := htmlListTemplate.Execute(w, &htmlListData{
+			URI:  r.uri,
+			List: &list,
+		})
+		if err != nil {
+			s.logError("error rendering list HTML: %s", err)
+		}
+		return
+	}
+
+	w.Header().Set("Content-Type", "application/json")
+	json.NewEncoder(w).Encode(&list)
+}
+
+func (s *Server) getManifestList(key storage.Key, prefix string) (list api.ManifestList, err error) {
+	walker, err := s.api.NewManifestWalker(key, nil)
+	if err != nil {
+		return
+	}
+
 	err = walker.Walk(func(entry *api.ManifestEntry) error {
 		// handle non-manifest files
 		if entry.ContentType != api.ManifestType {
@@ -495,27 +518,8 @@ func (s *Server) HandleGetList(w http.ResponseWriter, r *Request) {
 		// so just skip it
 		return api.SkipManifest
 	})
-	if err != nil {
-		s.Error(w, r, err)
-		return
-	}
 
-	// if the client wants HTML (e.g. a browser) then render the list as a
-	// HTML index with relative URLs
-	if strings.Contains(r.Header.Get("Accept"), "text/html") {
-		w.Header().Set("Content-Type", "text/html")
-		err := htmlListTemplate.Execute(w, &htmlListData{
-			URI:  r.uri,
-			List: &list,
-		})
-		if err != nil {
-			s.logError("error rendering list HTML: %s", err)
-		}
-		return
-	}
-
-	w.Header().Set("Content-Type", "application/json")
-	json.NewEncoder(w).Encode(&list)
+	return list, nil
 }
 
 // HandleGetFile handles a GET request to bzz://<manifest>/<path> and responds
@@ -544,6 +548,22 @@ func (s *Server) HandleGetFile(w http.ResponseWriter, r *Request) {
 		return
 	}
 
+	//the request results in ambiguous files
+	//e.g. /read with readme.md and readinglist.txt available in manifest
+	if status == http.StatusMultipleChoices {
+		list, err := s.getManifestList(key, r.uri.Path)
+
+		if err != nil {
+			s.Error(w, r, err)
+			return
+		}
+
+		s.logDebug(fmt.Sprintf("Multiple choices! -->  %v", list))
+		//show a nice page links to available entries
+		ShowMultipleChoices(w, &r.Request, list)
+		return
+	}
+
 	// check the root chunk exists by retrieving the file's size
 	if _, err := reader.Size(nil); err != nil {
 		s.NotFound(w, r, fmt.Errorf("File not found %s: %s", r.uri, err))

+ 28 - 4
swarm/api/manifest.go

@@ -22,6 +22,8 @@ import (
 	"errors"
 	"fmt"
 	"io"
+	"net/http"
+	"strings"
 	"sync"
 	"time"
 
@@ -422,25 +424,47 @@ func (self *manifestTrie) findPrefixOf(path string, quitC chan bool) (entry *man
 		return self.entries[256], 0
 	}
 
+	//see if first char is in manifest entries
 	b := byte(path[0])
 	entry = self.entries[b]
 	if entry == nil {
 		return self.entries[256], 0
 	}
+
 	epl := len(entry.Path)
 	log.Trace(fmt.Sprintf("path = %v  entry.Path = %v  epl = %v", path, entry.Path, epl))
-	if (len(path) >= epl) && (path[:epl] == entry.Path) {
+	if len(path) <= epl {
+		if entry.Path[:len(path)] == path {
+			if entry.ContentType == ManifestType {
+				entry.Status = http.StatusMultipleChoices
+			}
+			pos = len(path)
+			return
+		}
+		return nil, 0
+	}
+	if path[:epl] == entry.Path {
 		log.Trace(fmt.Sprintf("entry.ContentType = %v", entry.ContentType))
-		if entry.ContentType == ManifestType {
+		//the subentry is a manifest, load subtrie
+		if entry.ContentType == ManifestType && (strings.Contains(entry.Path, path) || strings.Contains(path, entry.Path)) {
 			err := self.loadSubTrie(entry, quitC)
 			if err != nil {
 				return nil, 0
 			}
-			entry, pos = entry.subtrie.findPrefixOf(path[epl:], quitC)
-			if entry != nil {
+			sub, pos := entry.subtrie.findPrefixOf(path[epl:], quitC)
+			if sub != nil {
+				entry = sub
 				pos += epl
+				return sub, pos
+			} else if path == entry.Path {
+				entry.Status = http.StatusMultipleChoices
 			}
+
 		} else {
+			//entry is not a manifest, return it
+			if path != entry.Path {
+				return nil, 0
+			}
 			pos = epl
 		}
 	}

+ 12 - 3
swarm/api/manifest_test.go

@@ -17,7 +17,6 @@
 package api
 
 import (
-	// "encoding/json"
 	"bytes"
 	"encoding/json"
 	"fmt"
@@ -72,11 +71,21 @@ func TestGetEntry(t *testing.T) {
 	testGetEntry(t, "/a", "", "")
 	testGetEntry(t, "/a/b", "a/b", "a/b")
 	// longest/deepest math
-	testGetEntry(t, "a/b", "-", "a", "a/ba", "a/b/c")
+	testGetEntry(t, "read", "read", "readme.md", "readit.md")
+	testGetEntry(t, "rf", "-", "readme.md", "readit.md")
+	testGetEntry(t, "readme", "readme", "readme.md")
+	testGetEntry(t, "readme", "-", "readit.md")
+	testGetEntry(t, "readme.md", "readme.md", "readme.md")
+	testGetEntry(t, "readme.md", "-", "readit.md")
+	testGetEntry(t, "readmeAmd", "-", "readit.md")
+	testGetEntry(t, "readme.mdffff", "-", "readme.md")
+	testGetEntry(t, "ab", "ab", "ab/cefg", "ab/cedh", "ab/kkkkkk")
+	testGetEntry(t, "ab/ce", "ab/ce", "ab/cefg", "ab/cedh", "ab/ceuuuuuuuuuu")
+	testGetEntry(t, "abc", "abc", "abcd", "abczzzzef", "abc/def", "abc/e/g")
+	testGetEntry(t, "a/b", "a/b", "a", "a/bc", "a/ba", "a/b/c")
 	testGetEntry(t, "a/b", "a/b", "a", "a/b", "a/bb", "a/b/c")
 	testGetEntry(t, "//a//b//", "a/b", "a", "a/b", "a/bb", "a/b/c")
 }
-
 func TestDeleteEntry(t *testing.T) {
 
 }

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff