diff options
Diffstat (limited to 'swarm/api/http')
-rw-r--r-- | swarm/api/http/middleware.go | 162 | ||||
-rw-r--r-- | swarm/api/http/response.go | 132 | ||||
-rw-r--r-- | swarm/api/http/response_test.go | 170 | ||||
-rw-r--r-- | swarm/api/http/roundtripper.go | 66 | ||||
-rw-r--r-- | swarm/api/http/roundtripper_test.go | 69 | ||||
-rw-r--r-- | swarm/api/http/sctx.go | 34 | ||||
-rw-r--r-- | swarm/api/http/server.go | 937 | ||||
-rw-r--r-- | swarm/api/http/server_test.go | 1409 | ||||
-rw-r--r-- | swarm/api/http/templates.go | 306 | ||||
-rw-r--r-- | swarm/api/http/test_server.go | 100 |
10 files changed, 0 insertions, 3385 deletions
diff --git a/swarm/api/http/middleware.go b/swarm/api/http/middleware.go deleted file mode 100644 index e6e263f4c..000000000 --- a/swarm/api/http/middleware.go +++ /dev/null @@ -1,162 +0,0 @@ -package http - -import ( - "fmt" - "net/http" - "runtime/debug" - "strings" - "time" - - "github.com/ethereum/go-ethereum/metrics" - "github.com/ethereum/go-ethereum/swarm/api" - "github.com/ethereum/go-ethereum/swarm/chunk" - "github.com/ethereum/go-ethereum/swarm/log" - "github.com/ethereum/go-ethereum/swarm/sctx" - "github.com/ethereum/go-ethereum/swarm/spancontext" - "github.com/pborman/uuid" -) - -// Adapt chains h (main request handler) main handler to adapters (middleware handlers) -// Please note that the order of execution for `adapters` is FIFO (adapters[0] will be executed first) -func Adapt(h http.Handler, adapters ...Adapter) http.Handler { - for i := range adapters { - adapter := adapters[len(adapters)-1-i] - h = adapter(h) - } - return h -} - -type Adapter func(http.Handler) http.Handler - -func SetRequestID(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(SetRUID(r.Context(), uuid.New()[:8])) - metrics.GetOrRegisterCounter(fmt.Sprintf("http.request.%s", r.Method), nil).Inc(1) - log.Info("created ruid for request", "ruid", GetRUID(r.Context()), "method", r.Method, "url", r.RequestURI) - - h.ServeHTTP(w, r) - }) -} - -func SetRequestHost(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(sctx.SetHost(r.Context(), r.Host)) - log.Info("setting request host", "ruid", GetRUID(r.Context()), "host", sctx.GetHost(r.Context())) - - h.ServeHTTP(w, r) - }) -} - -func ParseURI(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - uri, err := api.Parse(strings.TrimLeft(r.URL.Path, "/")) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - respondError(w, r, fmt.Sprintf("invalid URI %q", r.URL.Path), http.StatusBadRequest) - return - } - if uri.Addr != "" && strings.HasPrefix(uri.Addr, "0x") { - uri.Addr = strings.TrimPrefix(uri.Addr, "0x") - - msg := fmt.Sprintf(`The requested hash seems to be prefixed with '0x'. You will be redirected to the correct URL within 5 seconds.<br/> - Please click <a href='%[1]s'>here</a> if your browser does not redirect you within 5 seconds.<script>setTimeout("location.href='%[1]s';",5000);</script>`, "/"+uri.String()) - w.WriteHeader(http.StatusNotFound) - w.Write([]byte(msg)) - return - } - - ctx := r.Context() - r = r.WithContext(SetURI(ctx, uri)) - log.Debug("parsed request path", "ruid", GetRUID(r.Context()), "method", r.Method, "uri.Addr", uri.Addr, "uri.Path", uri.Path, "uri.Scheme", uri.Scheme) - - h.ServeHTTP(w, r) - }) -} - -func InitLoggingResponseWriter(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - tn := time.Now() - - writer := newLoggingResponseWriter(w) - h.ServeHTTP(writer, r) - - ts := time.Since(tn) - log.Info("request served", "ruid", GetRUID(r.Context()), "code", writer.statusCode, "time", ts) - metrics.GetOrRegisterResettingTimer(fmt.Sprintf("http.request.%s.time", r.Method), nil).Update(ts) - metrics.GetOrRegisterResettingTimer(fmt.Sprintf("http.request.%s.%d.time", r.Method, writer.statusCode), nil).Update(ts) - }) -} - -// InitUploadTag creates a new tag for an upload to the local HTTP proxy -// if a tag is not named using the SwarmTagHeaderName, a fallback name will be used -// when the Content-Length header is set, an ETA on chunking will be available since the -// number of chunks to be split is known in advance (not including enclosing manifest chunks) -// the tag can later be accessed using the appropriate identifier in the request context -func InitUploadTag(h http.Handler, tags *chunk.Tags) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - var ( - tagName string - err error - estimatedTotal int64 = 0 - contentType = r.Header.Get("Content-Type") - headerTag = r.Header.Get(SwarmTagHeaderName) - ) - if headerTag != "" { - tagName = headerTag - log.Trace("got tag name from http header", "tagName", tagName) - } else { - tagName = fmt.Sprintf("unnamed_tag_%d", time.Now().Unix()) - } - - if !strings.Contains(contentType, "multipart") && r.ContentLength > 0 { - log.Trace("calculating tag size", "contentType", contentType, "contentLength", r.ContentLength) - uri := GetURI(r.Context()) - if uri != nil { - log.Debug("got uri from context") - if uri.Addr == "encrypt" { - estimatedTotal = calculateNumberOfChunks(r.ContentLength, true) - } else { - estimatedTotal = calculateNumberOfChunks(r.ContentLength, false) - } - } - } - - log.Trace("creating tag", "tagName", tagName, "estimatedTotal", estimatedTotal) - - t, err := tags.New(tagName, estimatedTotal) - if err != nil { - log.Error("error creating tag", "err", err, "tagName", tagName) - } - - log.Trace("setting tag id to context", "uid", t.Uid) - ctx := sctx.SetTag(r.Context(), t.Uid) - - h.ServeHTTP(w, r.WithContext(ctx)) - }) -} - -func InstrumentOpenTracing(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - uri := GetURI(r.Context()) - if uri == nil || r.Method == "" || (uri != nil && uri.Scheme == "") { - h.ServeHTTP(w, r) // soft fail - return - } - spanName := fmt.Sprintf("http.%s.%s", r.Method, uri.Scheme) - ctx, sp := spancontext.StartSpan(r.Context(), spanName) - - defer sp.Finish() - h.ServeHTTP(w, r.WithContext(ctx)) - }) -} - -func RecoverPanic(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer func() { - if err := recover(); err != nil { - log.Error("panic recovery!", "stack trace", string(debug.Stack()), "url", r.URL.String(), "headers", r.Header) - } - }() - h.ServeHTTP(w, r) - }) -} diff --git a/swarm/api/http/response.go b/swarm/api/http/response.go deleted file mode 100644 index c851a3992..000000000 --- a/swarm/api/http/response.go +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. - -package http - -import ( - "encoding/json" - "fmt" - "html/template" - "net/http" - "strings" - "time" - - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/metrics" - "github.com/ethereum/go-ethereum/swarm/api" -) - -var ( - htmlCounter = metrics.NewRegisteredCounter("api.http.errorpage.html.count", nil) - jsonCounter = metrics.NewRegisteredCounter("api.http.errorpage.json.count", nil) - plaintextCounter = metrics.NewRegisteredCounter("api.http.errorpage.plaintext.count", nil) -) - -type ResponseParams struct { - Msg template.HTML - Code int - Timestamp string - template *template.Template - Details template.HTML -} - -// ShowMultipleChoices 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 contains 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) { - log.Debug("ShowMultipleChoices", "ruid", GetRUID(r.Context()), "uri", GetURI(r.Context())) - msg := "" - if list.Entries == nil { - respondError(w, r, "Could not resolve", http.StatusInternalServerError) - return - } - requestUri := strings.TrimPrefix(r.RequestURI, "/") - - uri, err := api.Parse(requestUri) - if err != nil { - respondError(w, r, "Bad Request", http.StatusBadRequest) - } - - uri.Scheme = "bzz-list" - msg += fmt.Sprintf("Disambiguation:<br/>Your request may refer to multiple choices.<br/>Click <a class=\"orange\" href='"+"/"+uri.String()+"'>here</a> if your browser does not redirect you within 5 seconds.<script>setTimeout(\"location.href='%s';\",5000);</script><br/>", "/"+uri.String()) - respondTemplate(w, r, "error", msg, http.StatusMultipleChoices) -} - -func respondTemplate(w http.ResponseWriter, r *http.Request, templateName, msg string, code int) { - log.Debug("respondTemplate", "ruid", GetRUID(r.Context()), "uri", GetURI(r.Context())) - respond(w, r, &ResponseParams{ - Code: code, - Msg: template.HTML(msg), - Timestamp: time.Now().Format(time.RFC1123), - template: TemplatesMap[templateName], - }) -} - -func respondError(w http.ResponseWriter, r *http.Request, msg string, code int) { - log.Info("respondError", "ruid", GetRUID(r.Context()), "uri", GetURI(r.Context()), "code", code, "msg", msg) - respondTemplate(w, r, "error", msg, code) -} - -func respond(w http.ResponseWriter, r *http.Request, params *ResponseParams) { - w.WriteHeader(params.Code) - - if params.Code >= 400 { - w.Header().Del("Cache-Control") - w.Header().Del("ETag") - } - - acceptHeader := r.Header.Get("Accept") - // this cannot be in a switch since an Accept header can have multiple values: "Accept: */*, text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8" - if strings.Contains(acceptHeader, "application/json") { - if err := respondJSON(w, r, params); err != nil { - respondError(w, r, "Internal server error", http.StatusInternalServerError) - } - } else if strings.Contains(acceptHeader, "text/html") { - respondHTML(w, r, params) - } else { - respondPlaintext(w, r, params) //returns nice errors for curl - } -} - -func respondHTML(w http.ResponseWriter, r *http.Request, params *ResponseParams) { - htmlCounter.Inc(1) - log.Info("respondHTML", "ruid", GetRUID(r.Context()), "code", params.Code) - err := params.template.Execute(w, params) - if err != nil { - log.Error(err.Error()) - } -} - -func respondJSON(w http.ResponseWriter, r *http.Request, params *ResponseParams) error { - jsonCounter.Inc(1) - log.Info("respondJSON", "ruid", GetRUID(r.Context()), "code", params.Code) - w.Header().Set("Content-Type", "application/json") - return json.NewEncoder(w).Encode(params) -} - -func respondPlaintext(w http.ResponseWriter, r *http.Request, params *ResponseParams) error { - plaintextCounter.Inc(1) - log.Info("respondPlaintext", "ruid", GetRUID(r.Context()), "code", params.Code) - w.Header().Set("Content-Type", "text/plain") - strToWrite := "Code: " + fmt.Sprintf("%d", params.Code) + "\n" - strToWrite += "Message: " + string(params.Msg) + "\n" - strToWrite += "Timestamp: " + params.Timestamp + "\n" - _, err := w.Write([]byte(strToWrite)) - return err -} diff --git a/swarm/api/http/response_test.go b/swarm/api/http/response_test.go deleted file mode 100644 index 486c19ab0..000000000 --- a/swarm/api/http/response_test.go +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. - -package http - -import ( - "encoding/json" - "io/ioutil" - "net/http" - "strings" - "testing" - - "golang.org/x/net/html" -) - -func TestError(t *testing.T) { - srv := NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - - var resp *http.Response - var respbody []byte - - url := srv.URL + "/this_should_fail_as_no_bzz_protocol_present" - 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 resp.StatusCode != 404 && !strings.Contains(string(respbody), "Invalid URI "/this_should_fail_as_no_bzz_protocol_present": unknown scheme") { - t.Fatalf("Response body does not match, expected: %v, to contain: %v; received code %d, expected code: %d", string(respbody), "Invalid bzz URI: unknown scheme", 400, resp.StatusCode) - } - - _, err = html.Parse(strings.NewReader(string(respbody))) - if err != nil { - t.Fatalf("HTML validation failed for error page returned!") - } -} - -func Test404Page(t *testing.T) { - srv := NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - - var resp *http.Response - var respbody []byte - - url := srv.URL + "/bzz:/1234567890123456789012345678901234567890123456789012345678901234" - 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 resp.StatusCode != 404 || !strings.Contains(string(respbody), "404") { - t.Fatalf("Invalid Status Code received, expected 404, got %d", resp.StatusCode) - } - - _, err = html.Parse(strings.NewReader(string(respbody))) - if err != nil { - t.Fatalf("HTML validation failed for error page returned!") - } -} - -func Test500Page(t *testing.T) { - srv := NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - - var resp *http.Response - var respbody []byte - - url := srv.URL + "/bzz:/thisShouldFailWith500Code" - 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 resp.StatusCode != 404 { - t.Fatalf("Invalid Status Code received, expected 404, got %d", resp.StatusCode) - } - - _, err = html.Parse(strings.NewReader(string(respbody))) - if err != nil { - t.Fatalf("HTML validation failed for error page returned!") - } -} -func Test500PageWith0xHashPrefix(t *testing.T) { - srv := NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - - var resp *http.Response - var respbody []byte - - url := srv.URL + "/bzz:/0xthisShouldFailWith500CodeAndAHelpfulMessage" - 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 resp.StatusCode != 404 { - t.Fatalf("Invalid Status Code received, expected 404, got %d", resp.StatusCode) - } - - if !strings.Contains(string(respbody), "The requested hash seems to be prefixed with") { - t.Fatalf("Did not receive the expected error message") - } - - _, err = html.Parse(strings.NewReader(string(respbody))) - if err != nil { - t.Fatalf("HTML validation failed for error page returned!") - } -} - -func TestJsonResponse(t *testing.T) { - srv := NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - - var resp *http.Response - var respbody []byte - - url := srv.URL + "/bzz:/thisShouldFailWith500Code/" - req, err := http.NewRequest("GET", url, nil) - if err != nil { - t.Fatalf("Request failed: %v", err) - } - req.Header.Set("Accept", "application/json") - resp, err = http.DefaultClient.Do(req) - if err != nil { - t.Fatalf("Request failed: %v", err) - } - - defer resp.Body.Close() - respbody, err = ioutil.ReadAll(resp.Body) - - if resp.StatusCode != 404 { - t.Fatalf("Invalid Status Code received, expected 404, got %d", resp.StatusCode) - } - - if !isJSON(string(respbody)) { - t.Fatalf("Expected response to be JSON, received invalid JSON: %s", string(respbody)) - } - -} - -func isJSON(s string) bool { - var js map[string]interface{} - return json.Unmarshal([]byte(s), &js) == nil -} diff --git a/swarm/api/http/roundtripper.go b/swarm/api/http/roundtripper.go deleted file mode 100644 index be8ea3985..000000000 --- a/swarm/api/http/roundtripper.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. - -package http - -import ( - "fmt" - "net/http" - - "github.com/ethereum/go-ethereum/swarm/log" -) - -/* -http roundtripper to register for bzz url scheme -see https://github.com/ethereum/go-ethereum/issues/2040 -Usage: - -import ( - "github.com/ethereum/go-ethereum/common/httpclient" - "github.com/ethereum/go-ethereum/swarm/api/http" -) -client := httpclient.New() -// for (private) swarm proxy running locally -client.RegisterScheme("bzz", &http.RoundTripper{Port: port}) -client.RegisterScheme("bzz-immutable", &http.RoundTripper{Port: port}) -client.RegisterScheme("bzz-raw", &http.RoundTripper{Port: port}) - -The port you give the Roundtripper is the port the swarm proxy is listening on. -If Host is left empty, localhost is assumed. - -Using a public gateway, the above few lines gives you the leanest -bzz-scheme aware read-only http client. You really only ever need this -if you need go-native swarm access to bzz addresses. -*/ - -type RoundTripper struct { - Host string - Port string -} - -func (self *RoundTripper) RoundTrip(req *http.Request) (resp *http.Response, err error) { - host := self.Host - if len(host) == 0 { - host = "localhost" - } - url := fmt.Sprintf("http://%s:%s/%s:/%s/%s", host, self.Port, req.Proto, req.URL.Host, req.URL.Path) - log.Info(fmt.Sprintf("roundtripper: proxying request '%s' to '%s'", req.RequestURI, url)) - reqProxy, err := http.NewRequest(req.Method, url, req.Body) - if err != nil { - return nil, err - } - return http.DefaultClient.Do(reqProxy) -} diff --git a/swarm/api/http/roundtripper_test.go b/swarm/api/http/roundtripper_test.go deleted file mode 100644 index f99c4f35e..000000000 --- a/swarm/api/http/roundtripper_test.go +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. - -package http - -import ( - "io/ioutil" - "net" - "net/http" - "net/http/httptest" - "strings" - "testing" - "time" -) - -func TestRoundTripper(t *testing.T) { - serveMux := http.NewServeMux() - serveMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - if r.Method == "GET" { - w.Header().Set("Content-Type", "text/plain") - http.ServeContent(w, r, "", time.Unix(0, 0), strings.NewReader(r.RequestURI)) - } else { - http.Error(w, "Method "+r.Method+" is not supported.", http.StatusMethodNotAllowed) - } - }) - - srv := httptest.NewServer(serveMux) - defer srv.Close() - - host, port, _ := net.SplitHostPort(srv.Listener.Addr().String()) - rt := &RoundTripper{Host: host, Port: port} - trans := &http.Transport{} - trans.RegisterProtocol("bzz", rt) - client := &http.Client{Transport: trans} - resp, err := client.Get("bzz://test.com/path") - if err != nil { - t.Errorf("expected no error, got %v", err) - return - } - - defer func() { - if resp != nil { - resp.Body.Close() - } - }() - - content, err := ioutil.ReadAll(resp.Body) - if err != nil { - t.Errorf("expected no error, got %v", err) - return - } - if string(content) != "/HTTP/1.1:/test.com/path" { - t.Errorf("incorrect response from http server: expected '%v', got '%v'", "/HTTP/1.1:/test.com/path", string(content)) - } - -} diff --git a/swarm/api/http/sctx.go b/swarm/api/http/sctx.go deleted file mode 100644 index b8dafab0b..000000000 --- a/swarm/api/http/sctx.go +++ /dev/null @@ -1,34 +0,0 @@ -package http - -import ( - "context" - - "github.com/ethereum/go-ethereum/swarm/api" - "github.com/ethereum/go-ethereum/swarm/sctx" -) - -type uriKey struct{} - -func GetRUID(ctx context.Context) string { - v, ok := ctx.Value(sctx.HTTPRequestIDKey{}).(string) - if ok { - return v - } - return "xxxxxxxx" -} - -func SetRUID(ctx context.Context, ruid string) context.Context { - return context.WithValue(ctx, sctx.HTTPRequestIDKey{}, ruid) -} - -func GetURI(ctx context.Context) *api.URI { - v, ok := ctx.Value(uriKey{}).(*api.URI) - if ok { - return v - } - return nil -} - -func SetURI(ctx context.Context, uri *api.URI) context.Context { - return context.WithValue(ctx, uriKey{}, uri) -} diff --git a/swarm/api/http/server.go b/swarm/api/http/server.go deleted file mode 100644 index 88f2e4db9..000000000 --- a/swarm/api/http/server.go +++ /dev/null @@ -1,937 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. - -/* -A simple http server interface to Swarm -*/ -package http - -import ( - "bufio" - "bytes" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "math" - "mime" - "mime/multipart" - "net/http" - "os" - "path" - "strconv" - "strings" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/metrics" - "github.com/ethereum/go-ethereum/swarm/api" - "github.com/ethereum/go-ethereum/swarm/chunk" - "github.com/ethereum/go-ethereum/swarm/log" - "github.com/ethereum/go-ethereum/swarm/sctx" - "github.com/ethereum/go-ethereum/swarm/storage" - "github.com/ethereum/go-ethereum/swarm/storage/feed" - "github.com/rs/cors" -) - -var ( - postRawCount = metrics.NewRegisteredCounter("api.http.post.raw.count", nil) - postRawFail = metrics.NewRegisteredCounter("api.http.post.raw.fail", nil) - postFilesCount = metrics.NewRegisteredCounter("api.http.post.files.count", nil) - postFilesFail = metrics.NewRegisteredCounter("api.http.post.files.fail", nil) - deleteCount = metrics.NewRegisteredCounter("api.http.delete.count", nil) - deleteFail = metrics.NewRegisteredCounter("api.http.delete.fail", nil) - getCount = metrics.NewRegisteredCounter("api.http.get.count", nil) - getFail = metrics.NewRegisteredCounter("api.http.get.fail", nil) - getFileCount = metrics.NewRegisteredCounter("api.http.get.file.count", nil) - getFileNotFound = metrics.NewRegisteredCounter("api.http.get.file.notfound", nil) - getFileFail = metrics.NewRegisteredCounter("api.http.get.file.fail", nil) - getListCount = metrics.NewRegisteredCounter("api.http.get.list.count", nil) - getListFail = metrics.NewRegisteredCounter("api.http.get.list.fail", nil) -) - -const SwarmTagHeaderName = "x-swarm-tag" - -type methodHandler map[string]http.Handler - -func (m methodHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) { - v, ok := m[r.Method] - if ok { - v.ServeHTTP(rw, r) - return - } - rw.WriteHeader(http.StatusMethodNotAllowed) -} - -func NewServer(api *api.API, corsString string) *Server { - var allowedOrigins []string - for _, domain := range strings.Split(corsString, ",") { - allowedOrigins = append(allowedOrigins, strings.TrimSpace(domain)) - } - c := cors.New(cors.Options{ - AllowedOrigins: allowedOrigins, - AllowedMethods: []string{http.MethodPost, http.MethodGet, http.MethodDelete, http.MethodPatch, http.MethodPut}, - MaxAge: 600, - AllowedHeaders: []string{"*"}, - }) - - server := &Server{api: api} - - defaultMiddlewares := []Adapter{ - RecoverPanic, - SetRequestID, - SetRequestHost, - InitLoggingResponseWriter, - ParseURI, - InstrumentOpenTracing, - } - - tagAdapter := Adapter(func(h http.Handler) http.Handler { - return InitUploadTag(h, api.Tags) - }) - - defaultPostMiddlewares := append(defaultMiddlewares, tagAdapter) - - mux := http.NewServeMux() - mux.Handle("/bzz:/", methodHandler{ - "GET": Adapt( - http.HandlerFunc(server.HandleBzzGet), - defaultMiddlewares..., - ), - "POST": Adapt( - http.HandlerFunc(server.HandlePostFiles), - defaultPostMiddlewares..., - ), - "DELETE": Adapt( - http.HandlerFunc(server.HandleDelete), - defaultMiddlewares..., - ), - }) - mux.Handle("/bzz-raw:/", methodHandler{ - "GET": Adapt( - http.HandlerFunc(server.HandleGet), - defaultMiddlewares..., - ), - "POST": Adapt( - http.HandlerFunc(server.HandlePostRaw), - defaultPostMiddlewares..., - ), - }) - mux.Handle("/bzz-immutable:/", methodHandler{ - "GET": Adapt( - http.HandlerFunc(server.HandleBzzGet), - defaultMiddlewares..., - ), - }) - mux.Handle("/bzz-hash:/", methodHandler{ - "GET": Adapt( - http.HandlerFunc(server.HandleGet), - defaultMiddlewares..., - ), - }) - mux.Handle("/bzz-list:/", methodHandler{ - "GET": Adapt( - http.HandlerFunc(server.HandleGetList), - defaultMiddlewares..., - ), - }) - mux.Handle("/bzz-feed:/", methodHandler{ - "GET": Adapt( - http.HandlerFunc(server.HandleGetFeed), - defaultMiddlewares..., - ), - "POST": Adapt( - http.HandlerFunc(server.HandlePostFeed), - defaultMiddlewares..., - ), - }) - - mux.Handle("/", methodHandler{ - "GET": Adapt( - http.HandlerFunc(server.HandleRootPaths), - SetRequestID, - InitLoggingResponseWriter, - ), - }) - server.Handler = c.Handler(mux) - - return server -} - -func (s *Server) ListenAndServe(addr string) error { - s.listenAddr = addr - return http.ListenAndServe(addr, s) -} - -// browser API for registering bzz url scheme handlers: -// https://developer.mozilla.org/en/docs/Web-based_protocol_handlers -// electron (chromium) api for registering bzz url scheme handlers: -// https://github.com/atom/electron/blob/master/docs/api/protocol.md -type Server struct { - http.Handler - api *api.API - listenAddr string -} - -func (s *Server) HandleBzzGet(w http.ResponseWriter, r *http.Request) { - log.Debug("handleBzzGet", "ruid", GetRUID(r.Context()), "uri", r.RequestURI) - if r.Header.Get("Accept") == "application/x-tar" { - uri := GetURI(r.Context()) - _, credentials, _ := r.BasicAuth() - reader, err := s.api.GetDirectoryTar(r.Context(), s.api.Decryptor(r.Context(), credentials), uri) - if err != nil { - if isDecryptError(err) { - w.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%q", uri.Address().String())) - respondError(w, r, err.Error(), http.StatusUnauthorized) - return - } - respondError(w, r, fmt.Sprintf("Had an error building the tarball: %v", err), http.StatusInternalServerError) - return - } - defer reader.Close() - - w.Header().Set("Content-Type", "application/x-tar") - - fileName := uri.Addr - if found := path.Base(uri.Path); found != "" && found != "." && found != "/" { - fileName = found - } - w.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=\"%s.tar\"", fileName)) - - w.WriteHeader(http.StatusOK) - io.Copy(w, reader) - return - } - - s.HandleGetFile(w, r) -} - -func (s *Server) HandleRootPaths(w http.ResponseWriter, r *http.Request) { - switch r.RequestURI { - case "/": - respondTemplate(w, r, "landing-page", "Swarm: Please request a valid ENS or swarm hash with the appropriate bzz scheme", 200) - return - case "/robots.txt": - w.Header().Set("Last-Modified", time.Now().Format(http.TimeFormat)) - fmt.Fprintf(w, "User-agent: *\nDisallow: /") - case "/favicon.ico": - w.WriteHeader(http.StatusOK) - w.Write(faviconBytes) - default: - respondError(w, r, "Not Found", http.StatusNotFound) - } -} - -// HandlePostRaw handles a POST request to a raw bzz-raw:/ URI, stores the request -// body in swarm and returns the resulting storage address as a text/plain response -func (s *Server) HandlePostRaw(w http.ResponseWriter, r *http.Request) { - ruid := GetRUID(r.Context()) - log.Debug("handle.post.raw", "ruid", ruid) - - tagUid := sctx.GetTag(r.Context()) - tag, err := s.api.Tags.Get(tagUid) - if err != nil { - log.Error("handle post raw got an error retrieving tag for DoneSplit", "tagUid", tagUid, "err", err) - } - - postRawCount.Inc(1) - - toEncrypt := false - uri := GetURI(r.Context()) - if uri.Addr == "encrypt" { - toEncrypt = true - } - - if uri.Path != "" { - postRawFail.Inc(1) - respondError(w, r, "raw POST request cannot contain a path", http.StatusBadRequest) - return - } - - if uri.Addr != "" && uri.Addr != "encrypt" { - postRawFail.Inc(1) - respondError(w, r, "raw POST request addr can only be empty or \"encrypt\"", http.StatusBadRequest) - return - } - - if r.Header.Get("Content-Length") == "" { - postRawFail.Inc(1) - respondError(w, r, "missing Content-Length header in request", http.StatusBadRequest) - return - } - - addr, wait, err := s.api.Store(r.Context(), r.Body, r.ContentLength, toEncrypt) - if err != nil { - postRawFail.Inc(1) - respondError(w, r, err.Error(), http.StatusInternalServerError) - return - } - - wait(r.Context()) - tag.DoneSplit(addr) - - log.Debug("stored content", "ruid", ruid, "key", addr) - - w.Header().Set("Content-Type", "text/plain") - w.WriteHeader(http.StatusOK) - fmt.Fprint(w, addr) -} - -// HandlePostFiles handles a POST request to -// bzz:/<hash>/<path> which contains either a single file or multiple files -// (either a tar archive or multipart form), adds those files either to an -// existing manifest or to a new manifest under <path> and returns the -// resulting manifest hash as a text/plain response -func (s *Server) HandlePostFiles(w http.ResponseWriter, r *http.Request) { - ruid := GetRUID(r.Context()) - log.Debug("handle.post.files", "ruid", ruid) - postFilesCount.Inc(1) - - contentType, params, err := mime.ParseMediaType(r.Header.Get("Content-Type")) - if err != nil { - postFilesFail.Inc(1) - respondError(w, r, err.Error(), http.StatusBadRequest) - return - } - - toEncrypt := false - uri := GetURI(r.Context()) - if uri.Addr == "encrypt" { - toEncrypt = true - } - - var addr storage.Address - if uri.Addr != "" && uri.Addr != "encrypt" { - addr, err = s.api.Resolve(r.Context(), uri.Addr) - if err != nil { - postFilesFail.Inc(1) - respondError(w, r, fmt.Sprintf("cannot resolve %s: %s", uri.Addr, err), http.StatusInternalServerError) - return - } - log.Debug("resolved key", "ruid", ruid, "key", addr) - } else { - addr, err = s.api.NewManifest(r.Context(), toEncrypt) - if err != nil { - postFilesFail.Inc(1) - respondError(w, r, err.Error(), http.StatusInternalServerError) - return - } - log.Debug("new manifest", "ruid", ruid, "key", addr) - } - newAddr, err := s.api.UpdateManifest(r.Context(), addr, func(mw *api.ManifestWriter) error { - switch contentType { - case "application/x-tar": - _, err := s.handleTarUpload(r, mw) - if err != nil { - respondError(w, r, fmt.Sprintf("error uploading tarball: %v", err), http.StatusInternalServerError) - return err - } - return nil - case "multipart/form-data": - return s.handleMultipartUpload(r, params["boundary"], mw) - - default: - return s.handleDirectUpload(r, mw) - } - }) - if err != nil { - postFilesFail.Inc(1) - respondError(w, r, fmt.Sprintf("cannot create manifest: %s", err), http.StatusInternalServerError) - return - } - - tagUid := sctx.GetTag(r.Context()) - tag, err := s.api.Tags.Get(tagUid) - if err != nil { - log.Error("got an error retrieving tag for DoneSplit", "tagUid", tagUid, "err", err) - } - - log.Debug("done splitting, setting tag total", "SPLIT", tag.Get(chunk.StateSplit), "TOTAL", tag.Total()) - tag.DoneSplit(newAddr) - - log.Debug("stored content", "ruid", ruid, "key", newAddr) - - w.Header().Set("Content-Type", "text/plain") - w.WriteHeader(http.StatusOK) - fmt.Fprint(w, newAddr) -} - -func (s *Server) handleTarUpload(r *http.Request, mw *api.ManifestWriter) (storage.Address, error) { - log.Debug("handle.tar.upload", "ruid", GetRUID(r.Context()), "tag", sctx.GetTag(r.Context())) - - defaultPath := r.URL.Query().Get("defaultpath") - - key, err := s.api.UploadTar(r.Context(), r.Body, GetURI(r.Context()).Path, defaultPath, mw) - if err != nil { - return nil, err - } - return key, nil -} - -func (s *Server) handleMultipartUpload(r *http.Request, boundary string, mw *api.ManifestWriter) error { - ruid := GetRUID(r.Context()) - log.Debug("handle.multipart.upload", "ruid", ruid) - mr := multipart.NewReader(r.Body, boundary) - for { - part, err := mr.NextPart() - if err == io.EOF { - return nil - } else if err != nil { - return fmt.Errorf("error reading multipart form: %s", err) - } - - var size int64 - var reader io.Reader - if contentLength := part.Header.Get("Content-Length"); contentLength != "" { - size, err = strconv.ParseInt(contentLength, 10, 64) - if err != nil { - return fmt.Errorf("error parsing multipart content length: %s", err) - } - reader = part - } else { - // copy the part to a tmp file to get its size - tmp, err := ioutil.TempFile("", "swarm-multipart") - if err != nil { - return err - } - defer os.Remove(tmp.Name()) - defer tmp.Close() - size, err = io.Copy(tmp, part) - if err != nil { - return fmt.Errorf("error copying multipart content: %s", err) - } - if _, err := tmp.Seek(0, io.SeekStart); err != nil { - return fmt.Errorf("error copying multipart content: %s", err) - } - reader = tmp - } - - // add the entry under the path from the request - name := part.FileName() - if name == "" { - name = part.FormName() - } - uri := GetURI(r.Context()) - path := path.Join(uri.Path, name) - entry := &api.ManifestEntry{ - Path: path, - ContentType: part.Header.Get("Content-Type"), - Size: size, - } - log.Debug("adding path to new manifest", "ruid", ruid, "bytes", entry.Size, "path", entry.Path) - contentKey, err := mw.AddEntry(r.Context(), reader, entry) - if err != nil { - return fmt.Errorf("error adding manifest entry from multipart form: %s", err) - } - log.Debug("stored content", "ruid", ruid, "key", contentKey) - } -} - -func (s *Server) handleDirectUpload(r *http.Request, mw *api.ManifestWriter) error { - ruid := GetRUID(r.Context()) - log.Debug("handle.direct.upload", "ruid", ruid) - key, err := mw.AddEntry(r.Context(), r.Body, &api.ManifestEntry{ - Path: GetURI(r.Context()).Path, - ContentType: r.Header.Get("Content-Type"), - Mode: 0644, - Size: r.ContentLength, - }) - if err != nil { - return err - } - log.Debug("stored content", "ruid", ruid, "key", key) - return nil -} - -// HandleDelete handles a DELETE request to bzz:/<manifest>/<path>, removes -// <path> from <manifest> and returns the resulting manifest hash as a -// text/plain response -func (s *Server) HandleDelete(w http.ResponseWriter, r *http.Request) { - ruid := GetRUID(r.Context()) - uri := GetURI(r.Context()) - log.Debug("handle.delete", "ruid", ruid) - deleteCount.Inc(1) - newKey, err := s.api.Delete(r.Context(), uri.Addr, uri.Path) - if err != nil { - deleteFail.Inc(1) - respondError(w, r, fmt.Sprintf("could not delete from manifest: %v", err), http.StatusInternalServerError) - return - } - - w.Header().Set("Content-Type", "text/plain") - w.WriteHeader(http.StatusOK) - fmt.Fprint(w, newKey) -} - -// Handles feed manifest creation and feed updates -// The POST request admits a JSON structure as defined in the feeds package: `feed.updateRequestJSON` -// The requests can be to a) create a feed manifest, b) update a feed or c) both a+b: create a feed manifest and publish a first update -func (s *Server) HandlePostFeed(w http.ResponseWriter, r *http.Request) { - ruid := GetRUID(r.Context()) - uri := GetURI(r.Context()) - log.Debug("handle.post.feed", "ruid", ruid) - var err error - - // Creation and update must send feed.updateRequestJSON JSON structure - body, err := ioutil.ReadAll(r.Body) - if err != nil { - respondError(w, r, err.Error(), http.StatusInternalServerError) - return - } - - fd, err := s.api.ResolveFeed(r.Context(), uri, r.URL.Query()) - if err != nil { // couldn't parse query string or retrieve manifest - getFail.Inc(1) - httpStatus := http.StatusBadRequest - if err == api.ErrCannotLoadFeedManifest || err == api.ErrCannotResolveFeedURI { - httpStatus = http.StatusNotFound - } - respondError(w, r, fmt.Sprintf("cannot retrieve feed from manifest: %s", err), httpStatus) - return - } - - var updateRequest feed.Request - updateRequest.Feed = *fd - query := r.URL.Query() - - if err := updateRequest.FromValues(query, body); err != nil { // decodes request from query parameters - respondError(w, r, err.Error(), http.StatusBadRequest) - return - } - - switch { - case updateRequest.IsUpdate(): - // Verify that the signature is intact and that the signer is authorized - // to update this feed - // Check this early, to avoid creating a feed and then not being able to set its first update. - if err = updateRequest.Verify(); err != nil { - respondError(w, r, err.Error(), http.StatusForbidden) - return - } - _, err = s.api.FeedsUpdate(r.Context(), &updateRequest) - if err != nil { - respondError(w, r, err.Error(), http.StatusInternalServerError) - return - } - fallthrough - case query.Get("manifest") == "1": - // we create a manifest so we can retrieve feed updates with bzz:// later - // this manifest has a special "feed type" manifest, and saves the - // feed identification used to retrieve feed updates later - m, err := s.api.NewFeedManifest(r.Context(), &updateRequest.Feed) - if err != nil { - respondError(w, r, fmt.Sprintf("failed to create feed manifest: %v", err), http.StatusInternalServerError) - return - } - // the key to the manifest will be passed back to the client - // the client can access the feed directly through its Feed member - // the manifest key can be set as content in the resolver of the ENS name - outdata, err := json.Marshal(m) - if err != nil { - respondError(w, r, fmt.Sprintf("failed to create json response: %s", err), http.StatusInternalServerError) - return - } - fmt.Fprint(w, string(outdata)) - - w.Header().Add("Content-type", "application/json") - default: - respondError(w, r, "Missing signature in feed update request", http.StatusBadRequest) - } -} - -// HandleGetFeed retrieves Swarm feeds updates: -// bzz-feed://<manifest address or ENS name> - get latest feed update, given a manifest address -// - or - -// specify user + topic (optional), subtopic name (optional) directly, without manifest: -// bzz-feed://?user=0x...&topic=0x...&name=subtopic name -// topic defaults to 0x000... if not specified. -// name defaults to empty string if not specified. -// thus, empty name and topic refers to the user's default feed. -// -// Optional parameters: -// time=xx - get the latest update before time (in epoch seconds) -// hint.time=xx - hint the lookup algorithm looking for updates at around that time -// hint.level=xx - hint the lookup algorithm looking for updates at around this frequency level -// meta=1 - get feed metadata and status information instead of performing a feed query -// NOTE: meta=1 will be deprecated in the near future -func (s *Server) HandleGetFeed(w http.ResponseWriter, r *http.Request) { - ruid := GetRUID(r.Context()) - uri := GetURI(r.Context()) - log.Debug("handle.get.feed", "ruid", ruid) - var err error - - fd, err := s.api.ResolveFeed(r.Context(), uri, r.URL.Query()) - if err != nil { // couldn't parse query string or retrieve manifest - getFail.Inc(1) - httpStatus := http.StatusBadRequest - if err == api.ErrCannotLoadFeedManifest || err == api.ErrCannotResolveFeedURI { - httpStatus = http.StatusNotFound - } - respondError(w, r, fmt.Sprintf("cannot retrieve feed information from manifest: %s", err), httpStatus) - return - } - - // determine if the query specifies period and version or it is a metadata query - if r.URL.Query().Get("meta") == "1" { - unsignedUpdateRequest, err := s.api.FeedsNewRequest(r.Context(), fd) - if err != nil { - getFail.Inc(1) - respondError(w, r, fmt.Sprintf("cannot retrieve feed metadata for feed=%s: %s", fd.Hex(), err), http.StatusNotFound) - return - } - rawResponse, err := unsignedUpdateRequest.MarshalJSON() - if err != nil { - respondError(w, r, fmt.Sprintf("cannot encode unsigned feed update request: %v", err), http.StatusInternalServerError) - return - } - w.Header().Add("Content-type", "application/json") - w.WriteHeader(http.StatusOK) - fmt.Fprint(w, string(rawResponse)) - return - } - - lookupParams := &feed.Query{Feed: *fd} - if err = lookupParams.FromValues(r.URL.Query()); err != nil { // parse period, version - respondError(w, r, fmt.Sprintf("invalid feed update request:%s", err), http.StatusBadRequest) - return - } - - data, err := s.api.FeedsLookup(r.Context(), lookupParams) - - // any error from the switch statement will end up here - if err != nil { - code, err2 := s.translateFeedError(w, r, "feed lookup fail", err) - respondError(w, r, err2.Error(), code) - return - } - - // All ok, serve the retrieved update - log.Debug("Found update", "feed", fd.Hex(), "ruid", ruid) - w.Header().Set("Content-Type", api.MimeOctetStream) - http.ServeContent(w, r, "", time.Now(), bytes.NewReader(data)) -} - -func (s *Server) translateFeedError(w http.ResponseWriter, r *http.Request, supErr string, err error) (int, error) { - code := 0 - defaultErr := fmt.Errorf("%s: %v", supErr, err) - rsrcErr, ok := err.(*feed.Error) - if !ok && rsrcErr != nil { - code = rsrcErr.Code() - } - switch code { - case storage.ErrInvalidValue: - return http.StatusBadRequest, defaultErr - case storage.ErrNotFound, storage.ErrNotSynced, storage.ErrNothingToReturn, storage.ErrInit: - return http.StatusNotFound, defaultErr - case storage.ErrUnauthorized, storage.ErrInvalidSignature: - return http.StatusUnauthorized, defaultErr - case storage.ErrDataOverflow: - return http.StatusRequestEntityTooLarge, defaultErr - } - - return http.StatusInternalServerError, defaultErr -} - -// HandleGet handles a GET request to -// - bzz-raw://<key> and responds with the raw content stored at the -// given storage key -// - bzz-hash://<key> 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 *http.Request) { - ruid := GetRUID(r.Context()) - uri := GetURI(r.Context()) - log.Debug("handle.get", "ruid", ruid, "uri", uri) - getCount.Inc(1) - _, pass, _ := r.BasicAuth() - - addr, err := s.api.ResolveURI(r.Context(), uri, pass) - if err != nil { - getFail.Inc(1) - respondError(w, r, fmt.Sprintf("cannot resolve %s: %s", uri.Addr, err), http.StatusNotFound) - return - } - w.Header().Set("Cache-Control", "max-age=2147483648, immutable") // url was of type bzz://<hex key>/path, so we are sure it is immutable. - - log.Debug("handle.get: resolved", "ruid", ruid, "key", addr) - - // if path is set, interpret <key> as a manifest and return the - // raw entry at the given path - etag := common.Bytes2Hex(addr) - noneMatchEtag := r.Header.Get("If-None-Match") - w.Header().Set("ETag", fmt.Sprintf("%q", etag)) // set etag to manifest key or raw entry key. - if noneMatchEtag != "" { - if bytes.Equal(storage.Address(common.Hex2Bytes(noneMatchEtag)), addr) { - w.WriteHeader(http.StatusNotModified) - return - } - } - - switch { - case uri.Raw(): - // check the root chunk exists by retrieving the file's size - reader, isEncrypted := s.api.Retrieve(r.Context(), addr) - if _, err := reader.Size(r.Context(), nil); err != nil { - getFail.Inc(1) - respondError(w, r, fmt.Sprintf("root chunk not found %s: %s", addr, err), http.StatusNotFound) - return - } - - w.Header().Set("X-Decrypted", fmt.Sprintf("%v", isEncrypted)) - - // allow the request to overwrite the content type using a query - // parameter - if typ := r.URL.Query().Get("content_type"); typ != "" { - w.Header().Set("Content-Type", typ) - } - http.ServeContent(w, r, "", time.Now(), reader) - case uri.Hash(): - w.Header().Set("Content-Type", "text/plain") - w.WriteHeader(http.StatusOK) - fmt.Fprint(w, addr) - } - -} - -// HandleGetList handles a GET request to bzz-list:/<manifest>/<path> and returns -// a list of all files contained in <manifest> under <path> grouped into -// common prefixes using "/" as a delimiter -func (s *Server) HandleGetList(w http.ResponseWriter, r *http.Request) { - ruid := GetRUID(r.Context()) - uri := GetURI(r.Context()) - _, credentials, _ := r.BasicAuth() - log.Debug("handle.get.list", "ruid", ruid, "uri", uri) - getListCount.Inc(1) - - // ensure the root path has a trailing slash so that relative URLs work - if uri.Path == "" && !strings.HasSuffix(r.URL.Path, "/") { - http.Redirect(w, r, r.URL.Path+"/", http.StatusMovedPermanently) - return - } - - addr, err := s.api.Resolve(r.Context(), uri.Addr) - if err != nil { - getListFail.Inc(1) - respondError(w, r, fmt.Sprintf("cannot resolve %s: %s", uri.Addr, err), http.StatusNotFound) - return - } - log.Debug("handle.get.list: resolved", "ruid", ruid, "key", addr) - - list, err := s.api.GetManifestList(r.Context(), s.api.Decryptor(r.Context(), credentials), addr, uri.Path) - if err != nil { - getListFail.Inc(1) - if isDecryptError(err) { - w.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%q", addr.String())) - respondError(w, r, err.Error(), http.StatusUnauthorized) - return - } - respondError(w, r, err.Error(), http.StatusInternalServerError) - 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 := TemplatesMap["bzz-list"].Execute(w, &htmlListData{ - URI: &api.URI{ - Scheme: "bzz", - Addr: uri.Addr, - Path: uri.Path, - }, - List: &list, - }) - if err != nil { - getListFail.Inc(1) - log.Error(fmt.Sprintf("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 -// with the content of the file at <path> from the given <manifest> -func (s *Server) HandleGetFile(w http.ResponseWriter, r *http.Request) { - ruid := GetRUID(r.Context()) - uri := GetURI(r.Context()) - _, credentials, _ := r.BasicAuth() - log.Debug("handle.get.file", "ruid", ruid, "uri", r.RequestURI) - getFileCount.Inc(1) - - // ensure the root path has a trailing slash so that relative URLs work - if uri.Path == "" && !strings.HasSuffix(r.URL.Path, "/") { - http.Redirect(w, r, r.URL.Path+"/", http.StatusMovedPermanently) - return - } - var err error - manifestAddr := uri.Address() - - if manifestAddr == nil { - manifestAddr, err = s.api.Resolve(r.Context(), uri.Addr) - if err != nil { - getFileFail.Inc(1) - respondError(w, r, fmt.Sprintf("cannot resolve %s: %s", uri.Addr, err), http.StatusNotFound) - return - } - } else { - w.Header().Set("Cache-Control", "max-age=2147483648, immutable") // url was of type bzz://<hex key>/path, so we are sure it is immutable. - } - - log.Debug("handle.get.file: resolved", "ruid", ruid, "key", manifestAddr) - - reader, contentType, status, contentKey, err := s.api.Get(r.Context(), s.api.Decryptor(r.Context(), credentials), manifestAddr, uri.Path) - - etag := common.Bytes2Hex(contentKey) - noneMatchEtag := r.Header.Get("If-None-Match") - w.Header().Set("ETag", fmt.Sprintf("%q", etag)) // set etag to actual content key. - if noneMatchEtag != "" { - if bytes.Equal(storage.Address(common.Hex2Bytes(noneMatchEtag)), contentKey) { - w.WriteHeader(http.StatusNotModified) - return - } - } - - if err != nil { - if isDecryptError(err) { - w.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%q", manifestAddr)) - respondError(w, r, err.Error(), http.StatusUnauthorized) - return - } - - switch status { - case http.StatusNotFound: - getFileNotFound.Inc(1) - respondError(w, r, err.Error(), http.StatusNotFound) - default: - getFileFail.Inc(1) - respondError(w, r, err.Error(), http.StatusInternalServerError) - } - 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.api.GetManifestList(r.Context(), s.api.Decryptor(r.Context(), credentials), manifestAddr, uri.Path) - if err != nil { - getFileFail.Inc(1) - if isDecryptError(err) { - w.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%q", manifestAddr)) - respondError(w, r, err.Error(), http.StatusUnauthorized) - return - } - respondError(w, r, err.Error(), http.StatusInternalServerError) - return - } - - log.Debug(fmt.Sprintf("Multiple choices! --> %v", list), "ruid", ruid) - //show a nice page links to available entries - ShowMultipleChoices(w, r, list) - return - } - - // check the root chunk exists by retrieving the file's size - if _, err := reader.Size(r.Context(), nil); err != nil { - getFileNotFound.Inc(1) - respondError(w, r, fmt.Sprintf("file not found %s: %s", uri, err), http.StatusNotFound) - return - } - - if contentType != "" { - w.Header().Set("Content-Type", contentType) - } - - fileName := uri.Addr - if found := path.Base(uri.Path); found != "" && found != "." && found != "/" { - fileName = found - } - w.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=\"%s\"", fileName)) - - http.ServeContent(w, r, fileName, time.Now(), newBufferedReadSeeker(reader, getFileBufferSize)) -} - -// calculateNumberOfChunks calculates the number of chunks in an arbitrary content length -func calculateNumberOfChunks(contentLength int64, isEncrypted bool) int64 { - if contentLength < 4096 { - return 1 - } - branchingFactor := 128 - if isEncrypted { - branchingFactor = 64 - } - - dataChunks := math.Ceil(float64(contentLength) / float64(4096)) - totalChunks := dataChunks - intermediate := dataChunks / float64(branchingFactor) - - for intermediate > 1 { - totalChunks += math.Ceil(intermediate) - intermediate = intermediate / float64(branchingFactor) - } - - return int64(totalChunks) + 1 -} - -// The size of buffer used for bufio.Reader on LazyChunkReader passed to -// http.ServeContent in HandleGetFile. -// Warning: This value influences the number of chunk requests and chunker join goroutines -// per file request. -// Recommended value is 4 times the io.Copy default buffer value which is 32kB. -const getFileBufferSize = 4 * 32 * 1024 - -// bufferedReadSeeker wraps bufio.Reader to expose Seek method -// from the provied io.ReadSeeker in newBufferedReadSeeker. -type bufferedReadSeeker struct { - r io.Reader - s io.Seeker -} - -// newBufferedReadSeeker creates a new instance of bufferedReadSeeker, -// out of io.ReadSeeker. Argument `size` is the size of the read buffer. -func newBufferedReadSeeker(readSeeker io.ReadSeeker, size int) bufferedReadSeeker { - return bufferedReadSeeker{ - r: bufio.NewReaderSize(readSeeker, size), - s: readSeeker, - } -} - -func (b bufferedReadSeeker) Read(p []byte) (n int, err error) { - return b.r.Read(p) -} - -func (b bufferedReadSeeker) Seek(offset int64, whence int) (int64, error) { - return b.s.Seek(offset, whence) -} - -type loggingResponseWriter struct { - http.ResponseWriter - statusCode int -} - -func newLoggingResponseWriter(w http.ResponseWriter) *loggingResponseWriter { - return &loggingResponseWriter{w, http.StatusOK} -} - -func (lrw *loggingResponseWriter) WriteHeader(code int) { - lrw.statusCode = code - lrw.ResponseWriter.WriteHeader(code) -} - -func isDecryptError(err error) bool { - return strings.Contains(err.Error(), api.ErrDecrypt.Error()) -} diff --git a/swarm/api/http/server_test.go b/swarm/api/http/server_test.go deleted file mode 100644 index 9df820ce4..000000000 --- a/swarm/api/http/server_test.go +++ /dev/null @@ -1,1409 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. - -package http - -import ( - "archive/tar" - "bytes" - "context" - "encoding/json" - "errors" - "flag" - "fmt" - "io" - "io/ioutil" - "math/big" - "mime/multipart" - "net/http" - "net/url" - "os" - "path" - "strconv" - "strings" - "testing" - "time" - - "github.com/ethereum/go-ethereum/swarm/storage/feed/lookup" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/swarm/api" - "github.com/ethereum/go-ethereum/swarm/storage" - "github.com/ethereum/go-ethereum/swarm/storage/feed" - "github.com/ethereum/go-ethereum/swarm/testutil" -) - -func init() { - loglevel := flag.Int("loglevel", 2, "loglevel") - flag.Parse() - log.Root().SetHandler(log.CallerFileHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(os.Stderr, log.TerminalFormat(true))))) -} - -func serverFunc(api *api.API) TestServer { - return NewServer(api, "") -} - -func newTestSigner() (*feed.GenericSigner, error) { - privKey, err := crypto.HexToECDSA("deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef") - if err != nil { - return nil, err - } - return feed.NewGenericSigner(privKey), nil -} - -// Test the transparent resolving of feed updates with bzz:// scheme -// -// First upload data to bzz:, and store the Swarm hash to the resulting manifest in a feed update. -// This effectively uses a feed to store a pointer to content rather than the content itself -// Retrieving the update with the Swarm hash should return the manifest pointing directly to the data -// and raw retrieve of that hash should return the data -func TestBzzWithFeed(t *testing.T) { - - signer, _ := newTestSigner() - - // Initialize Swarm test server - srv := NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - - // put together some data for our test: - dataBytes := []byte(` - // - // Create some data our manifest will point to. Data that could be very big and wouldn't fit in a feed update. - // So what we are going to do is upload it to Swarm bzz:// and obtain a **manifest hash** pointing to it: - // - // MANIFEST HASH --> DATA - // - // Then, we store that **manifest hash** into a Swarm Feed update. Once we have done this, - // we can use the **feed manifest hash** in bzz:// instead, this way: bzz://feed-manifest-hash. - // - // FEED MANIFEST HASH --> MANIFEST HASH --> DATA - // - // Given that we can update the feed at any time with a new **manifest hash** but the **feed manifest hash** - // stays constant, we have effectively created a fixed address to changing content. (Applause) - // - // FEED MANIFEST HASH (the same) --> MANIFEST HASH(2) --> DATA(2) ... - // - `) - - // POST data to bzz and get back a content-addressed **manifest hash** pointing to it. - resp, err := http.Post(fmt.Sprintf("%s/bzz:/", srv.URL), "text/plain", bytes.NewReader([]byte(dataBytes))) - if err != nil { - t.Fatal(err) - } - - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Fatalf("err %s", resp.Status) - } - manifestAddressHex, err := ioutil.ReadAll(resp.Body) - - if err != nil { - t.Fatal(err) - } - - manifestAddress := common.FromHex(string(manifestAddressHex)) - - log.Info("added data", "manifest", string(manifestAddressHex)) - - // At this point we have uploaded the data and have a manifest pointing to it - // Now store that manifest address in a feed update. - // We also want a feed manifest, so we can use it to refer to the feed. - - // First, create a topic for our feed: - topic, _ := feed.NewTopic("interesting topic indeed", nil) - - // Create a feed update request: - updateRequest := feed.NewFirstRequest(topic) - - // Store the **manifest address** as data into the feed update. - updateRequest.SetData(manifestAddress) - - // Sign the update - if err := updateRequest.Sign(signer); err != nil { - t.Fatal(err) - } - log.Info("added data", "data", common.ToHex(manifestAddress)) - - // Build the feed update http request: - feedUpdateURL, err := url.Parse(fmt.Sprintf("%s/bzz-feed:/", srv.URL)) - if err != nil { - t.Fatal(err) - } - query := feedUpdateURL.Query() - body := updateRequest.AppendValues(query) // this adds all query parameters and returns the data to be posted - query.Set("manifest", "1") // indicate we want a feed manifest back - feedUpdateURL.RawQuery = query.Encode() - - // submit the feed update request to Swarm - resp, err = http.Post(feedUpdateURL.String(), "application/octet-stream", bytes.NewReader(body)) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Fatalf("err %s", resp.Status) - } - - feedManifestAddressHex, err := ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatal(err) - } - feedManifestAddress := &storage.Address{} - err = json.Unmarshal(feedManifestAddressHex, feedManifestAddress) - if err != nil { - t.Fatalf("data %s could not be unmarshaled: %v", feedManifestAddressHex, err) - } - - correctManifestAddrHex := "747c402e5b9dc715a25a4393147512167bab018a007fad7cdcd9adc7fce1ced2" - if feedManifestAddress.Hex() != correctManifestAddrHex { - t.Fatalf("Response feed manifest address mismatch, expected '%s', got '%s'", correctManifestAddrHex, feedManifestAddress.Hex()) - } - - // get bzz manifest transparent feed update resolve - getBzzURL := fmt.Sprintf("%s/bzz:/%s", srv.URL, feedManifestAddress) - resp, err = http.Get(getBzzURL) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Fatalf("err %s", resp.Status) - } - retrievedData, err := ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(retrievedData, []byte(dataBytes)) { - t.Fatalf("retrieved data mismatch, expected %x, got %x", dataBytes, retrievedData) - } -} - -// Test Swarm feeds using the raw update methods -func TestBzzFeed(t *testing.T) { - srv := NewTestSwarmServer(t, serverFunc, nil) - signer, _ := newTestSigner() - - defer srv.Close() - - // data of update 1 - update1Data := testutil.RandomBytes(1, 666) - update1Timestamp := srv.CurrentTime - //data for update 2 - update2Data := []byte("foo") - - topic, _ := feed.NewTopic("foo.eth", nil) - updateRequest := feed.NewFirstRequest(topic) - updateRequest.SetData(update1Data) - - if err := updateRequest.Sign(signer); err != nil { - t.Fatal(err) - } - - // creates feed and sets update 1 - testUrl, err := url.Parse(fmt.Sprintf("%s/bzz-feed:/", srv.URL)) - if err != nil { - t.Fatal(err) - } - urlQuery := testUrl.Query() - body := updateRequest.AppendValues(urlQuery) // this adds all query parameters - urlQuery.Set("manifest", "1") // indicate we want a manifest back - testUrl.RawQuery = urlQuery.Encode() - - resp, err := http.Post(testUrl.String(), "application/octet-stream", bytes.NewReader(body)) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Fatalf("err %s", resp.Status) - } - b, err := ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatal(err) - } - rsrcResp := &storage.Address{} - err = json.Unmarshal(b, rsrcResp) - if err != nil { - t.Fatalf("data %s could not be unmarshaled: %v", b, err) - } - - correctManifestAddrHex := "bb056a5264c295c2b0f613c8409b9c87ce9d71576ace02458160df4cc894210b" - if rsrcResp.Hex() != correctManifestAddrHex { - t.Fatalf("Response feed manifest mismatch, expected '%s', got '%s'", correctManifestAddrHex, rsrcResp.Hex()) - } - - // get the manifest - testRawUrl := fmt.Sprintf("%s/bzz-raw:/%s", srv.URL, rsrcResp) - resp, err = http.Get(testRawUrl) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Fatalf("err %s", resp.Status) - } - b, err = ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatal(err) - } - manifest := &api.Manifest{} - err = json.Unmarshal(b, manifest) - if err != nil { - t.Fatal(err) - } - if len(manifest.Entries) != 1 { - t.Fatalf("Manifest has %d entries", len(manifest.Entries)) - } - correctFeedHex := "0x666f6f2e65746800000000000000000000000000000000000000000000000000c96aaa54e2d44c299564da76e1cd3184a2386b8d" - if manifest.Entries[0].Feed.Hex() != correctFeedHex { - t.Fatalf("Expected manifest Feed '%s', got '%s'", correctFeedHex, manifest.Entries[0].Feed.Hex()) - } - - // take the chance to have bzz: crash on resolving a feed update that does not contain - // a swarm hash: - testBzzUrl := fmt.Sprintf("%s/bzz:/%s", srv.URL, rsrcResp) - resp, err = http.Get(testBzzUrl) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - if resp.StatusCode == http.StatusOK { - t.Fatal("Expected error status since feed update does not contain a Swarm hash. Received 200 OK") - } - _, err = ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatal(err) - } - - // get non-existent name, should fail - testBzzResUrl := fmt.Sprintf("%s/bzz-feed:/bar", srv.URL) - resp, err = http.Get(testBzzResUrl) - if err != nil { - t.Fatal(err) - } - - if resp.StatusCode != http.StatusNotFound { - t.Fatalf("Expected get non-existent feed manifest to fail with StatusNotFound (404), got %d", resp.StatusCode) - } - - resp.Body.Close() - - // get latest update through bzz-feed directly - log.Info("get update latest = 1.1", "addr", correctManifestAddrHex) - testBzzResUrl = fmt.Sprintf("%s/bzz-feed:/%s", srv.URL, correctManifestAddrHex) - resp, err = http.Get(testBzzResUrl) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Fatalf("err %s", resp.Status) - } - b, err = ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(update1Data, b) { - t.Fatalf("Expected body '%x', got '%x'", update1Data, b) - } - - // update 2 - // Move the clock ahead 1 second - srv.CurrentTime++ - log.Info("update 2") - - // 1.- get metadata about this feed - testBzzResUrl = fmt.Sprintf("%s/bzz-feed:/%s/", srv.URL, correctManifestAddrHex) - resp, err = http.Get(testBzzResUrl + "?meta=1") - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Fatalf("Get feed metadata returned %s", resp.Status) - } - b, err = ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatal(err) - } - updateRequest = &feed.Request{} - if err = updateRequest.UnmarshalJSON(b); err != nil { - t.Fatalf("Error decoding feed metadata: %s", err) - } - updateRequest.SetData(update2Data) - if err = updateRequest.Sign(signer); err != nil { - t.Fatal(err) - } - testUrl, err = url.Parse(fmt.Sprintf("%s/bzz-feed:/", srv.URL)) - if err != nil { - t.Fatal(err) - } - urlQuery = testUrl.Query() - body = updateRequest.AppendValues(urlQuery) // this adds all query parameters - goodQueryParameters := urlQuery.Encode() // save the query parameters for a second attempt - - // create bad query parameters in which the signature is missing - urlQuery.Del("signature") - testUrl.RawQuery = urlQuery.Encode() - - // 1st attempt with bad query parameters in which the signature is missing - resp, err = http.Post(testUrl.String(), "application/octet-stream", bytes.NewReader(body)) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - expectedCode := http.StatusBadRequest - if resp.StatusCode != expectedCode { - t.Fatalf("Update returned %s. Expected %d", resp.Status, expectedCode) - } - - // 2nd attempt with bad query parameters in which the signature is of incorrect length - urlQuery.Set("signature", "0xabcd") // should be 130 hex chars - resp, err = http.Post(testUrl.String(), "application/octet-stream", bytes.NewReader(body)) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - expectedCode = http.StatusBadRequest - if resp.StatusCode != expectedCode { - t.Fatalf("Update returned %s. Expected %d", resp.Status, expectedCode) - } - - // 3rd attempt, with good query parameters: - testUrl.RawQuery = goodQueryParameters - resp, err = http.Post(testUrl.String(), "application/octet-stream", bytes.NewReader(body)) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - expectedCode = http.StatusOK - if resp.StatusCode != expectedCode { - t.Fatalf("Update returned %s. Expected %d", resp.Status, expectedCode) - } - - // get latest update through bzz-feed directly - log.Info("get update 1.2") - testBzzResUrl = fmt.Sprintf("%s/bzz-feed:/%s", srv.URL, correctManifestAddrHex) - resp, err = http.Get(testBzzResUrl) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Fatalf("err %s", resp.Status) - } - b, err = ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(update2Data, b) { - t.Fatalf("Expected body '%x', got '%x'", update2Data, b) - } - - // test manifest-less queries - log.Info("get first update in update1Timestamp via direct query") - query := feed.NewQuery(&updateRequest.Feed, update1Timestamp, lookup.NoClue) - - urlq, err := url.Parse(fmt.Sprintf("%s/bzz-feed:/", srv.URL)) - if err != nil { - t.Fatal(err) - } - - values := urlq.Query() - query.AppendValues(values) // this adds feed query parameters - urlq.RawQuery = values.Encode() - resp, err = http.Get(urlq.String()) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Fatalf("err %s", resp.Status) - } - b, err = ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(update1Data, b) { - t.Fatalf("Expected body '%x', got '%x'", update1Data, b) - } - -} - -func TestBzzGetPath(t *testing.T) { - testBzzGetPath(false, t) - testBzzGetPath(true, t) -} - -func testBzzGetPath(encrypted bool, t *testing.T) { - var err error - - testmanifest := []string{ - `{"entries":[{"path":"b","hash":"011b4d03dd8c01f1049143cf9c4c817e4b167f1d1b83e5c6f0f10d89ba1e7bce","contentType":"","status":0},{"path":"c","hash":"011b4d03dd8c01f1049143cf9c4c817e4b167f1d1b83e5c6f0f10d89ba1e7bce","contentType":"","status":0}]}`, - `{"entries":[{"path":"a","hash":"011b4d03dd8c01f1049143cf9c4c817e4b167f1d1b83e5c6f0f10d89ba1e7bce","contentType":"","status":0},{"path":"b/","hash":"<key0>","contentType":"application/bzz-manifest+json","status":0}]}`, - `{"entries":[{"path":"a/","hash":"<key1>","contentType":"application/bzz-manifest+json","status":0}]}`, - } - - testrequests := make(map[string]int) - testrequests["/"] = 2 - testrequests["/a/"] = 1 - testrequests["/a/b/"] = 0 - testrequests["/x"] = 0 - testrequests[""] = 0 - - expectedfailrequests := []string{"", "/x"} - - reader := [3]*bytes.Reader{} - - addr := [3]storage.Address{} - - srv := NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - - for i, mf := range testmanifest { - reader[i] = bytes.NewReader([]byte(mf)) - var wait func(context.Context) error - ctx := context.TODO() - addr[i], wait, err = srv.FileStore.Store(ctx, reader[i], int64(len(mf)), encrypted) - if err != nil { - t.Fatal(err) - } - for j := i + 1; j < len(testmanifest); j++ { - testmanifest[j] = strings.Replace(testmanifest[j], fmt.Sprintf("<key%v>", i), addr[i].Hex(), -1) - } - err = wait(ctx) - if err != nil { - t.Fatal(err) - } - } - - rootRef := addr[2].Hex() - - _, err = http.Get(srv.URL + "/bzz-raw:/" + rootRef + "/a") - if err != nil { - t.Fatalf("Failed to connect to proxy: %v", err) - } - - for k, v := range testrequests { - var resp *http.Response - var respbody []byte - - url := srv.URL + "/bzz-raw:/" - if k != "" { - url += rootRef + "/" + k[1:] + "?content_type=text/plain" - } - 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 err != nil { - t.Fatalf("Error while reading response body: %v", err) - } - - if string(respbody) != testmanifest[v] { - isexpectedfailrequest := false - - for _, r := range expectedfailrequests { - if k == r { - isexpectedfailrequest = true - } - } - if !isexpectedfailrequest { - t.Fatalf("Response body does not match, expected: %v, got %v", testmanifest[v], string(respbody)) - } - } - } - - for k, v := range testrequests { - var resp *http.Response - var respbody []byte - - url := srv.URL + "/bzz-hash:/" - if k != "" { - url += rootRef + "/" + 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 err != nil { - t.Fatalf("Read request body: %v", err) - } - - if string(respbody) != addr[v].Hex() { - isexpectedfailrequest := false - - for _, r := range expectedfailrequests { - if k == r { - isexpectedfailrequest = true - } - } - if !isexpectedfailrequest { - t.Fatalf("Response body does not match, expected: %v, got %v", addr[v], string(respbody)) - } - } - } - - ref := addr[2].Hex() - - for _, c := range []struct { - path string - json string - pageFragments []string - }{ - { - path: "/", - json: `{"common_prefixes":["a/"]}`, - pageFragments: []string{ - fmt.Sprintf("Swarm index of bzz:/%s/", ref), - `<a class="normal-link" href="a/">a/</a>`, - }, - }, - { - path: "/a/", - json: `{"common_prefixes":["a/b/"],"entries":[{"hash":"011b4d03dd8c01f1049143cf9c4c817e4b167f1d1b83e5c6f0f10d89ba1e7bce","path":"a/a","mod_time":"0001-01-01T00:00:00Z"}]}`, - pageFragments: []string{ - fmt.Sprintf("Swarm index of bzz:/%s/a/", ref), - `<a class="normal-link" href="b/">b/</a>`, - fmt.Sprintf(`<a class="normal-link" href="/bzz:/%s/a/a">a</a>`, ref), - }, - }, - { - path: "/a/b/", - json: `{"entries":[{"hash":"011b4d03dd8c01f1049143cf9c4c817e4b167f1d1b83e5c6f0f10d89ba1e7bce","path":"a/b/b","mod_time":"0001-01-01T00:00:00Z"},{"hash":"011b4d03dd8c01f1049143cf9c4c817e4b167f1d1b83e5c6f0f10d89ba1e7bce","path":"a/b/c","mod_time":"0001-01-01T00:00:00Z"}]}`, - pageFragments: []string{ - fmt.Sprintf("Swarm index of bzz:/%s/a/b/", ref), - fmt.Sprintf(`<a class="normal-link" href="/bzz:/%s/a/b/b">b</a>`, ref), - fmt.Sprintf(`<a class="normal-link" href="/bzz:/%s/a/b/c">c</a>`, ref), - }, - }, - { - path: "/x", - }, - { - path: "", - }, - } { - k := c.path - url := srv.URL + "/bzz-list:/" - if k != "" { - url += rootRef + "/" + k[1:] - } - t.Run("json list "+c.path, func(t *testing.T) { - resp, err := http.Get(url) - if err != nil { - t.Fatalf("HTTP request: %v", err) - } - defer resp.Body.Close() - respbody, err := ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatalf("Read response body: %v", err) - } - - body := strings.TrimSpace(string(respbody)) - if body != c.json { - isexpectedfailrequest := false - - for _, r := range expectedfailrequests { - if k == r { - isexpectedfailrequest = true - } - } - if !isexpectedfailrequest { - t.Errorf("Response list body %q does not match, expected: %v, got %v", k, c.json, body) - } - } - }) - t.Run("html list "+c.path, func(t *testing.T) { - req, err := http.NewRequest(http.MethodGet, url, nil) - if err != nil { - t.Fatalf("New request: %v", err) - } - req.Header.Set("Accept", "text/html") - resp, err := http.DefaultClient.Do(req) - if err != nil { - t.Fatalf("HTTP request: %v", err) - } - defer resp.Body.Close() - b, err := ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatalf("Read response body: %v", err) - } - - body := string(b) - - for _, f := range c.pageFragments { - if !strings.Contains(body, f) { - isexpectedfailrequest := false - - for _, r := range expectedfailrequests { - if k == r { - isexpectedfailrequest = true - } - } - if !isexpectedfailrequest { - t.Errorf("Response list body %q does not contain %q: body %q", k, f, body) - } - } - } - }) - } - - nonhashtests := []string{ - srv.URL + "/bzz:/name", - srv.URL + "/bzz-immutable:/nonhash", - srv.URL + "/bzz-raw:/nonhash", - srv.URL + "/bzz-list:/nonhash", - srv.URL + "/bzz-hash:/nonhash", - } - - nonhashresponses := []string{ - `cannot resolve name: no DNS to resolve name: "name"`, - `cannot resolve nonhash: no DNS to resolve name: "nonhash"`, - `cannot resolve nonhash: no DNS to resolve name: "nonhash"`, - `cannot resolve nonhash: no DNS to resolve name: "nonhash"`, - `cannot resolve nonhash: no DNS to resolve name: "nonhash"`, - } - - for i, url := range nonhashtests { - var resp *http.Response - var respbody []byte - - 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 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)) - } - } -} - -func TestBzzTar(t *testing.T) { - testBzzTar(false, t) - testBzzTar(true, t) -} - -func testBzzTar(encrypted bool, t *testing.T) { - srv := NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - fileNames := []string{"tmp1.txt", "tmp2.lock", "tmp3.rtf"} - fileContents := []string{"tmp1textfilevalue", "tmp2lockfilelocked", "tmp3isjustaplaintextfile"} - - buf := &bytes.Buffer{} - tw := tar.NewWriter(buf) - defer tw.Close() - - for i, v := range fileNames { - size := int64(len(fileContents[i])) - hdr := &tar.Header{ - Name: v, - Mode: 0644, - Size: size, - ModTime: time.Now(), - Xattrs: map[string]string{ - "user.swarm.content-type": "text/plain", - }, - } - if err := tw.WriteHeader(hdr); err != nil { - t.Fatal(err) - } - - // copy the file into the tar stream - n, err := io.Copy(tw, bytes.NewBufferString(fileContents[i])) - if err != nil { - t.Fatal(err) - } else if n != size { - t.Fatal("size mismatch") - } - } - - //post tar stream - url := srv.URL + "/bzz:/" - if encrypted { - url = url + "encrypt" - } - req, err := http.NewRequest("POST", url, buf) - if err != nil { - t.Fatal(err) - } - req.Header.Add("Content-Type", "application/x-tar") - req.Header.Add(SwarmTagHeaderName, "test-upload") - client := &http.Client{} - resp2, err := client.Do(req) - if err != nil { - t.Fatal(err) - } - if resp2.StatusCode != http.StatusOK { - t.Fatalf("err %s", resp2.Status) - } - - // check that the tag was written correctly - tag := srv.Tags.All()[0] - testutil.CheckTag(t, tag, 4, 4, 0, 4) - - swarmHash, err := ioutil.ReadAll(resp2.Body) - resp2.Body.Close() - if err != nil { - t.Fatal(err) - } - - // now do a GET to get a tarball back - req, err = http.NewRequest("GET", fmt.Sprintf(srv.URL+"/bzz:/%s", string(swarmHash)), nil) - if err != nil { - t.Fatal(err) - } - req.Header.Add("Accept", "application/x-tar") - resp2, err = client.Do(req) - if err != nil { - t.Fatal(err) - } - defer resp2.Body.Close() - - if h := resp2.Header.Get("Content-Type"); h != "application/x-tar" { - t.Fatalf("Content-Type header expected: application/x-tar, got: %s", h) - } - - expectedFileName := string(swarmHash) + ".tar" - expectedContentDisposition := fmt.Sprintf("inline; filename=\"%s\"", expectedFileName) - if h := resp2.Header.Get("Content-Disposition"); h != expectedContentDisposition { - t.Fatalf("Content-Disposition header expected: %s, got: %s", expectedContentDisposition, h) - } - - file, err := ioutil.TempFile("", "swarm-downloaded-tarball") - if err != nil { - t.Fatal(err) - } - defer os.Remove(file.Name()) - _, err = io.Copy(file, resp2.Body) - if err != nil { - t.Fatalf("error getting tarball: %v", err) - } - file.Sync() - file.Close() - - tarFileHandle, err := os.Open(file.Name()) - if err != nil { - t.Fatal(err) - } - tr := tar.NewReader(tarFileHandle) - - for { - hdr, err := tr.Next() - if err == io.EOF { - break - } else if err != nil { - t.Fatalf("error reading tar stream: %s", err) - } - bb := make([]byte, hdr.Size) - _, err = tr.Read(bb) - if err != nil && err != io.EOF { - t.Fatal(err) - } - passed := false - for i, v := range fileNames { - if v == hdr.Name { - if string(bb) == fileContents[i] { - passed = true - break - } - } - } - if !passed { - t.Fatalf("file %s did not pass content assertion", hdr.Name) - } - } - - // now check the tags endpoint -} - -// TestBzzCorrectTagEstimate checks that the HTTP middleware sets the total number of chunks -// in the tag according to an estimate from the HTTP request Content-Length header divided -// by chunk size (4096). It is needed to be checked BEFORE chunking is done, therefore -// concurrency was introduced to slow down the HTTP request -func TestBzzCorrectTagEstimate(t *testing.T) { - srv := NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - - for _, v := range []struct { - toEncrypt bool - expChunks int64 - }{ - {toEncrypt: false, expChunks: 248}, - {toEncrypt: true, expChunks: 250}, - } { - pr, pw := io.Pipe() - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - addr := "" - if v.toEncrypt { - addr = "encrypt" - } - req, err := http.NewRequest("POST", srv.URL+"/bzz:/"+addr, pr) - if err != nil { - t.Fatal(err) - } - - req = req.WithContext(ctx) - req.ContentLength = 1000000 - req.Header.Add(SwarmTagHeaderName, "1000000") - - go func() { - for { - select { - case <-ctx.Done(): - return - case <-time.After(1 * time.Millisecond): - _, err := pw.Write([]byte{0}) - if err != nil { - t.Error(err) - } - } - } - }() - go func() { - transport := http.DefaultTransport - _, err := transport.RoundTrip(req) - if err != nil { - t.Error(err) - } - }() - done := false - for !done { - switch len(srv.Tags.All()) { - case 0: - <-time.After(10 * time.Millisecond) - case 1: - tag := srv.Tags.All()[0] - testutil.CheckTag(t, tag, 0, 0, 0, v.expChunks) - srv.Tags.Delete(tag.Uid) - done = true - } - } - } -} - -// TestBzzRootRedirect tests that getting the root path of a manifest without -// a trailing slash gets redirected to include the trailing slash so that -// relative URLs work as expected. -func TestBzzRootRedirect(t *testing.T) { - testBzzRootRedirect(false, t) -} -func TestBzzRootRedirectEncrypted(t *testing.T) { - testBzzRootRedirect(true, t) -} - -func testBzzRootRedirect(toEncrypt bool, t *testing.T) { - srv := NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - - // create a manifest with some data at the root path - data := []byte("data") - headers := map[string]string{"Content-Type": "text/plain"} - res, hash := httpDo("POST", srv.URL+"/bzz:/", bytes.NewReader(data), headers, false, t) - if res.StatusCode != http.StatusOK { - t.Fatalf("unexpected status code from server %d want %d", res.StatusCode, http.StatusOK) - } - - // define a CheckRedirect hook which ensures there is only a single - // redirect to the correct URL - redirected := false - httpClient := http.Client{ - CheckRedirect: func(req *http.Request, via []*http.Request) error { - if redirected { - return errors.New("too many redirects") - } - redirected = true - expectedPath := "/bzz:/" + hash + "/" - if req.URL.Path != expectedPath { - return fmt.Errorf("expected redirect to %q, got %q", expectedPath, req.URL.Path) - } - return nil - }, - } - - // perform the GET request and assert the response - res, err := httpClient.Get(srv.URL + "/bzz:/" + hash) - if err != nil { - t.Fatal(err) - } - defer res.Body.Close() - if !redirected { - t.Fatal("expected GET /bzz:/<hash> to redirect to /bzz:/<hash>/ but it didn't") - } - gotData, err := ioutil.ReadAll(res.Body) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(gotData, data) { - t.Fatalf("expected response to equal %q, got %q", data, gotData) - } -} - -func TestMethodsNotAllowed(t *testing.T) { - srv := NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - databytes := "bar" - for _, c := range []struct { - url string - code int - }{ - { - url: fmt.Sprintf("%s/bzz-list:/", srv.URL), - code: http.StatusMethodNotAllowed, - }, { - url: fmt.Sprintf("%s/bzz-hash:/", srv.URL), - code: http.StatusMethodNotAllowed, - }, - { - url: fmt.Sprintf("%s/bzz-immutable:/", srv.URL), - code: http.StatusMethodNotAllowed, - }, - } { - res, _ := http.Post(c.url, "text/plain", bytes.NewReader([]byte(databytes))) - if res.StatusCode != c.code { - t.Fatalf("should have failed. requested url: %s, expected code %d, got %d", c.url, c.code, res.StatusCode) - } - } - -} - -func httpDo(httpMethod string, url string, reqBody io.Reader, headers map[string]string, verbose bool, t *testing.T) (*http.Response, string) { - // Build the Request - req, err := http.NewRequest(httpMethod, url, reqBody) - if err != nil { - t.Fatal(err) - } - for key, value := range headers { - req.Header.Set(key, value) - } - if verbose { - t.Log(req.Method, req.URL, req.Header, req.Body) - } - - // Send Request out - httpClient := &http.Client{} - res, err := httpClient.Do(req) - if err != nil { - t.Fatal(err) - } - - // Read the HTTP Body - buffer, err := ioutil.ReadAll(res.Body) - if err != nil { - t.Fatal(err) - } - defer res.Body.Close() - body := string(buffer) - - return res, body -} - -func TestGet(t *testing.T) { - srv := NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - - for _, testCase := range []struct { - uri string - method string - headers map[string]string - expectedStatusCode int - assertResponseBody string - verbose bool - }{ - { - uri: fmt.Sprintf("%s/", srv.URL), - method: "GET", - headers: map[string]string{"Accept": "text/html"}, - expectedStatusCode: http.StatusOK, - assertResponseBody: "Swarm provides censorship resistant storage and communication infrastructure for a sovereign digital society", - verbose: false, - }, - { - uri: fmt.Sprintf("%s/", srv.URL), - method: "GET", - headers: map[string]string{"Accept": "application/json"}, - expectedStatusCode: http.StatusOK, - assertResponseBody: "Swarm: Please request a valid ENS or swarm hash with the appropriate bzz scheme", - verbose: false, - }, - { - uri: fmt.Sprintf("%s/robots.txt", srv.URL), - method: "GET", - headers: map[string]string{"Accept": "text/html"}, - expectedStatusCode: http.StatusOK, - assertResponseBody: "User-agent: *\nDisallow: /", - verbose: false, - }, - { - uri: fmt.Sprintf("%s/nonexistent_path", srv.URL), - method: "GET", - headers: map[string]string{}, - expectedStatusCode: http.StatusNotFound, - verbose: false, - }, - { - uri: fmt.Sprintf("%s/bzz:asdf/", srv.URL), - method: "GET", - headers: map[string]string{}, - expectedStatusCode: http.StatusNotFound, - verbose: false, - }, - { - uri: fmt.Sprintf("%s/tbz2/", srv.URL), - method: "GET", - headers: map[string]string{}, - expectedStatusCode: http.StatusNotFound, - verbose: false, - }, - { - uri: fmt.Sprintf("%s/bzz-rack:/", srv.URL), - method: "GET", - headers: map[string]string{}, - expectedStatusCode: http.StatusNotFound, - verbose: false, - }, - { - uri: fmt.Sprintf("%s/bzz-ls", srv.URL), - method: "GET", - headers: map[string]string{}, - expectedStatusCode: http.StatusNotFound, - verbose: false, - }} { - t.Run("GET "+testCase.uri, func(t *testing.T) { - res, body := httpDo(testCase.method, testCase.uri, nil, testCase.headers, testCase.verbose, t) - if res.StatusCode != testCase.expectedStatusCode { - t.Fatalf("expected status code %d but got %d", testCase.expectedStatusCode, res.StatusCode) - } - if testCase.assertResponseBody != "" && !strings.Contains(body, testCase.assertResponseBody) { - t.Fatalf("expected response to be: %s but got: %s", testCase.assertResponseBody, body) - } - }) - } -} - -func TestModify(t *testing.T) { - srv := NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - headers := map[string]string{"Content-Type": "text/plain"} - res, hash := httpDo("POST", srv.URL+"/bzz:/", bytes.NewReader([]byte("data")), headers, false, t) - if res.StatusCode != http.StatusOK { - t.Fatalf("unexpected status code from server %d want %d", res.StatusCode, http.StatusOK) - } - - for _, testCase := range []struct { - uri string - method string - headers map[string]string - requestBody []byte - expectedStatusCode int - assertResponseBody string - assertResponseHeaders map[string]string - verbose bool - }{ - { - uri: fmt.Sprintf("%s/bzz:/%s", srv.URL, hash), - method: "DELETE", - headers: map[string]string{}, - expectedStatusCode: http.StatusOK, - assertResponseBody: "8b634aea26eec353ac0ecbec20c94f44d6f8d11f38d4578a4c207a84c74ef731", - verbose: false, - }, - { - uri: fmt.Sprintf("%s/bzz:/%s", srv.URL, hash), - method: "PUT", - headers: map[string]string{}, - expectedStatusCode: http.StatusMethodNotAllowed, - verbose: false, - }, - { - uri: fmt.Sprintf("%s/bzz-raw:/%s", srv.URL, hash), - method: "PUT", - headers: map[string]string{}, - expectedStatusCode: http.StatusMethodNotAllowed, - verbose: false, - }, - { - uri: fmt.Sprintf("%s/bzz:/%s", srv.URL, hash), - method: "PATCH", - headers: map[string]string{}, - expectedStatusCode: http.StatusMethodNotAllowed, - verbose: false, - }, - { - uri: fmt.Sprintf("%s/bzz-raw:/", srv.URL), - method: "POST", - headers: map[string]string{}, - requestBody: []byte("POSTdata"), - expectedStatusCode: http.StatusOK, - assertResponseHeaders: map[string]string{"Content-Length": "64"}, - verbose: false, - }, - { - uri: fmt.Sprintf("%s/bzz-raw:/encrypt", srv.URL), - method: "POST", - headers: map[string]string{}, - requestBody: []byte("POSTdata"), - expectedStatusCode: http.StatusOK, - assertResponseHeaders: map[string]string{"Content-Length": "128"}, - verbose: false, - }, - } { - t.Run(testCase.method+" "+testCase.uri, func(t *testing.T) { - reqBody := bytes.NewReader(testCase.requestBody) - res, body := httpDo(testCase.method, testCase.uri, reqBody, testCase.headers, testCase.verbose, t) - - if res.StatusCode != testCase.expectedStatusCode { - t.Fatalf("expected status code %d but got %d, %s", testCase.expectedStatusCode, res.StatusCode, body) - } - if testCase.assertResponseBody != "" && !strings.Contains(body, testCase.assertResponseBody) { - t.Log(body) - t.Fatalf("expected response %s but got %s", testCase.assertResponseBody, body) - } - for key, value := range testCase.assertResponseHeaders { - if res.Header.Get(key) != value { - t.Logf("expected %s=%s in HTTP response header but got %s", key, value, res.Header.Get(key)) - } - } - }) - } -} - -func TestMultiPartUpload(t *testing.T) { - // POST /bzz:/ Content-Type: multipart/form-data - verbose := false - // Setup Swarm - srv := NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - - url := fmt.Sprintf("%s/bzz:/", srv.URL) - - buf := new(bytes.Buffer) - form := multipart.NewWriter(buf) - form.WriteField("name", "John Doe") - file1, _ := form.CreateFormFile("cv", "cv.txt") - file1.Write([]byte("John Doe's Credentials")) - file2, _ := form.CreateFormFile("profile_picture", "profile.jpg") - file2.Write([]byte("imaginethisisjpegdata")) - form.Close() - - headers := map[string]string{ - "Content-Type": form.FormDataContentType(), - "Content-Length": strconv.Itoa(buf.Len()), - } - res, body := httpDo("POST", url, buf, headers, verbose, t) - - if res.StatusCode != http.StatusOK { - t.Fatalf("expected POST multipart/form-data to return 200, but it returned %d", res.StatusCode) - } - if len(body) != 64 { - t.Fatalf("expected POST multipart/form-data to return a 64 char manifest but the answer was %d chars long", len(body)) - } -} - -// TestBzzGetFileWithResolver tests fetching a file using a mocked ENS resolver -func TestBzzGetFileWithResolver(t *testing.T) { - resolver := newTestResolveValidator("") - srv := NewTestSwarmServer(t, serverFunc, resolver) - defer srv.Close() - fileNames := []string{"dir1/tmp1.txt", "dir2/tmp2.lock", "dir3/tmp3.rtf"} - fileContents := []string{"tmp1textfilevalue", "tmp2lockfilelocked", "tmp3isjustaplaintextfile"} - - buf := &bytes.Buffer{} - tw := tar.NewWriter(buf) - - for i, v := range fileNames { - size := len(fileContents[i]) - hdr := &tar.Header{ - Name: v, - Mode: 0644, - Size: int64(size), - ModTime: time.Now(), - Xattrs: map[string]string{ - "user.swarm.content-type": "text/plain", - }, - } - if err := tw.WriteHeader(hdr); err != nil { - t.Fatal(err) - } - - // copy the file into the tar stream - n, err := io.WriteString(tw, fileContents[i]) - if err != nil { - t.Fatal(err) - } else if n != size { - t.Fatal("size mismatch") - } - } - - if err := tw.Close(); err != nil { - t.Fatal(err) - } - - //post tar stream - url := srv.URL + "/bzz:/" - - req, err := http.NewRequest("POST", url, buf) - if err != nil { - t.Fatal(err) - } - req.Header.Add("Content-Type", "application/x-tar") - client := &http.Client{} - serverResponse, err := client.Do(req) - if err != nil { - t.Fatal(err) - } - if serverResponse.StatusCode != http.StatusOK { - t.Fatalf("err %s", serverResponse.Status) - } - swarmHash, err := ioutil.ReadAll(serverResponse.Body) - serverResponse.Body.Close() - if err != nil { - t.Fatal(err) - } - // set the resolved hash to be the swarm hash of what we've just uploaded - hash := common.HexToHash(string(swarmHash)) - resolver.hash = &hash - for _, v := range []struct { - addr string - path string - expectedStatusCode int - expectedContentType string - expectedFileName string - }{ - { - addr: string(swarmHash), - path: fileNames[0], - expectedStatusCode: http.StatusOK, - expectedContentType: "text/plain", - expectedFileName: path.Base(fileNames[0]), - }, - { - addr: "somebogusensname", - path: fileNames[0], - expectedStatusCode: http.StatusOK, - expectedContentType: "text/plain", - expectedFileName: path.Base(fileNames[0]), - }, - } { - req, err := http.NewRequest("GET", fmt.Sprintf(srv.URL+"/bzz:/%s/%s", v.addr, v.path), nil) - if err != nil { - t.Fatal(err) - } - serverResponse, err := client.Do(req) - if err != nil { - t.Fatal(err) - } - defer serverResponse.Body.Close() - if serverResponse.StatusCode != v.expectedStatusCode { - t.Fatalf("expected %d, got %d", v.expectedStatusCode, serverResponse.StatusCode) - } - - if h := serverResponse.Header.Get("Content-Type"); h != v.expectedContentType { - t.Fatalf("Content-Type header expected: %s, got %s", v.expectedContentType, h) - } - - expectedContentDisposition := fmt.Sprintf("inline; filename=\"%s\"", v.expectedFileName) - if h := serverResponse.Header.Get("Content-Disposition"); h != expectedContentDisposition { - t.Fatalf("Content-Disposition header expected: %s, got: %s", expectedContentDisposition, h) - } - - } -} - -// TestCalculateNumberOfChunks is a unit test for the chunk-number-according-to-content-length -// calculation -func TestCalculateNumberOfChunks(t *testing.T) { - - //test cases: - for _, tc := range []struct{ len, chunks int64 }{ - {len: 1000, chunks: 1}, - {len: 5000, chunks: 3}, - {len: 10000, chunks: 4}, - {len: 100000, chunks: 26}, - {len: 1000000, chunks: 248}, - {len: 325839339210, chunks: 79550620 + 621490 + 4856 + 38 + 1}, - } { - res := calculateNumberOfChunks(tc.len, false) - if res != tc.chunks { - t.Fatalf("expected result for %d bytes to be %d got %d", tc.len, tc.chunks, res) - } - } -} - -// TestCalculateNumberOfChunksEncrypted is a unit test for the chunk-number-according-to-content-length -// calculation with encryption (branching factor=64) -func TestCalculateNumberOfChunksEncrypted(t *testing.T) { - - //test cases: - for _, tc := range []struct{ len, chunks int64 }{ - {len: 1000, chunks: 1}, - {len: 5000, chunks: 3}, - {len: 10000, chunks: 4}, - {len: 100000, chunks: 26}, - {len: 1000000, chunks: 245 + 4 + 1}, - {len: 325839339210, chunks: 79550620 + 1242979 + 19422 + 304 + 5 + 1}, - } { - res := calculateNumberOfChunks(tc.len, true) - if res != tc.chunks { - t.Fatalf("expected result for %d bytes to be %d got %d", tc.len, tc.chunks, res) - } - } -} - -// testResolver implements the Resolver interface and either returns the given -// hash if it is set, or returns a "name not found" error -type testResolveValidator struct { - hash *common.Hash -} - -func newTestResolveValidator(addr string) *testResolveValidator { - r := &testResolveValidator{} - if addr != "" { - hash := common.HexToHash(addr) - r.hash = &hash - } - return r -} - -func (t *testResolveValidator) Resolve(addr string) (common.Hash, error) { - if t.hash == nil { - return common.Hash{}, fmt.Errorf("DNS name not found: %q", addr) - } - return *t.hash, nil -} - -func (t *testResolveValidator) Owner(node [32]byte) (addr common.Address, err error) { - return -} - -func (t *testResolveValidator) HeaderByNumber(context.Context, *big.Int) (header *types.Header, err error) { - return -} diff --git a/swarm/api/http/templates.go b/swarm/api/http/templates.go deleted file mode 100644 index 19c4040ca..000000000 --- a/swarm/api/http/templates.go +++ /dev/null @@ -1,306 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. - -package http - -import ( - "encoding/hex" - "fmt" - "html/template" - "path" - - "github.com/ethereum/go-ethereum/swarm/api" -) - -type htmlListData struct { - URI *api.URI - List *api.ManifestList -} - -var TemplatesMap = make(map[string]*template.Template) -var faviconBytes []byte - -func init() { - for _, v := range []struct { - templateName string - partial string - funcs template.FuncMap - }{ - { - templateName: "error", - partial: errorResponse, - }, - { - templateName: "bzz-list", - partial: bzzList, - funcs: template.FuncMap{ - "basename": path.Base, - "leaflink": leafLink, - }, - }, - { - templateName: "landing-page", - partial: landing, - }, - } { - TemplatesMap[v.templateName] = template.Must(template.New(v.templateName).Funcs(v.funcs).Parse(baseTemplate + css + v.partial + logo)) - } - - bytes, err := hex.DecodeString(favicon) - if err != nil { - panic(err) - } - faviconBytes = bytes -} - -func leafLink(URI api.URI, manifestEntry api.ManifestEntry) string { - return fmt.Sprintf("/bzz:/%s/%s", URI.Addr, manifestEntry.Path) -} - -const bzzList = `{{ define "content" }} -<h3 class="top-space">Swarm index of {{ .URI }}</h3> -<hr> -<table> - <thead> - <tr> - <th>Path</th> - <th>Type</th> - <th>Size</th> - </tr> - </thead> - - <tbody> - {{ range .List.CommonPrefixes }} - <tr> - <td> - <a class="normal-link" href="{{ basename . }}/">{{ basename . }}/</a> - </td> - <td>DIR</td> - <td>-</td> - </tr> - {{ end }} - {{ range .List.Entries }} - <tr> - <td> - <a class="normal-link" href="{{ leaflink $.URI . }}">{{ basename .Path }}</a> - </td> - <td>{{ .ContentType }}</td> - <td>{{ .Size }}</td> - </tr> - {{ end }} -</table> -<hr> - - {{ end }}` - -const errorResponse = `{{ define "content" }} -<div class="container"> - <div class="logo"> - {{ template "logo" . }} - </div> - - <div class="separate-block"> - <h3>{{.Msg}}</h3> - </div> - - <div> - <h5>Error code: {{.Code}}</h5> - </div> - - <div class="footer"> - <p>{{.Timestamp}}</p> - <p>Swarm provides censorship resistant storage and communication infrastructure for a sovereign digital society</p> - </div> - </div> -{{ end }}` - -const landing = `{{ define "content" }} - -<script type="text/javascript"> -function goToPage() { - var page = document.getElementById('page').value; - if (page == "") { - var page = "theswarm.eth" - } - var address = "/bzz:/" + page; - location.href = address; - console.log(address) -} -</script> - -<div class="container"> -<div class="logo"> - {{ template "logo" . }} -</div> - - -<div class="searchbar"> - <form class="separate-block" action="javascript:goToPage();"> - <input type="text" id="page" autofocus name="search" placeholder="Please enter an ENS name or swarm hash to retrieve .."> - <button class="button" type="submit" value="submit" onclick="goToPage();">Go!</button> - </form> -</div> -<div class="footer"> -<p>{{.Timestamp}}</p> -<p>Swarm provides censorship resistant storage and communication infrastructure for a sovereign digital society</p> -</div> -</div> - -{{ end }}` - -const baseTemplate = `<html> -<head> - <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> - <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0"/> - <meta http-equiv="X-UA-Compatible" ww="chrome=1"/> - <link rel="icon" type="image/x-icon" href="favicon.ico"/> - <style> - {{ template "css" . }} - </style> -</head> -<body> - {{ template "content" . }} -</body> -</html> -` - -const css = `{{ define "css" }} -html { - font-size: 18px; - font-size: 1.13rem; - -webkit-text-size-adjust: 100%; - -ms-text-size-adjust: 100%; - font-family: Helvetica, Arial, sans-serif; -} - -body { - background: #f6f6f6; - color: #333; -} - -a, a:visited, a:active { - color: darkorange; -} - -a.normal-link, a.normal-link:active { color: #0000EE; } -a.normal-link:visited { color: #551A8B; } - -table { - border-collapse: separate; -} - -td { - padding: 3px 10px; -} - - -.container { - max-width: 600px; - margin: 40px auto 40px; - text-align: center; -} - -.separate-block { - margin: 40px 0; - word-wrap: break-word; -} - -.footer { - font-size: 12px; - font-size: 0.75rem; - text-align: center; -} - -.orange { - color: #ffa500; -} - -.top-space { - margin-top: 20px; - margin-bottom: 20px; -} - -/* SVG Logos, editable */ - -.searchbar { - padding: 20px 20px 0; -} - -.logo { - margin: 100px 80px 0; -} - -.logo a img { - max-width: 140px; -} - -/* Tablet < 600p*/ - -@media only screen and (max-width: 600px) {} - -/* Mobile phone < 360p*/ - -@media only screen and (max-width: 360px) { - h1 { - font-size: 20px; - font-size: 1.5rem; - } - h2 { - font-size: 0.88rem; - margin: 0; - } - .logo { - margin: 50px 40px 0; - } - .footer { - font-size: 0.63rem; - text-align: center; - } -} - -input[type=text] { - width: 100%; - box-sizing: border-box; - border: 2px solid #777; - border-radius: 2px; - font-size: 16px; - padding: 12px 20px 12px 20px; - transition: border 250ms ease-in-out; -} - -input[type=text]:focus { - border: 2px solid #ffce73; -} - -.button { - background-color: #ffa500; - margin: 20px 0; - border: none; - border-radius: 2px; - color: #222; - padding: 15px 32px; - text-align: center; - text-decoration: none; - display: inline-block; - font-size: 16px; -} -{{ end }}` - -const logo = `{{ define "logo" }} -<a href="/bzz:/theswarm.eth"><img src="data:image/png;base64,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"/></a> -{{ end }}` - -const favicon = `000001000400101000000000200068040000460000002020000000002000a8100000ae0400003030000000002000a825000056150000404000000000200028420000fe3a000028000000100000002000000001002000000000004004000000000000000000000000000000000000ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff017e7e7e0362626263545454c548484849ffffff01ffffff01ffffff01ffffff01646464375b5b5bbf4545457758585809ffffff01ffffff01ffffff0164646443626262cf626262ff535353ff454545ff454545b74949492b6868681d626262a5626262fd5c5c5cff464646ff454545dd47474755ffffff01ffffff013f3f3feb565656ff636363ff535353ff464646ff3f3f3fff373737ab393939894d4d4dff626262ff5c5c5cff464646ff424242ff3a3a3af7ffffff01ffffff01383838e9353535ff424242ff474747ff383838ff353535ff363636ab35353587363636ff3a3a3aff4a4a4aff3b3b3bff353535ff363636f5ffffff01ffffff01383838e9303030ff181818ff131313ff232323ff343434ff363636ab35353587343434ff202020ff101010ff1d1d1dff303030ff373737f5ffffff01ffffff01232323c50c0c0cff0d0d0dff131313ff171717ff171717ff2929298b2727276b0f0f0ffd0d0d0dff101010ff171717ff161616ff232323d9ffffff01ffffff014d4d4d030f0f0f650c0c0ce7131313ff161616d51d1d1d4b63636363464646691717173b0d0d0dc50f0f0fff161616ef171717752e2e2e07ffffff01ffffff01ffffff01ffffff011d1d1d0f1515155360606045626262cf636363ff464646ff454545d3484848491414144d24242417ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff013c3c3c374f4f4fff636363ff636363ff464646ff464646ff3f3f3fff3c3c3c41ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff013636363d353535ff3c3c3cff575757ff363636ff181818ff282828ff37373747ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff013636363d363636ff303030ff181818ff292929ff131313ef17171771696969136565653bffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01323232371e1e1eff0d0d0dff0c0c0cff363636ff363636a3ffffff0185858515606060ff4747476bffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01111111450d0d0dd10c0c0cff1b1b1bff2a2a2a993e3e3e0b30303085292929ff37373787ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01363636030e0e0e671616166b45454505323232432e2e2ed9151515c31d1d1d2dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff014e4e4e05ffffff01ffffff01ffffff01ffffff010000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff28000000200000004000000001002000000000008010000000000000000000000000000000000000ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff017272721b646464a54646466f72727205ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0168686845575757b74f4f4f39ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff017e7e7e0b6262627d616161f3636363ff424242ff444444d74f4f4f49ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff016c6c6c27636363b5616161ff555555ff434343ff464646a35858581dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff016666665d616161e3626262ff636363ff636363ff444444ff464646ff434343ff454545b95252522bffffff01ffffff01ffffff01ffffff016c6c6c1363636393616161fb636363ff636363ff555555ff464646ff464646ff444444f5464646836666660bffffff01ffffff01ffffff01ffffff01ffffff016a6a6a3f626262c9616161ff636363ff636363ff636363ff636363ff444444ff464646ff464646ff464646ff434343fb48484897545454135b5b5b036868686f616161ef626262ff636363ff636363ff636363ff555555ff464646ff464646ff464646ff454545ff444444e54a4a4a5fffffff01ffffff01ffffff01ffffff013b3b3bd7505050ff646464ff636363ff636363ff636363ff636363ff444444ff464646ff464646ff464646ff454545ff3a3a3aff33333357313131113c3c3cff5a5a5aff646464ff636363ff636363ff636363ff555555ff464646ff464646ff464646ff464646ff424242ff383838f1ffffff01ffffff01ffffff01ffffff013a3a3ad5353535ff3a3a3aff575757ff646464ff626262ff636363ff444444ff464646ff464646ff3d3d3dff353535ff363636ff3636365535353511363636ff343434ff434343ff606060ff636363ff636363ff555555ff464646ff464646ff444444ff393939ff353535ff373737edffffff01ffffff01ffffff01ffffff013a3a3ad5363636ff363636ff343434ff3f3f3fff5d5d5dff646464ff444444ff404040ff363636ff353535ff363636ff363636ff3636365535353511363636ff363636ff363636ff343434ff4a4a4aff636363ff555555ff454545ff3c3c3cff353535ff363636ff363636ff373737edffffff01ffffff01ffffff01ffffff013a3a3ad5363636ff363636ff363636ff363636ff353535ff3f3f3fff363636ff353535ff363636ff363636ff363636ff363636ff3636365535353511363636ff363636ff363636ff363636ff353535ff383838ff3a3a3aff373737ff353535ff363636ff363636ff363636ff373737edffffff01ffffff01ffffff01ffffff013a3a3ad5363636ff363636ff363636ff323232ff181818ff0e0e0eff171717ff282828ff373737ff363636ff363636ff363636ff3636365535353511363636ff363636ff353535ff373737ff292929ff0f0f0fff111111ff1b1b1bff2f2f2fff373737ff363636ff363636ff373737edffffff01ffffff01ffffff01ffffff013a3a3ad5363636ff363636ff1e1e1eff0b0b0bff0d0d0dff0f0f0fff171717ff161616ff191919ff2c2c2cff373737ff363636ff3636365535353511363636ff373737ff2f2f2fff141414ff0b0b0bff0d0d0dff131313ff171717ff151515ff1f1f1fff333333ff363636ff373737edffffff01ffffff01ffffff01ffffff013b3b3bd5252525ff0d0d0dff0c0c0cff0d0d0dff0d0d0dff0f0f0fff171717ff171717ff171717ff151515ff1c1c1cff313131ff3535355734343411333333ff1a1a1aff0b0b0bff0d0d0dff0d0d0dff0d0d0dff131313ff171717ff171717ff171717ff161616ff242424ff373737efffffff01ffffff01ffffff01ffffff012020205d0b0b0be50b0b0bff0d0d0dff0d0d0dff0d0d0dff0f0f0fff171717ff171717ff171717ff171717ff131313ff161616b73333331f3b3b3b05111111970a0a0afb0d0d0dff0d0d0dff0d0d0dff0d0d0dff131313ff171717ff171717ff171717ff161616ff141414f51c1c1c7fffffff01ffffff01ffffff01ffffff01ffffff014d4d4d0b1212127f0a0a0af50d0d0dff0d0d0dff0f0f0fff171717ff171717ff151515ff151515d522222249ffffff017373731b51515121ffffff011d1d1d2b101010b50a0a0aff0d0d0dff0d0d0dff131313ff171717ff171717ff131313ff181818a12e2e2e1dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff012c2c2c1b0f0f0fa10a0a0afd0f0f0fff161616ff141414e91b1b1b69656565057878780b6363637b626262f3464646f7454545896969690fffffff011c1c1c470c0c0cd30b0b0bff131313ff141414ff151515c32a2a2a37ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff011d1d1d35111111bd1a1a1a8d2f2f2f11ffffff0166666659616161e1626262ff646464ff474747ff454545ff444444e9494949677b7b7b054040400517171769131313cd24242455ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0169696939626262c7616161ff636363ff636363ff646464ff474747ff464646ff464646ff444444ff454545d14e4e4e45ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01424242615e5e5eff636363ff636363ff636363ff636363ff646464ff474747ff464646ff464646ff464646ff464646ff434343ff3f3f3f77ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0136363679343434ff494949ff636363ff636363ff636363ff646464ff474747ff464646ff464646ff474747ff3d3d3dff353535ff3a3a3a8dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0136363679363636ff353535ff363636ff505050ff646464ff636363ff474747ff484848ff2f2f2fff1c1c1cff323232ff363636ff3a3a3a8dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0136363679363636ff363636ff363636ff353535ff3a3a3aff5a5a5aff393939ff0f0f0fff040404ff111111ff151515ff232323ff3535358fffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0136363679363636ff363636ff363636ff363636ff323232ff171717ff2a2a2aff0c0c0cff030303ff111111ff141414fb171717992e2e2e17a3a3a305ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0136363679363636ff363636ff363636ff1f1f1fff0b0b0bff0d0d0dff363636ff383838ff242424ff121212bf2a2a2a2dffffff01ffffff018484842b636363bf6d6d6d2fffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0136363679373737ff252525ff0d0d0dff0c0c0cff0d0d0dff0d0d0dff373737ff363636ff353535ff39393949ffffff01ffffff01ffffff0186868629646464ff656565fb6464649b55555505ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff012e2e2e650e0e0eff0c0c0cff0d0d0dff0d0d0dff0d0d0dff0c0c0cff353535ff363636ff353535ff37373749ffffff01ffffff01ffffff0185858529656565ff525252ff353535ff4b4b4b0fffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff011c1c1c430d0d0dcf0b0b0bff0d0d0dff0d0d0dff0d0d0dff171717ff282828ff363636ff37373749ffffff01ffffff01ffffff0144444459363636ff353535ff353535ff4e4e4e0fffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0162626203161616630b0b0be70c0c0cff0d0d0dff171717ff161616ff171717ed3737372fffffff013e3e3e2b303030b72a2a2aff151515ff262626ff363636ff4b4b4b0fffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff013636360d101010850a0a0af7141414f91717178f45454511ffffff014c4c4c252c2c2cdb303030ff2d2d2dff151515ff131313ff1b1b1bad5a5a5a07ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff012b2b2b2121212127ffffff01ffffff01ffffff01ffffff0161616109313131752b2b2bf1131313cd26262641ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff014e4e4e1359595903ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000028000000300000006000000001002000000000008025000000000000000000000000000000000000ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0173737357545454997c7c7c11ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0176767663515151916c6c6c0dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff017676762d636363bb636363ff4d4d4dff434343eb4f4f4f6d7f7f7f05ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0176767635616161c3626262ff494949ff424242e94f4f4f6392929203ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff017e7e7e19626262955f5f5ffd626262ff666666ff4f4f4fff464646ff424242ff434343d75a5a5a49ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff017777771d6464649f5f5f5fff636363ff656565ff4b4b4bff464646ff424242ff444444d158585841ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff018585850966666677606060ef626262ff636363ff636363ff666666ff4f4f4fff464646ff464646ff464646ff414141ff464646b75d5d5d2dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff018989890d6868687f5f5f5ff5626262ff636363ff636363ff656565ff4b4b4bff464646ff464646ff464646ff404040ff484848b160606027ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff016a6a6a55626262df606060ff636363ff636363ff636363ff636363ff666666ff4f4f4fff464646ff464646ff464646ff464646ff454545ff424242fd484848956a6a6a17ffffff01ffffff01ffffff01ffffff01ffffff016969695f606060e3606060ff636363ff636363ff636363ff636363ff656565ff4b4b4bff464646ff464646ff464646ff464646ff454545ff414141f94a4a4a8d65656513ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff016e6e6e3b656565c15f5f5fff636363ff636363ff636363ff636363ff636363ff636363ff666666ff4f4f4fff464646ff464646ff464646ff464646ff464646ff464646ff444444ff424242ed52525277ffffff01ffffff016c6c6c37676767c95f5f5fff636363ff636363ff636363ff636363ff636363ff636363ff656565ff4b4b4bff464646ff464646ff464646ff464646ff464646ff464646ff434343ff444444e94d4d4d6dffffff01ffffff01ffffff01ffffff01ffffff01ffffff013c3c3cc5454545ff646464ff646464ff636363ff636363ff636363ff636363ff636363ff666666ff4f4f4fff464646ff464646ff464646ff464646ff464646ff464646ff474747ff424242ff333333fb34343409ffffff0131313199494949ff656565ff646464ff636363ff636363ff636363ff636363ff636363ff656565ff4b4b4bff464646ff464646ff464646ff464646ff464646ff464646ff474747ff414141ff373737ebffffff01ffffff01ffffff01ffffff01ffffff01ffffff01404040bf333333ff343434ff4f4f4fff666666ff636363ff636363ff636363ff636363ff666666ff4f4f4fff464646ff464646ff464646ff464646ff474747ff444444ff383838ff343434ff363636f737373707ffffff0135353597343434ff343434ff525252ff666666ff636363ff636363ff636363ff636363ff656565ff4b4b4bff464646ff464646ff464646ff464646ff474747ff444444ff383838ff343434ff393939e3ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01404040bf343434ff363636ff333333ff383838ff585858ff676767ff636363ff636363ff666666ff4f4f4fff464646ff464646ff474747ff464646ff3b3b3bff343434ff363636ff363636ff363636f737373707ffffff0135353597363636ff363636ff333333ff383838ff5a5a5aff666666ff636363ff636363ff656565ff4b4b4bff464646ff464646ff474747ff454545ff3a3a3aff343434ff363636ff363636ff393939e3ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01404040bf343434ff363636ff363636ff363636ff323232ff3d3d3dff5d5d5dff666666ff666666ff4f4f4fff464646ff474747ff3e3e3eff353535ff353535ff363636ff363636ff363636ff363636f737373707ffffff0135353597363636ff363636ff363636ff363636ff313131ff3f3f3fff5f5f5fff666666ff656565ff4b4b4bff464646ff474747ff3d3d3dff353535ff353535ff363636ff363636ff363636ff393939e3ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01404040bf343434ff363636ff363636ff363636ff363636ff353535ff323232ff444444ff676767ff525252ff404040ff363636ff353535ff363636ff363636ff363636ff363636ff363636ff363636f737373707ffffff0135353597363636ff363636ff363636ff363636ff363636ff353535ff323232ff464646ff676767ff4e4e4eff404040ff363636ff353535ff363636ff363636ff363636ff363636ff363636ff393939e3ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01404040bf343434ff363636ff363636ff363636ff363636ff363636ff353535ff383838ff2d2d2dff2b2b2bff373737ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636f737373707ffffff0135353597363636ff363636ff363636ff363636ff363636ff363636ff363636ff383838ff2c2c2cff2a2a2aff373737ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff393939e3ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01404040bf343434ff363636ff363636ff363636ff353535ff383838ff343434ff171717ff090909ff151515ff171717ff2d2d2dff383838ff363636ff363636ff363636ff363636ff363636ff363636f737373707ffffff0135353597363636ff363636ff363636ff363636ff353535ff383838ff333333ff151515ff090909ff151515ff181818ff2f2f2fff383838ff363636ff363636ff363636ff363636ff363636ff393939e3ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01404040bf343434ff363636ff363636ff373737ff373737ff1f1f1fff090909ff0c0c0cff0c0c0cff171717ff171717ff141414ff1b1b1bff323232ff383838ff363636ff363636ff363636ff363636f737373707ffffff0135353597363636ff363636ff363636ff373737ff373737ff1d1d1dff0a0a0aff0c0c0cff0c0c0cff171717ff171717ff141414ff1c1c1cff333333ff383838ff353535ff363636ff363636ff393939e3ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01404040bf343434ff363636ff393939ff272727ff0c0c0cff0b0b0bff0d0d0dff0d0d0dff0c0c0cff171717ff171717ff171717ff161616ff141414ff202020ff353535ff373737ff363636ff363636f737373707ffffff0135353597363636ff363636ff383838ff252525ff0b0b0bff0b0b0bff0d0d0dff0d0d0dff0c0c0cff171717ff171717ff171717ff161616ff141414ff222222ff363636ff373737ff363636ff393939e3ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01404040bf383838ff2d2d2dff101010ff0a0a0aff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff171717ff171717ff171717ff171717ff171717ff161616ff141414ff262626ff373737ff373737f737373707ffffff0136363697393939ff2b2b2bff0f0f0fff0a0a0aff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff171717ff171717ff171717ff171717ff171717ff161616ff151515ff272727ff383838ff393939e3ffffff01ffffff01ffffff01ffffff01ffffff01ffffff013a3a3abd131313ff090909ff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff171717ff171717ff171717ff171717ff171717ff171717ff171717ff151515ff171717ff262626fb38383807ffffff012a2a2a97121212ff090909ff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff171717ff171717ff171717ff171717ff171717ff171717ff171717ff151515ff161616ff2a2a2ae7ffffff01ffffff01ffffff01ffffff01ffffff01ffffff015f5f5f0b1616167b090909ef0a0a0aff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff171717ff171717ff171717ff171717ff171717ff171717ff171717ff0f0f0fff181818b74040402dffffff01ffffff014646461118181883080808f30b0b0bff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff171717ff171717ff171717ff171717ff171717ff171717ff161616ff101010ff181818b141414127ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff014d4d4d171212129b090909fd0c0c0cff0d0d0dff0d0d0dff0d0d0dff0c0c0cff171717ff171717ff171717ff171717ff171717ff111111ff141414d335353547ffffff01ffffff01ffffff01ffffff01ffffff01ffffff013838381d131313a5060606ff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff171717ff171717ff171717ff171717ff171717ff111111ff181818cd2e2e2e3dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01333333310f0f0fbb070707ff0d0d0dff0d0d0dff0c0c0cff171717ff171717ff171717ff141414ff121212e72424246d86868603ffffff01ffffff017373732b656565b9464646c95e5e5e3bffffff01ffffff01ffffff01323232370e0e0ec3080808ff0d0d0dff0d0d0dff0c0c0cff171717ff171717ff171717ff121212ff161616e525252563ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff012525254d0e0e0ed9090909ff0c0c0cff171717ff151515ff121212f91d1d1d894d4d4d13ffffff01ffffff0178787815656565935f5f5ffb646464ff484848ff404040ff454545a96a6a6a1fffffff01ffffff01ffffff011b1b1b570e0e0edf080808ff0d0d0dff171717ff151515ff0f0f0ff3212121815656560dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01636363071a1a1a710a0a0aed0f0f0fff1b1b1bad2f2f2f23ffffff01ffffff018d8d8d0566666675616161eb616161ff636363ff646464ff484848ff464646ff454545ff424242f54c4c4c856262620fffffff01ffffff014040400b21212179080808f10f0f0fff1b1b1ba15757571dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff014141411740404037ffffff01ffffff01ffffff016a6a6a4d616161db606060ff636363ff636363ff636363ff646464ff484848ff464646ff464646ff464646ff434343ff434343e751515167ffffff01ffffff01ffffff014646461d30303033ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0176767631616161c35f5f5fff636363ff636363ff636363ff636363ff636363ff646464ff484848ff464646ff464646ff464646ff464646ff464646ff424242ff454545d158585841ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff015252527f636363ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff646464ff484848ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff434343ff454545a1ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01313131b53b3b3bff5b5b5bff676767ff636363ff636363ff636363ff636363ff636363ff646464ff484848ff464646ff464646ff464646ff464646ff464646ff474747ff444444ff393939ff383838d3ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01363636b3363636ff323232ff404040ff616161ff656565ff626262ff636363ff636363ff646464ff484848ff464646ff464646ff454545ff494949ff474747ff3b3b3bff343434ff353535ff3a3a3ad3ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01363636b3363636ff363636ff353535ff323232ff484848ff656565ff646464ff636363ff646464ff484848ff464646ff474747ff494949ff242424ff282828ff383838ff363636ff363636ff3a3a3ad3ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01363636b3363636ff363636ff363636ff363636ff343434ff343434ff515151ff666666ff656565ff484848ff4b4b4bff323232ff070707ff040404ff151515ff181818ff2f2f2fff383838ff3a3a3ad3ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01363636b3363636ff363636ff363636ff363636ff363636ff363636ff333333ff383838ff5f5f5fff3c3c3cff0f0f0fff020202ff050505ff050505ff171717ff171717ff141414ff1c1c1cff323232d7ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01363636b3363636ff363636ff363636ff363636ff363636ff353535ff383838ff343434ff161616ff2a2a2aff0c0c0cff020202ff050505ff050505ff171717ff171717ff101010ff161616bf2e2e2e35ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01363636b3363636ff363636ff363636ff363636ff373737ff383838ff1f1f1fff0a0a0aff0c0c0cff373737ff3a3a3aff262626ff060606ff040404ff121212ff151515dd30303051ffffff01ffffff01ffffff018787872d6b6b6b47ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01363636b3363636ff363636ff363636ff393939ff272727ff0d0d0dff0b0b0bff0d0d0dff0d0d0dff373737ff363636ff373737ff383838ff1c1c1cf92020207568686807ffffff01ffffff01ffffff01ffffff018686863d5f5f5fff676767af77777721ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01363636b3363636ff393939ff2e2e2eff101010ff0a0a0aff0d0d0dff0d0d0dff0d0d0dff0d0d0dff373737ff363636ff363636ff353535ff373737ebffffff01ffffff01ffffff01ffffff01ffffff01ffffff018686863d626262ff666666ff646464f76969698d9494940fffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01383838b5333333ff161616ff090909ff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff373737ff363636ff363636ff363636ff353535ebffffff01ffffff01ffffff01ffffff01ffffff01ffffff018686863d626262ff676767ff6b6b6bff555555ff3a3a3a93ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0125252589030303ff0c0c0cff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff333333ff383838ff353535ff363636ff353535ebffffff01ffffff01ffffff01ffffff01ffffff01ffffff018585853d666666ff5f5f5fff3c3c3cff313131ff3a3a3a93ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff012d2d2d3f0e0e0ecb080808ff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff141414ff222222ff363636ff373737ff353535ebffffff01ffffff01ffffff01ffffff01ffffff01ffffff0177777741414141ff313131ff363636ff353535ff3a3a3a93ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff011e1e1e5f0a0a0ae50a0a0aff0d0d0dff0d0d0dff0d0d0dff0d0d0dff171717ff161616ff151515ff282828ff353535f3ffffff01ffffff01ffffff01ffffff016e6e6e0b37373781242424f1191919ff333333ff383838ff343434ff3a3a3a93ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff015a5a5a0d1919197f0a0a0af30b0b0bff0d0d0dff0d0d0dff171717ff171717ff161616ff0f0f0ffb24242489ffffff01ffffff01ffffff013e3e3e5d2d2d2de52e2e2eff2b2b2bff151515ff141414ff212121ff363636ff3b3b3b95ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff013636361b111111a3080808ff0c0c0cff181818ff0f0f0fff171717b545454525ffffff01ffffff017f7f7f05363636c7282828ff313131ff313131ff2b2b2bff151515ff171717ff161616ff0c0c0cfb3434346bffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01303030350f0f0fc7121212d337373741ffffff01ffffff01ffffff01ffffff01ffffff016b6b6b0b3a3a3a7d2c2c2cf12f2f2fff2b2b2bff151515ff101010ff171717bb4646462dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01515151193535359b242424ff131313d72828284bffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff014e4e4e2b59595905ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff28000000400000008000000001002000000000000042000000000000000000000000000000000000ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0176767635666666914e4e4e457c7c7c09ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff018080801569696989545454696c6c6c0bffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff018484840d70707061616161d5606060fb3d3d3ddf4e4e4e9172727213ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff017070704d626262b35f5f5ffb464646f1454545a16a6a6a33ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff017676760f67676753646464cf5e5e5eff656565ff626262ff414141ff404040ff444444e54b4b4b7b69696919ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01979797036c6c6c45676767a95d5d5dff616161ff626262ff484848ff424242ff3e3e3efd4e4e4e8958585831ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff017e7e7e2b616161a75f5f5fef616161ff636363ff656565ff626262ff424242ff464646ff444444ff414141fd434343b961616153ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff017777771969696981606060e7606060ff636363ff636363ff626262ff484848ff464646ff454545ff424242fd414141d95656566569696911ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01858585056e6e6e29656565995f5f5ff1616161ff636363ff636363ff636363ff656565ff626262ff424242ff464646ff464646ff464646ff444444ff3f3f3fff484848af5353534b86868607ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01797979216a6a6a6f616161ed5e5e5eff636363ff636363ff636363ff636363ff626262ff484848ff464646ff464646ff464646ff464646ff3e3e3eff474747d75151515762626213ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01838383036f6f6f755f5f5fd3606060ff626262ff636363ff636363ff636363ff636363ff656565ff626262ff424242ff464646ff464646ff464646ff464646ff454545ff434343ff404040e94e4e4e8d5f5f5f1bffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff018f8f8f056b6b6b45616161c95f5f5ff7616161ff636363ff636363ff636363ff636363ff636363ff626262ff484848ff464646ff464646ff464646ff464646ff464646ff444444ff424242f1434343b16666662dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff017070700f6969695f626262d35e5e5eff626262ff636363ff636363ff636363ff636363ff636363ff636363ff656565ff626262ff424242ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff404040ff444444f14d4d4d776a6a6a23ffffff01ffffff01ffffff01ffffff017b7b7b096c6c6c39636363c15f5f5ffb626262ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff626262ff484848ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff434343ff414141f54a4a4aa35b5b5b2d70707007ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0171717143676767a7616161f3616161ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff656565ff626262ff424242ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff444444ff414141f7474747cd54545447ffffff01ffffff015b5b5b096b6b6b99646464e1606060ff626262ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff626262ff484848ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff444444ff424242ff414141d552525277ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01404040b33b3b3bff5c5c5cff656565ff646464ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff656565ff626262ff424242ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff474747ff454545ff3a3a3aff313131ad34343407ffffff012e2e2e25383838ff535353ff656565ff656565ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff626262ff484848ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff474747ff464646ff3b3b3bff3a3a3ae9ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01444444a9313131ff363636ff484848ff636363ff676767ff636363ff636363ff636363ff636363ff636363ff636363ff656565ff626262ff424242ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff404040ff363636ff343434ff353535a537373705ffffff0135353521333333ff333333ff434343ff5c5c5cff686868ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff626262ff484848ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff484848ff414141ff393939ff313131ff3c3c3cdbffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01444444a9343434ff363636ff323232ff353535ff4b4b4bff636363ff656565ff636363ff626262ff636363ff636363ff656565ff626262ff424242ff464646ff464646ff464646ff464646ff474747ff464646ff414141ff363636ff343434ff363636ff363636ff363636a537373705ffffff0135353521363636ff363636ff333333ff313131ff484848ff5e5e5eff666666ff646464ff626262ff636363ff636363ff636363ff626262ff484848ff464646ff464646ff464646ff464646ff464646ff474747ff424242ff3a3a3aff343434ff353535ff333333ff3d3d3ddbffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01444444a9343434ff363636ff363636ff343434ff333333ff3d3d3dff555555ff686868ff656565ff626262ff636363ff656565ff626262ff424242ff464646ff464646ff464646ff484848ff444444ff393939ff353535ff353535ff363636ff363636ff363636ff363636a537373705ffffff0135353521363636ff363636ff363636ff353535ff323232ff363636ff515151ff646464ff656565ff636363ff636363ff636363ff626262ff484848ff464646ff464646ff464646ff484848ff454545ff3d3d3dff353535ff343434ff363636ff363636ff333333ff3d3d3ddbffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01444444a9343434ff363636ff363636ff363636ff363636ff343434ff303030ff3f3f3fff575757ff666666ff656565ff646464ff626262ff424242ff464646ff474747ff454545ff3a3a3aff343434ff353535ff363636ff363636ff363636ff363636ff363636ff363636a537373705ffffff0135353521363636ff363636ff363636ff363636ff363636ff363636ff303030ff373737ff535353ff636363ff656565ff636363ff626262ff484848ff464646ff474747ff454545ff3e3e3eff353535ff343434ff363636ff363636ff363636ff363636ff333333ff3d3d3ddbffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01444444a9343434ff363636ff363636ff363636ff363636ff363636ff363636ff333333ff333333ff484848ff606060ff696969ff626262ff434343ff474747ff3e3e3eff363636ff353535ff353535ff363636ff363636ff363636ff363636ff363636ff363636ff363636a537373705ffffff0135353521363636ff363636ff363636ff363636ff363636ff363636ff353535ff343434ff333333ff3e3e3eff5d5d5dff686868ff626262ff484848ff474747ff424242ff373737ff353535ff353535ff363636ff363636ff363636ff363636ff363636ff333333ff3d3d3ddbffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01444444a9343434ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff323232ff323232ff505050ff616161ff3d3d3dff373737ff343434ff353535ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636a537373705ffffff0135353521363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff343434ff313131ff434343ff606060ff464646ff383838ff343434ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff333333ff3d3d3ddbffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01444444a9343434ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff3a3a3aff2b2b2bff1e1e1eff2d2d2dff383838ff373737ff353535ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636a537373705ffffff0135353521363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff393939ff323232ff1c1c1cff262626ff373737ff383838ff353535ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff333333ff3d3d3ddbffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01444444a9343434ff363636ff363636ff363636ff363636ff363636ff353535ff373737ff383838ff303030ff191919ff080808ff101010ff141414ff1a1a1aff303030ff383838ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636a537373705ffffff0135353521363636ff363636ff363636ff363636ff363636ff363636ff353535ff363636ff383838ff363636ff1d1d1dff0b0b0bff0c0c0cff141414ff181818ff292929ff373737ff373737ff363636ff363636ff363636ff363636ff363636ff363636ff333333ff3d3d3ddbffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01444444a9343434ff363636ff363636ff363636ff363636ff353535ff393939ff363636ff222222ff0c0c0cff0a0a0aff0c0c0cff121212ff171717ff151515ff161616ff212121ff353535ff393939ff363636ff363636ff363636ff363636ff363636ff363636ff363636a537373705ffffff0135353521363636ff363636ff363636ff363636ff363636ff353535ff383838ff3a3a3aff262626ff121212ff0a0a0aff0c0c0cff0f0f0fff171717ff151515ff151515ff1e1e1eff2f2f2fff3a3a3aff363636ff363636ff363636ff363636ff363636ff333333ff3d3d3ddbffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01444444a9343434ff363636ff363636ff363636ff383838ff363636ff262626ff0d0d0dff090909ff0d0d0dff0d0d0dff0c0c0cff121212ff171717ff171717ff171717ff141414ff151515ff232323ff353535ff383838ff363636ff353535ff363636ff363636ff363636a537373705ffffff0135353521363636ff363636ff363636ff353535ff383838ff383838ff292929ff131313ff080808ff0c0c0cff0d0d0dff0d0d0dff0f0f0fff171717ff171717ff171717ff151515ff131313ff202020ff313131ff383838ff363636ff363636ff363636ff333333ff3d3d3ddbffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01444444a9343434ff353535ff363636ff3a3a3aff2e2e2eff131313ff0a0a0aff0b0b0bff0d0d0dff0d0d0dff0d0d0dff0c0c0cff121212ff171717ff171717ff171717ff171717ff161616ff141414ff1a1a1aff2a2a2aff393939ff373737ff363636ff363636ff363636a537373705ffffff0135353521363636ff363636ff363636ff3a3a3aff313131ff1c1c1cff0a0a0aff0a0a0aff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0f0f0fff171717ff171717ff171717ff171717ff161616ff151515ff161616ff282828ff363636ff383838ff363636ff333333ff3d3d3ddbffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01444444a9353535ff383838ff313131ff151515ff080808ff0b0b0bff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff121212ff171717ff171717ff171717ff171717ff171717ff171717ff161616ff131313ff1b1b1bff2d2d2dff373737ff373737ff363636a537373705ffffff0134343421363636ff383838ff333333ff1e1e1eff090909ff0a0a0aff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0f0f0fff171717ff171717ff171717ff171717ff171717ff171717ff171717ff131313ff171717ff2a2a2aff363636ff353535ff3d3d3ddbffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01444444af353535ff1e1e1eff0d0d0dff0a0a0aff0c0c0cff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff121212ff171717ff171717ff171717ff171717ff171717ff171717ff171717ff171717ff151515ff151515ff222222ff333333ff353535ad30303007ffffff0134343423373737ff282828ff0d0d0dff0a0a0aff0c0c0cff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0f0f0fff171717ff171717ff171717ff171717ff171717ff171717ff171717ff171717ff151515ff141414ff1b1b1bff2e2e2eff3e3e3ee1ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff013e3e3e6f0f0f0fd5040404ff0b0b0bff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff121212ff171717ff171717ff171717ff171717ff171717ff171717ff171717ff171717ff171717ff171717ff101010ff0e0e0ee72f2f2f7347474703ffffff013b3b3b13141414cd050505f70a0a0aff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0f0f0fff171717ff171717ff171717ff171717ff171717ff171717ff171717ff171717ff171717ff171717ff121212ff0c0c0cf12a2a2aa5ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff015f5f5f052020202b1a1a1aa1080808f1070707ff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff121212ff171717ff171717ff171717ff171717ff171717ff171717ff171717ff171717ff141414ff0c0c0cff212121af2a2a2a496d6d6d07ffffff01ffffff01ffffff01333333231d1d1d730b0b0beb060606ff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0f0f0fff171717ff171717ff171717ff171717ff171717ff171717ff171717ff171717ff151515ff0e0e0eff181818d72626265546464615ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff014d4d4d29121212af080808ef0a0a0aff0c0c0cff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff121212ff171717ff171717ff171717ff171717ff171717ff171717ff141414ff121212f9141414b93b3b3b4fffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0138383819151515890a0a0ae5080808ff0c0c0cff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0f0f0fff171717ff171717ff171717ff171717ff171717ff171717ff161616ff101010fb151515d72c2c2c614444440dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0133333311262626510f0f0fd7050505ff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff121212ff171717ff171717ff171717ff171717ff171717ff101010ff141414e7242424733a3a3a19ffffff01ffffff01ffffff01878787097272725f4d4d4d736a6a6a11ffffff01ffffff01ffffff016060600524242445191919ad040404ff0a0a0aff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0f0f0fff171717ff171717ff171717ff171717ff171717ff111111ff0e0e0efd242424873232322dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff015c5c5c0d2525255f090909d7080808fb0b0b0bff0d0d0dff0c0c0cff121212ff171717ff171717ff161616ff121212ff121212df2121218965656511ffffff01ffffff01ffffff018080800d6767674b646464d1606060ff454545ff464646df4f4f4f6165656517ffffff01ffffff01ffffff01ffffff012d2d2d4b101010b5060606fb0a0a0aff0d0d0dff0d0d0dff0f0f0fff171717ff171717ff161616ff131313ff101010ef2020209d4242422dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff012c2c2c2d1f1f1f83080808fb080808ff0d0d0dff121212ff171717ff141414ff0f0f0ff91e1e1eb12c2c2c354d4d4d09ffffff01ffffff01ffffff0178787825646464a75f5f5feb616161ff656565ff4a4a4aff414141ff424242f3414141bd69696937ffffff01ffffff01ffffff01ffffff0142424219171717710d0d0de3060606ff0c0c0cff0f0f0fff171717ff151515ff0d0d0dff171717c3292929575656560dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff013737372d1212129d080808ef0d0d0dff121212f5191919bf2e2e2e3d70707003ffffff01ffffff018c8c8c037676762564646497606060ed606060ff636363ff636363ff656565ff4a4a4aff444444ff464646ff444444ff404040f74a4a4aad5555553162626207ffffff01ffffff01ffffff014040401125252589090909dd0a0a0aff121212ff141414c738383869ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff015b5b5b0b1f1f1f591d1d1daf292929673f3f3f19ffffff01ffffff01ffffff01ffffff016d6d6d715f5f5fcd606060ff626262ff636363ff636363ff636363ff656565ff4a4a4aff444444ff464646ff464646ff454545ff434343ff414141db4f4f4f857b7b7b11ffffff01ffffff01ffffff0153535307222222331d1d1da91b1b1b8d4141412365656503ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff017c7c7c0f6868685d636363cb5e5e5eff626262ff636363ff636363ff636363ff636363ff636363ff656565ff4a4a4aff444444ff464646ff464646ff464646ff464646ff464646ff404040ff454545e14c4c4c6b69696917ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0177777733626262a3606060f3616161ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff656565ff4a4a4aff444444ff464646ff464646ff464646ff464646ff464646ff464646ff444444ff424242f9454545b55d5d5d49ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff014b4b4b0f5e5e5e85626262ff626262ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff656565ff4a4a4aff444444ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff444444ff414141ff454545a16464641dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0132323225333333cf4e4e4eff646464ff666666ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff656565ff4a4a4aff444444ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff474747ff404040ff303030e35757573bffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0137373723363636cd313131ff363636ff515151ff636363ff656565ff636363ff636363ff636363ff636363ff636363ff636363ff656565ff4a4a4aff444444ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff414141ff373737ff343434ff323232e159595939ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0137373723363636cd363636ff343434ff333333ff3c3c3cff5b5b5bff686868ff636363ff626262ff636363ff636363ff636363ff656565ff4a4a4aff444444ff464646ff464646ff454545ff464646ff4c4c4cff454545ff393939ff353535ff353535ff353535ff323232e159595939ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0137373723363636cd363636ff363636ff363636ff353535ff313131ff3f3f3fff5d5d5dff666666ff646464ff626262ff636363ff656565ff4a4a4aff444444ff454545ff474747ff4a4a4aff404040ff212121ff2f2f2fff373737ff373737ff353535ff363636ff323232e159595939ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0137373723363636cd363636ff363636ff363636ff363636ff353535ff333333ff363636ff484848ff646464ff676767ff626262ff656565ff4a4a4aff444444ff4b4b4bff4a4a4aff262626ff0b0b0bff090909ff171717ff252525ff353535ff393939ff363636ff323232e159595939ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0137373723363636cd363636ff363636ff363636ff363636ff363636ff363636ff363636ff323232ff363636ff4c4c4cff646464ff676767ff4d4d4dff484848ff2c2c2cff0b0b0bff020202ff040404ff0b0b0bff171717ff141414ff161616ff282828ff353535ff343434e359595939ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0137373723363636cd363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff343434ff323232ff3f3f3fff5f5f5fff3a3a3aff161616ff030303ff030303ff050505ff040404ff0b0b0bff171717ff171717ff161616ff151515ff1a1a1aff242424e55555553bffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0137373723363636cd363636ff363636ff363636ff363636ff363636ff363636ff363636ff353535ff363636ff383838ff2e2e2eff191919ff262626ff111111ff030303ff030303ff050505ff040404ff0b0b0bff171717ff171717ff151515ff111111f9121212cd272727557d7d7d09ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0137373723363636cd363636ff363636ff363636ff363636ff363636ff363636ff363636ff383838ff373737ff242424ff0b0b0bff0a0a0aff393939ff393939ff222222ff080808ff020202ff030303ff0b0b0bff181818ff0f0f0fff151515f32424247935353525ffffff01ffffff01ffffff01a3a3a30fffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0137373723363636cd363636ff363636ff363636ff363636ff363636ff383838ff373737ff272727ff0c0c0cff090909ff0c0c0cff0e0e0eff373737ff363636ff3a3a3aff393939ff1e1e1eff080808ff080808ff0f0f0feb232323914040401dffffff01ffffff01ffffff01ffffff01ffffff018282825d626262c36d6d6d4d8d8d8d09ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0137373723363636cd363636ff363636ff353535ff363636ff3a3a3aff2f2f2fff131313ff0b0b0bff0b0b0bff0d0d0dff0c0c0cff0e0e0eff373737ff363636ff353535ff363636ff393939ff303030ff1c1c1cc92626264d68686807ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01868686515e5e5eff646464e9696969957878781fffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0137373723363636cd363636ff373737ff383838ff313131ff161616ff090909ff0b0b0bff0d0d0dff0d0d0dff0d0d0dff0c0c0cff0e0e0eff373737ff363636ff363636ff363636ff353535ff353535ff3c3c3c8fffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0186868651616161ff676767ff646464ff656565f16a6a6a7d7f7f7f25ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0137373723353535cd393939ff373737ff1f1f1fff0d0d0dff0a0a0aff0c0c0cff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff0e0e0eff373737ff363636ff363636ff363636ff363636ff353535ff37373791ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0186868651616161ff676767ff666666ff676767ff686868f9555555cd55555511ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0134343425323232cf212121ff0e0e0eff090909ff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff0e0e0eff383838ff363636ff363636ff363636ff363636ff353535ff37373791ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0186868651616161ff686868ff696969ff5f5f5fff3d3d3dff303030ff4848481dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01474747132323238f020202ff080808ff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff0c0c0cff2e2e2eff393939ff363636ff353535ff363636ff353535ff37373791ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0185858551666666ff676767ff494949ff353535ff323232ff353535ff4e4e4e1bffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0130303045101010af080808f70a0a0aff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff0d0d0dff131313ff1c1c1cff303030ff373737ff363636ff353535ff37373791ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0181818151494949ff363636ff313131ff363636ff353535ff363636ff4e4e4e1bffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0141414113191919690f0f0fdb060606ff0c0c0cff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff0d0d0dff171717ff151515ff161616ff222222ff363636ff383838ff37373791ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff014d4d4d53272727c1242424ff373737ff373737ff353535ff353535ff363636ff4e4e4e1bffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01626262091c1c1c830b0b0bd7090909ff0c0c0cff0d0d0dff0d0d0dff0c0c0cff0d0d0dff171717ff171717ff171717ff141414ff151515ff202020ff35353595ffffff01ffffff01ffffff01ffffff017474740540404049343434af2a2a2aff262626ff101010ff191919ff2e2e2eff373737ff363636ff363636ff4e4e4e1bffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff015a5a5a073636362d141414a7080808f5080808ff0d0d0dff0c0c0cff0d0d0dff171717ff171717ff171717ff151515ff0e0e0efb1b1b1bbb3d3d3d29ffffff01ffffff01ffffff0151515119393939892a2a2ae92d2d2dff323232ff282828ff141414ff151515ff151515ff1f1f1fff343434ff393939ff4949491dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff013636362f111111b5070707f30a0a0aff0d0d0dff171717ff141414ff111111f5111111c74343433d70707005ffffff01ffffff017c7c7c034e4e4e632a2a2af7292929ff323232ff313131ff323232ff282828ff141414ff171717ff171717ff151515ff0e0e0efd222222e153535315ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff012d2d2d151f1f1f590e0e0edb040404ff0f0f0fff171717e7262626673f3f3f1dffffff01ffffff01ffffff01ffffff01ffffff01444444293535358b2d2d2deb2b2b2bff313131ff323232ff282828ff141414ff171717ff121212ff0d0d0dff2222229d2626263dbebebe03ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01505050112626266f1d1d1d7f36363617ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01616161213333339d2c2c2ce92f2f2fff282828ff111111ff111111f7191919ab3c3c3c41ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff015151510b3b3b3b43383838c51f1f1fff141414d71e1e1e654f4f4f13ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff015858580b4d4d4d4159595909ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000` diff --git a/swarm/api/http/test_server.go b/swarm/api/http/test_server.go deleted file mode 100644 index a3be01e99..000000000 --- a/swarm/api/http/test_server.go +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. - -package http - -import ( - "io/ioutil" - "net/http" - "net/http/httptest" - "os" - "testing" - - "github.com/ethereum/go-ethereum/swarm/api" - "github.com/ethereum/go-ethereum/swarm/chunk" - "github.com/ethereum/go-ethereum/swarm/storage" - "github.com/ethereum/go-ethereum/swarm/storage/feed" - "github.com/ethereum/go-ethereum/swarm/storage/localstore" -) - -type TestServer interface { - ServeHTTP(http.ResponseWriter, *http.Request) -} - -func NewTestSwarmServer(t *testing.T, serverFunc func(*api.API) TestServer, resolver api.Resolver) *TestSwarmServer { - swarmDir, err := ioutil.TempDir("", "swarm-storage-test") - if err != nil { - t.Fatal(err) - } - localStore, err := localstore.New(swarmDir, make([]byte, 32), nil) - if err != nil { - os.RemoveAll(swarmDir) - t.Fatal(err) - } - - tags := chunk.NewTags() - fileStore := storage.NewFileStore(localStore, storage.NewFileStoreParams(), tags) - - // Swarm feeds test setup - feedsDir, err := ioutil.TempDir("", "swarm-feeds-test") - if err != nil { - t.Fatal(err) - } - - feeds, err := feed.NewTestHandler(feedsDir, &feed.HandlerParams{}) - if err != nil { - t.Fatal(err) - } - - swarmApi := api.NewAPI(fileStore, resolver, feeds.Handler, nil, tags) - apiServer := httptest.NewServer(serverFunc(swarmApi)) - - tss := &TestSwarmServer{ - Server: apiServer, - FileStore: fileStore, - Tags: tags, - dir: swarmDir, - Hasher: storage.MakeHashFunc(storage.DefaultHash)(), - cleanup: func() { - apiServer.Close() - fileStore.Close() - feeds.Close() - os.RemoveAll(swarmDir) - os.RemoveAll(feedsDir) - }, - CurrentTime: 42, - } - feed.TimestampProvider = tss - return tss -} - -type TestSwarmServer struct { - *httptest.Server - Hasher storage.SwarmHash - FileStore *storage.FileStore - Tags *chunk.Tags - dir string - cleanup func() - CurrentTime uint64 -} - -func (t *TestSwarmServer) Close() { - t.cleanup() -} - -func (t *TestSwarmServer) Now() feed.Timestamp { - return feed.Timestamp{Time: t.CurrentTime} -} |