mirror of https://github.com/status-im/op-geth.git
swarm/api: fixed 404 handling on missing default entry (#15139)
This commit is contained in:
parent
d54e3539d4
commit
1ae0411d41
|
@ -144,9 +144,13 @@ func (self *Api) Get(key storage.Key, path string) (reader storage.LazySectionRe
|
||||||
if entry != nil {
|
if entry != nil {
|
||||||
key = common.Hex2Bytes(entry.Hash)
|
key = common.Hex2Bytes(entry.Hash)
|
||||||
status = entry.Status
|
status = entry.Status
|
||||||
mimeType = entry.ContentType
|
if status == http.StatusMultipleChoices {
|
||||||
log.Trace(fmt.Sprintf("content lookup key: '%v' (%v)", key, mimeType))
|
return
|
||||||
reader = self.dpa.Retrieve(key)
|
} else {
|
||||||
|
mimeType = entry.ContentType
|
||||||
|
log.Trace(fmt.Sprintf("content lookup key: '%v' (%v)", key, mimeType))
|
||||||
|
reader = self.dpa.Retrieve(key)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
status = http.StatusNotFound
|
status = http.StatusNotFound
|
||||||
err = fmt.Errorf("manifest entry for '%s' not found", path)
|
err = fmt.Errorf("manifest entry for '%s' not found", path)
|
||||||
|
|
|
@ -25,9 +25,11 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"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
|
//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
|
//pages are saved as strings - get these strings
|
||||||
genErrPage := GetGenericErrorPage()
|
genErrPage := GetGenericErrorPage()
|
||||||
notFoundPage := GetNotFoundErrorPage()
|
notFoundPage := GetNotFoundErrorPage()
|
||||||
|
multipleChoicesPage := GetMultipleChoicesErrorPage()
|
||||||
//map the codes to the available pages
|
//map the codes to the available pages
|
||||||
tnames := map[int]string{
|
tnames := map[int]string{
|
||||||
0: genErrPage, //default
|
0: genErrPage, //default
|
||||||
400: genErrPage,
|
http.StatusBadRequest: genErrPage,
|
||||||
404: notFoundPage,
|
http.StatusNotFound: notFoundPage,
|
||||||
500: genErrPage,
|
http.StatusMultipleChoices: multipleChoicesPage,
|
||||||
|
http.StatusInternalServerError: genErrPage,
|
||||||
}
|
}
|
||||||
templateMap = make(map[int]*template.Template)
|
templateMap = make(map[int]*template.Template)
|
||||||
for code, tname := range tnames {
|
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.
|
//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
|
//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.
|
//The function just takes a string message which will be displayed in the error page.
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -441,14 +441,37 @@ func (s *Server) HandleGetList(w http.ResponseWriter, r *Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
walker, err := s.api.NewManifestWalker(key, nil)
|
list, err := s.getManifestList(key, r.uri.Path)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Error(w, r, err)
|
s.Error(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var list api.ManifestList
|
// if the client wants HTML (e.g. a browser) then render the list as a
|
||||||
prefix := r.uri.Path
|
// 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 {
|
err = walker.Walk(func(entry *api.ManifestEntry) error {
|
||||||
// handle non-manifest files
|
// handle non-manifest files
|
||||||
if entry.ContentType != api.ManifestType {
|
if entry.ContentType != api.ManifestType {
|
||||||
|
@ -495,27 +518,8 @@ func (s *Server) HandleGetList(w http.ResponseWriter, r *Request) {
|
||||||
// so just skip it
|
// so just skip it
|
||||||
return api.SkipManifest
|
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
|
return list, nil
|
||||||
// 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleGetFile handles a GET request to bzz://<manifest>/<path> and responds
|
// 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
|
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
|
// check the root chunk exists by retrieving the file's size
|
||||||
if _, err := reader.Size(nil); err != nil {
|
if _, err := reader.Size(nil); err != nil {
|
||||||
s.NotFound(w, r, fmt.Errorf("File not found %s: %s", r.uri, err))
|
s.NotFound(w, r, fmt.Errorf("File not found %s: %s", r.uri, err))
|
||||||
|
|
|
@ -22,6 +22,8 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -422,25 +424,47 @@ func (self *manifestTrie) findPrefixOf(path string, quitC chan bool) (entry *man
|
||||||
return self.entries[256], 0
|
return self.entries[256], 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//see if first char is in manifest entries
|
||||||
b := byte(path[0])
|
b := byte(path[0])
|
||||||
entry = self.entries[b]
|
entry = self.entries[b]
|
||||||
if entry == nil {
|
if entry == nil {
|
||||||
return self.entries[256], 0
|
return self.entries[256], 0
|
||||||
}
|
}
|
||||||
|
|
||||||
epl := len(entry.Path)
|
epl := len(entry.Path)
|
||||||
log.Trace(fmt.Sprintf("path = %v entry.Path = %v epl = %v", path, entry.Path, epl))
|
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))
|
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)
|
err := self.loadSubTrie(entry, quitC)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0
|
return nil, 0
|
||||||
}
|
}
|
||||||
entry, pos = entry.subtrie.findPrefixOf(path[epl:], quitC)
|
sub, pos := entry.subtrie.findPrefixOf(path[epl:], quitC)
|
||||||
if entry != nil {
|
if sub != nil {
|
||||||
|
entry = sub
|
||||||
pos += epl
|
pos += epl
|
||||||
|
return sub, pos
|
||||||
|
} else if path == entry.Path {
|
||||||
|
entry.Status = http.StatusMultipleChoices
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
//entry is not a manifest, return it
|
||||||
|
if path != entry.Path {
|
||||||
|
return nil, 0
|
||||||
|
}
|
||||||
pos = epl
|
pos = epl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// "encoding/json"
|
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -72,11 +71,21 @@ func TestGetEntry(t *testing.T) {
|
||||||
testGetEntry(t, "/a", "", "")
|
testGetEntry(t, "/a", "", "")
|
||||||
testGetEntry(t, "/a/b", "a/b", "a/b")
|
testGetEntry(t, "/a/b", "a/b", "a/b")
|
||||||
// longest/deepest math
|
// 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")
|
||||||
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) {
|
func TestDeleteEntry(t *testing.T) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue