From 021182f822858361f956383976cbddff4c700250 Mon Sep 17 00:00:00 2001 From: zelig Date: Sat, 30 May 2015 11:06:45 +0100 Subject: [PATCH] http access: fix regression and improve - correct slash handling and path matching in manifest - fix subpath match and use deepest path instead of longest for matching - http POST: give key as http response instead of fmt.Printf - improved logging --- bzz/api.go | 18 +++++++++--------- bzz/httpaccess.go | 29 +++++++++++++++++------------ bzz/manifest.go | 37 +++++++++++++++++++++++++++---------- 3 files changed, 53 insertions(+), 31 deletions(-) diff --git a/bzz/api.go b/bzz/api.go index 38fcf6933e..958795beac 100644 --- a/bzz/api.go +++ b/bzz/api.go @@ -300,35 +300,35 @@ func (self *Api) getPath(uri string) (reader SectionReader, mimeType string, sta var size int size, err = manifestReader.Read(manifestData) if int64(size) < manifestReader.Size() { - dpaLogger.Debugf("Swarm: Manifest for '%s' not found.", uri) + dpaLogger.Debugf("Swarm: Manifest for '%s' not found (uri: '%s')", path[:pos], uri) if err == nil { err = fmt.Errorf("Manifest retrieval cut short: %v < %v", size, manifestReader.Size()) } return } - dpaLogger.Debugf("Swarm: Manifest for '%s' retrieved", uri) + dpaLogger.Debugf("Swarm: Manifest for '%s' retrieved", path[:pos]) man := manifest{} err = json.Unmarshal(manifestData, &man) if err != nil { - err = fmt.Errorf("Manifest for '%s' is malformed: %v", uri, err) + err = fmt.Errorf("Manifest for '%s' is malformed: %v", path[:pos], err) dpaLogger.Debugf("Swarm: %v", err) return } - dpaLogger.Debugf("Swarm: Manifest for '%s' has %d entries. Retrieving entry for '%s'", uri, len(man.Entries), path) + dpaLogger.Debugf("Swarm: Manifest for '%s' has %d entries. Match path '%s'", path[:pos], len(man.Entries), path[pos:]) // retrieve entry that matches path from manifest entries - var entry *manifestEntry - entry, pos = man.getEntry(path) + entry, hop := man.getEntry(path[pos:]) + if entry == nil { - err = fmt.Errorf("Content for '%s' not found.", uri) + err = fmt.Errorf("Path '%s' not found on manifest '%s'", path[:pos], path[pos:]) return } // check hash of entry if !hashMatcher.MatchString(entry.Hash) { - err = fmt.Errorf("Incorrect hash '%064x' for '%s'", entry.Hash, uri) + err = fmt.Errorf("Incorrect hash '%064x' for path '%s' on manifest for '%s')", entry.Hash, path[pos:], path[:pos]) return } key = common.Hex2Bytes(entry.Hash) @@ -349,7 +349,7 @@ func (self *Api) getPath(uri string) (reader SectionReader, mimeType string, sta } // otherwise continue along the path with manifest resolution - path = path[pos:] + pos += hop } return } diff --git a/bzz/httpaccess.go b/bzz/httpaccess.go index 2f95a2db60..8707baee27 100644 --- a/bzz/httpaccess.go +++ b/bzz/httpaccess.go @@ -4,22 +4,24 @@ A simple http server interface to Swarm package bzz import ( - "fmt" + "bytes" "io" "net/http" "regexp" "sync" "time" + + "github.com/ethereum/go-ethereum/common" ) const ( - notFoundStatus = 404 - rawType = "application/octet-stream" + rawType = "application/octet-stream" ) var ( rawUrl = regexp.MustCompile("^/+raw/*") trailingSlashes = regexp.MustCompile("/+$") + forever = time.Unix(0, 0) ) type sequentialReader struct { @@ -40,8 +42,9 @@ func startHttpServer(api *Api, port string) { } func handler(w http.ResponseWriter, r *http.Request, api *Api) { - dpaLogger.Debugf("request URL: '%s' Host: '%s', Path: '%s'", r.RequestURI, r.URL.Host, r.URL.Path) - uri := r.URL.Path + requestURL := r.URL + dpaLogger.Debugf("Swarm: HTTP request URL: '%s', Host: '%s', Path: '%s', Referer: '%s', Accept: '%s'", r.RequestURI, requestURL.Host, requestURL.Path, r.Referer(), r.Header.Get("Accept")) + uri := requestURL.Path var raw bool path := rawUrl.ReplaceAllStringFunc(uri, func(string) string { raw = true @@ -50,7 +53,6 @@ func handler(w http.ResponseWriter, r *http.Request, api *Api) { switch { case r.Method == "POST": - dpaLogger.Debugf("request URL Host: '%s', Path: '%s'", r.URL.Host, r.URL.Path) if raw { dpaLogger.Debugf("Swarm: POST request received.") key, err := api.dpa.Store(io.NewSectionReader(&sequentialReader{ @@ -58,7 +60,8 @@ func handler(w http.ResponseWriter, r *http.Request, api *Api) { ahead: make(map[int64]chan bool), }, 0, r.ContentLength), nil) if err == nil { - fmt.Fprintf(w, "%064x", key) + w.Header().Set("Content-Type", "text/plain") + http.ServeContent(w, r, "", time.Now(), bytes.NewReader([]byte(common.Bytes2Hex(key)))) dpaLogger.Debugf("Swarm: Content for '%064x' stored", key) } else { http.Error(w, err.Error(), http.StatusBadRequest) @@ -70,7 +73,6 @@ func handler(w http.ResponseWriter, r *http.Request, api *Api) { } case r.Method == "GET" || r.Method == "HEAD": - dpaLogger.Debugf("request URL Host: '%s', Path: '%s'", r.URL.Host, uri) path = trailingSlashes.ReplaceAllString(path, "") if raw { dpaLogger.Debugf("Swarm: Raw GET request '%s' received", uri) @@ -88,14 +90,14 @@ func handler(w http.ResponseWriter, r *http.Request, api *Api) { dpaLogger.Debugf("Swarm: Reading %d bytes.", reader.Size()) // setting mime type - qv := r.URL.Query() + qv := requestURL.Query() mimeType := qv.Get("content_type") if mimeType == "" { mimeType = rawType } w.Header().Set("Content-Type", mimeType) - http.ServeContent(w, r, uri, time.Unix(0, 0), reader) + http.ServeContent(w, r, uri, forever, reader) dpaLogger.Debugf("Swarm: Serve raw content '%s' (%d bytes) as '%s'", uri, reader.Size(), mimeType) // retrieve path via manifest @@ -121,9 +123,12 @@ func handler(w http.ResponseWriter, r *http.Request, api *Api) { w.Header().Set("Content-Type", mimeType) if status > 0 { w.WriteHeader(status) + } else { + status = 200 } - dpaLogger.Debugf("Swarm: Served '%s' (%d bytes) as '%s' (status code: %v)", uri, reader.Size(), mimeType, w.Header()) - http.ServeContent(w, r, uri, time.Unix(0, 0), reader) + dpaLogger.Debugf("Swarm: Served '%s' (%d bytes) as '%s' (status code: %v)", uri, reader.Size(), mimeType, status) + + http.ServeContent(w, r, path, forever, reader) } default: diff --git a/bzz/manifest.go b/bzz/manifest.go index 47b9d99ebb..09c646bfe3 100644 --- a/bzz/manifest.go +++ b/bzz/manifest.go @@ -24,24 +24,41 @@ type manifestEntry struct { Status int `json:"status"` } -func (self *manifest) getEntry(path string) (entry *manifestEntry, pos int) { +// path must not have any leading slashes +func (self *manifest) getEntry(path string) (matchingEntry *manifestEntry, matchlength int) { + var pos, depth, maxdepth int + var entry *manifestEntry + // path := leadingSlashes.ReplaceAllString(fullpath, "") + // iterate over entries matching paths to the target + // due to redundant slashes, it is NOT the longest match but the match with + // the highest depth is chosen + // this gives thse matches in case of trailing slashes: + // "a/" -> "a/" not "a" and "a" matches "a" not "a/" if both exist + // "a" never matches "a/" but "a/" matches a for _, entry = range self.Entries { entryPath := leadingSlashes.ReplaceAllString(entry.Path, "") pos = len(entryPath) - if len(path) >= pos && path[:pos] == entryPath { - var n int + depth = len(slashes.Split(entryPath, -1)) + if len(path) >= pos && path[:pos] == entryPath && (depth > maxdepth || depth == maxdepth && pos > matchlength) { + var hop int + // hop and chop leading hashes of the continuation if len(path) > pos { chopped := leadingSlashes.ReplaceAllString(path[pos:], "") - n = len(path) - pos - len(chopped) + hop = len(path) - pos - len(chopped) } - if n > 0 || pos == 0 || path[pos-1] == '/' { - pos += n - dpaLogger.Debugf("Swarm: '%s' matches '%s'.", path, entry.Path) - return + // check if pos actually ends a subpath "ab" matches on "" not on "a" + if hop > 0 || pos == len(path) || pos == 0 || path[pos-1] == '/' { + matchlength = pos + hop + maxdepth = depth + matchingEntry = entry } } } - entry = nil - dpaLogger.Debugf("Path '%s' on manifest not found.", path) + if matchingEntry != nil { + dpaLogger.Debugf("Swarm: '%s' matches '%s'.", path, matchingEntry.Path) + } else { + dpaLogger.Debugf("Path '%s' not found on manifest ", path) + } + return }