github.com/oskarth/go-ethereum@v1.6.8-0.20191013093314-dac24a9d3494/swarm/api/http/server_test.go (about) 1 // Copyright 2017 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package http 18 19 import ( 20 "archive/tar" 21 "bytes" 22 "context" 23 "crypto/rand" 24 "encoding/json" 25 "errors" 26 "flag" 27 "fmt" 28 "io" 29 "io/ioutil" 30 "math/big" 31 "mime/multipart" 32 "net/http" 33 "net/url" 34 "os" 35 "path" 36 "strconv" 37 "strings" 38 "testing" 39 "time" 40 41 "github.com/ethereum/go-ethereum/swarm/storage/feed/lookup" 42 43 "github.com/ethereum/go-ethereum/common" 44 "github.com/ethereum/go-ethereum/core/types" 45 "github.com/ethereum/go-ethereum/crypto" 46 "github.com/ethereum/go-ethereum/log" 47 "github.com/ethereum/go-ethereum/swarm/api" 48 swarm "github.com/ethereum/go-ethereum/swarm/api/client" 49 "github.com/ethereum/go-ethereum/swarm/multihash" 50 "github.com/ethereum/go-ethereum/swarm/storage" 51 "github.com/ethereum/go-ethereum/swarm/storage/feed" 52 "github.com/ethereum/go-ethereum/swarm/testutil" 53 ) 54 55 func init() { 56 loglevel := flag.Int("loglevel", 2, "loglevel") 57 flag.Parse() 58 log.Root().SetHandler(log.CallerFileHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(os.Stderr, log.TerminalFormat(true))))) 59 } 60 61 func serverFunc(api *api.API) testutil.TestServer { 62 return NewServer(api, "") 63 } 64 65 func newTestSigner() (*feed.GenericSigner, error) { 66 privKey, err := crypto.HexToECDSA("deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef") 67 if err != nil { 68 return nil, err 69 } 70 return feed.NewGenericSigner(privKey), nil 71 } 72 73 // test the transparent resolving of multihash-containing feed updates with bzz:// scheme 74 // 75 // first upload data, and store the multihash to the resulting manifest in a feed update 76 // retrieving the update with the multihash should return the manifest pointing directly to the data 77 // and raw retrieve of that hash should return the data 78 func TestBzzFeedMultihash(t *testing.T) { 79 80 signer, _ := newTestSigner() 81 82 srv := testutil.NewTestSwarmServer(t, serverFunc, nil) 83 defer srv.Close() 84 85 // add the data our multihash aliased manifest will point to 86 databytes := "bar" 87 testBzzUrl := fmt.Sprintf("%s/bzz:/", srv.URL) 88 resp, err := http.Post(testBzzUrl, "text/plain", bytes.NewReader([]byte(databytes))) 89 if err != nil { 90 t.Fatal(err) 91 } 92 defer resp.Body.Close() 93 if resp.StatusCode != http.StatusOK { 94 t.Fatalf("err %s", resp.Status) 95 } 96 b, err := ioutil.ReadAll(resp.Body) 97 98 if err != nil { 99 t.Fatal(err) 100 } 101 s := common.FromHex(string(b)) 102 mh := multihash.ToMultihash(s) 103 104 log.Info("added data", "manifest", string(b), "data", common.ToHex(mh)) 105 106 topic, _ := feed.NewTopic("foo.eth", nil) 107 updateRequest := feed.NewFirstRequest(topic) 108 109 updateRequest.SetData(mh) 110 111 if err := updateRequest.Sign(signer); err != nil { 112 t.Fatal(err) 113 } 114 log.Info("added data", "manifest", string(b), "data", common.ToHex(mh)) 115 116 testUrl, err := url.Parse(fmt.Sprintf("%s/bzz-feed:/", srv.URL)) 117 if err != nil { 118 t.Fatal(err) 119 } 120 query := testUrl.Query() 121 body := updateRequest.AppendValues(query) // this adds all query parameters and returns the data to be posted 122 query.Set("manifest", "1") // indicate we want a manifest back 123 testUrl.RawQuery = query.Encode() 124 125 // create the multihash update 126 resp, err = http.Post(testUrl.String(), "application/octet-stream", bytes.NewReader(body)) 127 if err != nil { 128 t.Fatal(err) 129 } 130 defer resp.Body.Close() 131 if resp.StatusCode != http.StatusOK { 132 t.Fatalf("err %s", resp.Status) 133 } 134 b, err = ioutil.ReadAll(resp.Body) 135 if err != nil { 136 t.Fatal(err) 137 } 138 rsrcResp := &storage.Address{} 139 err = json.Unmarshal(b, rsrcResp) 140 if err != nil { 141 t.Fatalf("data %s could not be unmarshaled: %v", b, err) 142 } 143 144 correctManifestAddrHex := "bb056a5264c295c2b0f613c8409b9c87ce9d71576ace02458160df4cc894210b" 145 if rsrcResp.Hex() != correctManifestAddrHex { 146 t.Fatalf("Response feed manifest address mismatch, expected '%s', got '%s'", correctManifestAddrHex, rsrcResp.Hex()) 147 } 148 149 // get bzz manifest transparent feed update resolve 150 testBzzUrl = fmt.Sprintf("%s/bzz:/%s", srv.URL, rsrcResp) 151 resp, err = http.Get(testBzzUrl) 152 if err != nil { 153 t.Fatal(err) 154 } 155 defer resp.Body.Close() 156 if resp.StatusCode != http.StatusOK { 157 t.Fatalf("err %s", resp.Status) 158 } 159 b, err = ioutil.ReadAll(resp.Body) 160 if err != nil { 161 t.Fatal(err) 162 } 163 if !bytes.Equal(b, []byte(databytes)) { 164 t.Fatalf("retrieved data mismatch, expected %x, got %x", databytes, b) 165 } 166 } 167 168 // Test Swarm feeds using the raw update methods 169 func TestBzzFeed(t *testing.T) { 170 srv := testutil.NewTestSwarmServer(t, serverFunc, nil) 171 signer, _ := newTestSigner() 172 173 defer srv.Close() 174 175 // data of update 1 176 update1Data := make([]byte, 666) 177 update1Timestamp := srv.CurrentTime 178 _, err := rand.Read(update1Data) 179 if err != nil { 180 t.Fatal(err) 181 } 182 //data for update 2 183 update2Data := []byte("foo") 184 185 topic, _ := feed.NewTopic("foo.eth", nil) 186 updateRequest := feed.NewFirstRequest(topic) 187 if err != nil { 188 t.Fatal(err) 189 } 190 updateRequest.SetData(update1Data) 191 192 if err := updateRequest.Sign(signer); err != nil { 193 t.Fatal(err) 194 } 195 196 // creates feed and sets update 1 197 testUrl, err := url.Parse(fmt.Sprintf("%s/bzz-feed:/", srv.URL)) 198 if err != nil { 199 t.Fatal(err) 200 } 201 urlQuery := testUrl.Query() 202 body := updateRequest.AppendValues(urlQuery) // this adds all query parameters 203 urlQuery.Set("manifest", "1") // indicate we want a manifest back 204 testUrl.RawQuery = urlQuery.Encode() 205 206 resp, err := http.Post(testUrl.String(), "application/octet-stream", bytes.NewReader(body)) 207 if err != nil { 208 t.Fatal(err) 209 } 210 defer resp.Body.Close() 211 if resp.StatusCode != http.StatusOK { 212 t.Fatalf("err %s", resp.Status) 213 } 214 b, err := ioutil.ReadAll(resp.Body) 215 if err != nil { 216 t.Fatal(err) 217 } 218 rsrcResp := &storage.Address{} 219 err = json.Unmarshal(b, rsrcResp) 220 if err != nil { 221 t.Fatalf("data %s could not be unmarshaled: %v", b, err) 222 } 223 224 correctManifestAddrHex := "bb056a5264c295c2b0f613c8409b9c87ce9d71576ace02458160df4cc894210b" 225 if rsrcResp.Hex() != correctManifestAddrHex { 226 t.Fatalf("Response feed manifest mismatch, expected '%s', got '%s'", correctManifestAddrHex, rsrcResp.Hex()) 227 } 228 229 // get the manifest 230 testRawUrl := fmt.Sprintf("%s/bzz-raw:/%s", srv.URL, rsrcResp) 231 resp, err = http.Get(testRawUrl) 232 if err != nil { 233 t.Fatal(err) 234 } 235 defer resp.Body.Close() 236 if resp.StatusCode != http.StatusOK { 237 t.Fatalf("err %s", resp.Status) 238 } 239 b, err = ioutil.ReadAll(resp.Body) 240 if err != nil { 241 t.Fatal(err) 242 } 243 manifest := &api.Manifest{} 244 err = json.Unmarshal(b, manifest) 245 if err != nil { 246 t.Fatal(err) 247 } 248 if len(manifest.Entries) != 1 { 249 t.Fatalf("Manifest has %d entries", len(manifest.Entries)) 250 } 251 correctFeedHex := "0x666f6f2e65746800000000000000000000000000000000000000000000000000c96aaa54e2d44c299564da76e1cd3184a2386b8d" 252 if manifest.Entries[0].Feed.Hex() != correctFeedHex { 253 t.Fatalf("Expected manifest Feed '%s', got '%s'", correctFeedHex, manifest.Entries[0].Feed.Hex()) 254 } 255 256 // get bzz manifest transparent feed update resolve 257 testBzzUrl := fmt.Sprintf("%s/bzz:/%s", srv.URL, rsrcResp) 258 resp, err = http.Get(testBzzUrl) 259 if err != nil { 260 t.Fatal(err) 261 } 262 defer resp.Body.Close() 263 if resp.StatusCode == http.StatusOK { 264 t.Fatal("Expected error status since feed update does not contain multihash. Received 200 OK") 265 } 266 b, err = ioutil.ReadAll(resp.Body) 267 if err != nil { 268 t.Fatal(err) 269 } 270 271 // get non-existent name, should fail 272 testBzzResUrl := fmt.Sprintf("%s/bzz-feed:/bar", srv.URL) 273 resp, err = http.Get(testBzzResUrl) 274 if err != nil { 275 t.Fatal(err) 276 } 277 278 if resp.StatusCode != http.StatusNotFound { 279 t.Fatalf("Expected get non-existent feed manifest to fail with StatusNotFound (404), got %d", resp.StatusCode) 280 } 281 282 resp.Body.Close() 283 284 // get latest update through bzz-feed directly 285 log.Info("get update latest = 1.1", "addr", correctManifestAddrHex) 286 testBzzResUrl = fmt.Sprintf("%s/bzz-feed:/%s", srv.URL, correctManifestAddrHex) 287 resp, err = http.Get(testBzzResUrl) 288 if err != nil { 289 t.Fatal(err) 290 } 291 defer resp.Body.Close() 292 if resp.StatusCode != http.StatusOK { 293 t.Fatalf("err %s", resp.Status) 294 } 295 b, err = ioutil.ReadAll(resp.Body) 296 if err != nil { 297 t.Fatal(err) 298 } 299 if !bytes.Equal(update1Data, b) { 300 t.Fatalf("Expected body '%x', got '%x'", update1Data, b) 301 } 302 303 // update 2 304 // Move the clock ahead 1 second 305 srv.CurrentTime++ 306 log.Info("update 2") 307 308 // 1.- get metadata about this feed 309 testBzzResUrl = fmt.Sprintf("%s/bzz-feed:/%s/", srv.URL, correctManifestAddrHex) 310 resp, err = http.Get(testBzzResUrl + "?meta=1") 311 if err != nil { 312 t.Fatal(err) 313 } 314 defer resp.Body.Close() 315 if resp.StatusCode != http.StatusOK { 316 t.Fatalf("Get feed metadata returned %s", resp.Status) 317 } 318 b, err = ioutil.ReadAll(resp.Body) 319 if err != nil { 320 t.Fatal(err) 321 } 322 updateRequest = &feed.Request{} 323 if err = updateRequest.UnmarshalJSON(b); err != nil { 324 t.Fatalf("Error decoding feed metadata: %s", err) 325 } 326 updateRequest.SetData(update2Data) 327 if err = updateRequest.Sign(signer); err != nil { 328 t.Fatal(err) 329 } 330 testUrl, err = url.Parse(fmt.Sprintf("%s/bzz-feed:/", srv.URL)) 331 if err != nil { 332 t.Fatal(err) 333 } 334 urlQuery = testUrl.Query() 335 body = updateRequest.AppendValues(urlQuery) // this adds all query parameters 336 testUrl.RawQuery = urlQuery.Encode() 337 338 resp, err = http.Post(testUrl.String(), "application/octet-stream", bytes.NewReader(body)) 339 if err != nil { 340 t.Fatal(err) 341 } 342 defer resp.Body.Close() 343 if resp.StatusCode != http.StatusOK { 344 t.Fatalf("Update returned %s", resp.Status) 345 } 346 347 // get latest update through bzz-feed directly 348 log.Info("get update 1.2") 349 testBzzResUrl = fmt.Sprintf("%s/bzz-feed:/%s", srv.URL, correctManifestAddrHex) 350 resp, err = http.Get(testBzzResUrl) 351 if err != nil { 352 t.Fatal(err) 353 } 354 defer resp.Body.Close() 355 if resp.StatusCode != http.StatusOK { 356 t.Fatalf("err %s", resp.Status) 357 } 358 b, err = ioutil.ReadAll(resp.Body) 359 if err != nil { 360 t.Fatal(err) 361 } 362 if !bytes.Equal(update2Data, b) { 363 t.Fatalf("Expected body '%x', got '%x'", update2Data, b) 364 } 365 366 // test manifest-less queries 367 log.Info("get first update in update1Timestamp via direct query") 368 query := feed.NewQuery(&updateRequest.Feed, update1Timestamp, lookup.NoClue) 369 370 urlq, err := url.Parse(fmt.Sprintf("%s/bzz-feed:/", srv.URL)) 371 if err != nil { 372 t.Fatal(err) 373 } 374 375 values := urlq.Query() 376 query.AppendValues(values) // this adds feed query parameters 377 urlq.RawQuery = values.Encode() 378 resp, err = http.Get(urlq.String()) 379 if err != nil { 380 t.Fatal(err) 381 } 382 defer resp.Body.Close() 383 if resp.StatusCode != http.StatusOK { 384 t.Fatalf("err %s", resp.Status) 385 } 386 b, err = ioutil.ReadAll(resp.Body) 387 if err != nil { 388 t.Fatal(err) 389 } 390 if !bytes.Equal(update1Data, b) { 391 t.Fatalf("Expected body '%x', got '%x'", update1Data, b) 392 } 393 394 } 395 396 func TestBzzGetPath(t *testing.T) { 397 testBzzGetPath(false, t) 398 testBzzGetPath(true, t) 399 } 400 401 func testBzzGetPath(encrypted bool, t *testing.T) { 402 var err error 403 404 testmanifest := []string{ 405 `{"entries":[{"path":"b","hash":"011b4d03dd8c01f1049143cf9c4c817e4b167f1d1b83e5c6f0f10d89ba1e7bce","contentType":"","status":0},{"path":"c","hash":"011b4d03dd8c01f1049143cf9c4c817e4b167f1d1b83e5c6f0f10d89ba1e7bce","contentType":"","status":0}]}`, 406 `{"entries":[{"path":"a","hash":"011b4d03dd8c01f1049143cf9c4c817e4b167f1d1b83e5c6f0f10d89ba1e7bce","contentType":"","status":0},{"path":"b/","hash":"<key0>","contentType":"application/bzz-manifest+json","status":0}]}`, 407 `{"entries":[{"path":"a/","hash":"<key1>","contentType":"application/bzz-manifest+json","status":0}]}`, 408 } 409 410 testrequests := make(map[string]int) 411 testrequests["/"] = 2 412 testrequests["/a/"] = 1 413 testrequests["/a/b/"] = 0 414 testrequests["/x"] = 0 415 testrequests[""] = 0 416 417 expectedfailrequests := []string{"", "/x"} 418 419 reader := [3]*bytes.Reader{} 420 421 addr := [3]storage.Address{} 422 423 srv := testutil.NewTestSwarmServer(t, serverFunc, nil) 424 defer srv.Close() 425 426 for i, mf := range testmanifest { 427 reader[i] = bytes.NewReader([]byte(mf)) 428 var wait func(context.Context) error 429 ctx := context.TODO() 430 addr[i], wait, err = srv.FileStore.Store(ctx, reader[i], int64(len(mf)), encrypted) 431 if err != nil { 432 t.Fatal(err) 433 } 434 for j := i + 1; j < len(testmanifest); j++ { 435 testmanifest[j] = strings.Replace(testmanifest[j], fmt.Sprintf("<key%v>", i), addr[i].Hex(), -1) 436 } 437 err = wait(ctx) 438 if err != nil { 439 t.Fatal(err) 440 } 441 } 442 443 rootRef := addr[2].Hex() 444 445 _, err = http.Get(srv.URL + "/bzz-raw:/" + rootRef + "/a") 446 if err != nil { 447 t.Fatalf("Failed to connect to proxy: %v", err) 448 } 449 450 for k, v := range testrequests { 451 var resp *http.Response 452 var respbody []byte 453 454 url := srv.URL + "/bzz-raw:/" 455 if k != "" { 456 url += rootRef + "/" + k[1:] + "?content_type=text/plain" 457 } 458 resp, err = http.Get(url) 459 if err != nil { 460 t.Fatalf("Request failed: %v", err) 461 } 462 defer resp.Body.Close() 463 respbody, err = ioutil.ReadAll(resp.Body) 464 465 if string(respbody) != testmanifest[v] { 466 isexpectedfailrequest := false 467 468 for _, r := range expectedfailrequests { 469 if k == r { 470 isexpectedfailrequest = true 471 } 472 } 473 if !isexpectedfailrequest { 474 t.Fatalf("Response body does not match, expected: %v, got %v", testmanifest[v], string(respbody)) 475 } 476 } 477 } 478 479 for k, v := range testrequests { 480 var resp *http.Response 481 var respbody []byte 482 483 url := srv.URL + "/bzz-hash:/" 484 if k != "" { 485 url += rootRef + "/" + k[1:] 486 } 487 resp, err = http.Get(url) 488 if err != nil { 489 t.Fatalf("Request failed: %v", err) 490 } 491 defer resp.Body.Close() 492 respbody, err = ioutil.ReadAll(resp.Body) 493 if err != nil { 494 t.Fatalf("Read request body: %v", err) 495 } 496 497 if string(respbody) != addr[v].Hex() { 498 isexpectedfailrequest := false 499 500 for _, r := range expectedfailrequests { 501 if k == r { 502 isexpectedfailrequest = true 503 } 504 } 505 if !isexpectedfailrequest { 506 t.Fatalf("Response body does not match, expected: %v, got %v", addr[v], string(respbody)) 507 } 508 } 509 } 510 511 ref := addr[2].Hex() 512 513 for _, c := range []struct { 514 path string 515 json string 516 pageFragments []string 517 }{ 518 { 519 path: "/", 520 json: `{"common_prefixes":["a/"]}`, 521 pageFragments: []string{ 522 fmt.Sprintf("Swarm index of bzz:/%s/", ref), 523 `<a class="normal-link" href="a/">a/</a>`, 524 }, 525 }, 526 { 527 path: "/a/", 528 json: `{"common_prefixes":["a/b/"],"entries":[{"hash":"011b4d03dd8c01f1049143cf9c4c817e4b167f1d1b83e5c6f0f10d89ba1e7bce","path":"a/a","mod_time":"0001-01-01T00:00:00Z"}]}`, 529 pageFragments: []string{ 530 fmt.Sprintf("Swarm index of bzz:/%s/a/", ref), 531 `<a class="normal-link" href="b/">b/</a>`, 532 fmt.Sprintf(`<a class="normal-link" href="/bzz:/%s/a/a">a</a>`, ref), 533 }, 534 }, 535 { 536 path: "/a/b/", 537 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"}]}`, 538 pageFragments: []string{ 539 fmt.Sprintf("Swarm index of bzz:/%s/a/b/", ref), 540 fmt.Sprintf(`<a class="normal-link" href="/bzz:/%s/a/b/b">b</a>`, ref), 541 fmt.Sprintf(`<a class="normal-link" href="/bzz:/%s/a/b/c">c</a>`, ref), 542 }, 543 }, 544 { 545 path: "/x", 546 }, 547 { 548 path: "", 549 }, 550 } { 551 k := c.path 552 url := srv.URL + "/bzz-list:/" 553 if k != "" { 554 url += rootRef + "/" + k[1:] 555 } 556 t.Run("json list "+c.path, func(t *testing.T) { 557 resp, err := http.Get(url) 558 if err != nil { 559 t.Fatalf("HTTP request: %v", err) 560 } 561 defer resp.Body.Close() 562 respbody, err := ioutil.ReadAll(resp.Body) 563 if err != nil { 564 t.Fatalf("Read response body: %v", err) 565 } 566 567 body := strings.TrimSpace(string(respbody)) 568 if body != c.json { 569 isexpectedfailrequest := false 570 571 for _, r := range expectedfailrequests { 572 if k == r { 573 isexpectedfailrequest = true 574 } 575 } 576 if !isexpectedfailrequest { 577 t.Errorf("Response list body %q does not match, expected: %v, got %v", k, c.json, body) 578 } 579 } 580 }) 581 t.Run("html list "+c.path, func(t *testing.T) { 582 req, err := http.NewRequest(http.MethodGet, url, nil) 583 if err != nil { 584 t.Fatalf("New request: %v", err) 585 } 586 req.Header.Set("Accept", "text/html") 587 resp, err := http.DefaultClient.Do(req) 588 if err != nil { 589 t.Fatalf("HTTP request: %v", err) 590 } 591 defer resp.Body.Close() 592 b, err := ioutil.ReadAll(resp.Body) 593 if err != nil { 594 t.Fatalf("Read response body: %v", err) 595 } 596 597 body := string(b) 598 599 for _, f := range c.pageFragments { 600 if !strings.Contains(body, f) { 601 isexpectedfailrequest := false 602 603 for _, r := range expectedfailrequests { 604 if k == r { 605 isexpectedfailrequest = true 606 } 607 } 608 if !isexpectedfailrequest { 609 t.Errorf("Response list body %q does not contain %q: body %q", k, f, body) 610 } 611 } 612 } 613 }) 614 } 615 616 nonhashtests := []string{ 617 srv.URL + "/bzz:/name", 618 srv.URL + "/bzz-immutable:/nonhash", 619 srv.URL + "/bzz-raw:/nonhash", 620 srv.URL + "/bzz-list:/nonhash", 621 srv.URL + "/bzz-hash:/nonhash", 622 } 623 624 nonhashresponses := []string{ 625 `cannot resolve name: no DNS to resolve name: "name"`, 626 `cannot resolve nonhash: no DNS to resolve name: "nonhash"`, 627 `cannot resolve nonhash: no DNS to resolve name: "nonhash"`, 628 `cannot resolve nonhash: no DNS to resolve name: "nonhash"`, 629 `cannot resolve nonhash: no DNS to resolve name: "nonhash"`, 630 } 631 632 for i, url := range nonhashtests { 633 var resp *http.Response 634 var respbody []byte 635 636 resp, err = http.Get(url) 637 638 if err != nil { 639 t.Fatalf("Request failed: %v", err) 640 } 641 defer resp.Body.Close() 642 respbody, err = ioutil.ReadAll(resp.Body) 643 if err != nil { 644 t.Fatalf("ReadAll failed: %v", err) 645 } 646 if !strings.Contains(string(respbody), nonhashresponses[i]) { 647 t.Fatalf("Non-Hash response body does not match, expected: %v, got: %v", nonhashresponses[i], string(respbody)) 648 } 649 } 650 } 651 652 func TestBzzTar(t *testing.T) { 653 testBzzTar(false, t) 654 testBzzTar(true, t) 655 } 656 657 func testBzzTar(encrypted bool, t *testing.T) { 658 srv := testutil.NewTestSwarmServer(t, serverFunc, nil) 659 defer srv.Close() 660 fileNames := []string{"tmp1.txt", "tmp2.lock", "tmp3.rtf"} 661 fileContents := []string{"tmp1textfilevalue", "tmp2lockfilelocked", "tmp3isjustaplaintextfile"} 662 663 buf := &bytes.Buffer{} 664 tw := tar.NewWriter(buf) 665 defer tw.Close() 666 667 for i, v := range fileNames { 668 size := int64(len(fileContents[i])) 669 hdr := &tar.Header{ 670 Name: v, 671 Mode: 0644, 672 Size: size, 673 ModTime: time.Now(), 674 Xattrs: map[string]string{ 675 "user.swarm.content-type": "text/plain", 676 }, 677 } 678 if err := tw.WriteHeader(hdr); err != nil { 679 t.Fatal(err) 680 } 681 682 // copy the file into the tar stream 683 n, err := io.Copy(tw, bytes.NewBufferString(fileContents[i])) 684 if err != nil { 685 t.Fatal(err) 686 } else if n != size { 687 t.Fatal("size mismatch") 688 } 689 } 690 691 //post tar stream 692 url := srv.URL + "/bzz:/" 693 if encrypted { 694 url = url + "encrypt" 695 } 696 req, err := http.NewRequest("POST", url, buf) 697 if err != nil { 698 t.Fatal(err) 699 } 700 req.Header.Add("Content-Type", "application/x-tar") 701 client := &http.Client{} 702 resp2, err := client.Do(req) 703 if err != nil { 704 t.Fatal(err) 705 } 706 if resp2.StatusCode != http.StatusOK { 707 t.Fatalf("err %s", resp2.Status) 708 } 709 swarmHash, err := ioutil.ReadAll(resp2.Body) 710 resp2.Body.Close() 711 if err != nil { 712 t.Fatal(err) 713 } 714 715 // now do a GET to get a tarball back 716 req, err = http.NewRequest("GET", fmt.Sprintf(srv.URL+"/bzz:/%s", string(swarmHash)), nil) 717 if err != nil { 718 t.Fatal(err) 719 } 720 req.Header.Add("Accept", "application/x-tar") 721 resp2, err = client.Do(req) 722 if err != nil { 723 t.Fatal(err) 724 } 725 defer resp2.Body.Close() 726 727 if h := resp2.Header.Get("Content-Type"); h != "application/x-tar" { 728 t.Fatalf("Content-Type header expected: application/x-tar, got: %s", h) 729 } 730 731 expectedFileName := string(swarmHash) + ".tar" 732 expectedContentDisposition := fmt.Sprintf("inline; filename=\"%s\"", expectedFileName) 733 if h := resp2.Header.Get("Content-Disposition"); h != expectedContentDisposition { 734 t.Fatalf("Content-Disposition header expected: %s, got: %s", expectedContentDisposition, h) 735 } 736 737 file, err := ioutil.TempFile("", "swarm-downloaded-tarball") 738 if err != nil { 739 t.Fatal(err) 740 } 741 defer os.Remove(file.Name()) 742 _, err = io.Copy(file, resp2.Body) 743 if err != nil { 744 t.Fatalf("error getting tarball: %v", err) 745 } 746 file.Sync() 747 file.Close() 748 749 tarFileHandle, err := os.Open(file.Name()) 750 if err != nil { 751 t.Fatal(err) 752 } 753 tr := tar.NewReader(tarFileHandle) 754 755 for { 756 hdr, err := tr.Next() 757 if err == io.EOF { 758 break 759 } else if err != nil { 760 t.Fatalf("error reading tar stream: %s", err) 761 } 762 bb := make([]byte, hdr.Size) 763 _, err = tr.Read(bb) 764 if err != nil && err != io.EOF { 765 t.Fatal(err) 766 } 767 passed := false 768 for i, v := range fileNames { 769 if v == hdr.Name { 770 if string(bb) == fileContents[i] { 771 passed = true 772 break 773 } 774 } 775 } 776 if !passed { 777 t.Fatalf("file %s did not pass content assertion", hdr.Name) 778 } 779 } 780 } 781 782 // TestBzzRootRedirect tests that getting the root path of a manifest without 783 // a trailing slash gets redirected to include the trailing slash so that 784 // relative URLs work as expected. 785 func TestBzzRootRedirect(t *testing.T) { 786 testBzzRootRedirect(false, t) 787 } 788 func TestBzzRootRedirectEncrypted(t *testing.T) { 789 testBzzRootRedirect(true, t) 790 } 791 792 func testBzzRootRedirect(toEncrypt bool, t *testing.T) { 793 srv := testutil.NewTestSwarmServer(t, serverFunc, nil) 794 defer srv.Close() 795 796 // create a manifest with some data at the root path 797 client := swarm.NewClient(srv.URL) 798 data := []byte("data") 799 file := &swarm.File{ 800 ReadCloser: ioutil.NopCloser(bytes.NewReader(data)), 801 ManifestEntry: api.ManifestEntry{ 802 Path: "", 803 ContentType: "text/plain", 804 Size: int64(len(data)), 805 }, 806 } 807 hash, err := client.Upload(file, "", toEncrypt) 808 if err != nil { 809 t.Fatal(err) 810 } 811 812 // define a CheckRedirect hook which ensures there is only a single 813 // redirect to the correct URL 814 redirected := false 815 httpClient := http.Client{ 816 CheckRedirect: func(req *http.Request, via []*http.Request) error { 817 if redirected { 818 return errors.New("too many redirects") 819 } 820 redirected = true 821 expectedPath := "/bzz:/" + hash + "/" 822 if req.URL.Path != expectedPath { 823 return fmt.Errorf("expected redirect to %q, got %q", expectedPath, req.URL.Path) 824 } 825 return nil 826 }, 827 } 828 829 // perform the GET request and assert the response 830 res, err := httpClient.Get(srv.URL + "/bzz:/" + hash) 831 if err != nil { 832 t.Fatal(err) 833 } 834 defer res.Body.Close() 835 if !redirected { 836 t.Fatal("expected GET /bzz:/<hash> to redirect to /bzz:/<hash>/ but it didn't") 837 } 838 gotData, err := ioutil.ReadAll(res.Body) 839 if err != nil { 840 t.Fatal(err) 841 } 842 if !bytes.Equal(gotData, data) { 843 t.Fatalf("expected response to equal %q, got %q", data, gotData) 844 } 845 } 846 847 func TestMethodsNotAllowed(t *testing.T) { 848 srv := testutil.NewTestSwarmServer(t, serverFunc, nil) 849 defer srv.Close() 850 databytes := "bar" 851 for _, c := range []struct { 852 url string 853 code int 854 }{ 855 { 856 url: fmt.Sprintf("%s/bzz-list:/", srv.URL), 857 code: http.StatusMethodNotAllowed, 858 }, { 859 url: fmt.Sprintf("%s/bzz-hash:/", srv.URL), 860 code: http.StatusMethodNotAllowed, 861 }, 862 { 863 url: fmt.Sprintf("%s/bzz-immutable:/", srv.URL), 864 code: http.StatusMethodNotAllowed, 865 }, 866 } { 867 res, _ := http.Post(c.url, "text/plain", bytes.NewReader([]byte(databytes))) 868 if res.StatusCode != c.code { 869 t.Fatalf("should have failed. requested url: %s, expected code %d, got %d", c.url, c.code, res.StatusCode) 870 } 871 } 872 873 } 874 875 func httpDo(httpMethod string, url string, reqBody io.Reader, headers map[string]string, verbose bool, t *testing.T) (*http.Response, string) { 876 // Build the Request 877 req, err := http.NewRequest(httpMethod, url, reqBody) 878 if err != nil { 879 t.Fatal(err) 880 } 881 for key, value := range headers { 882 req.Header.Set(key, value) 883 } 884 if verbose { 885 t.Log(req.Method, req.URL, req.Header, req.Body) 886 } 887 888 // Send Request out 889 httpClient := &http.Client{} 890 res, err := httpClient.Do(req) 891 if err != nil { 892 t.Fatal(err) 893 } 894 895 // Read the HTTP Body 896 buffer, err := ioutil.ReadAll(res.Body) 897 if err != nil { 898 t.Fatal(err) 899 } 900 defer res.Body.Close() 901 body := string(buffer) 902 903 return res, body 904 } 905 906 func TestGet(t *testing.T) { 907 srv := testutil.NewTestSwarmServer(t, serverFunc, nil) 908 defer srv.Close() 909 910 for _, testCase := range []struct { 911 uri string 912 method string 913 headers map[string]string 914 expectedStatusCode int 915 assertResponseBody string 916 verbose bool 917 }{ 918 { 919 uri: fmt.Sprintf("%s/", srv.URL), 920 method: "GET", 921 headers: map[string]string{"Accept": "text/html"}, 922 expectedStatusCode: http.StatusOK, 923 assertResponseBody: "Swarm: Serverless Hosting Incentivised Peer-To-Peer Storage And Content Distribution", 924 verbose: false, 925 }, 926 { 927 uri: fmt.Sprintf("%s/", srv.URL), 928 method: "GET", 929 headers: map[string]string{"Accept": "application/json"}, 930 expectedStatusCode: http.StatusOK, 931 assertResponseBody: "Swarm: Please request a valid ENS or swarm hash with the appropriate bzz scheme", 932 verbose: false, 933 }, 934 { 935 uri: fmt.Sprintf("%s/robots.txt", srv.URL), 936 method: "GET", 937 headers: map[string]string{"Accept": "text/html"}, 938 expectedStatusCode: http.StatusOK, 939 assertResponseBody: "User-agent: *\nDisallow: /", 940 verbose: false, 941 }, 942 { 943 uri: fmt.Sprintf("%s/nonexistent_path", srv.URL), 944 method: "GET", 945 headers: map[string]string{}, 946 expectedStatusCode: http.StatusNotFound, 947 verbose: false, 948 }, 949 { 950 uri: fmt.Sprintf("%s/bzz:asdf/", srv.URL), 951 method: "GET", 952 headers: map[string]string{}, 953 expectedStatusCode: http.StatusNotFound, 954 verbose: false, 955 }, 956 { 957 uri: fmt.Sprintf("%s/tbz2/", srv.URL), 958 method: "GET", 959 headers: map[string]string{}, 960 expectedStatusCode: http.StatusNotFound, 961 verbose: false, 962 }, 963 { 964 uri: fmt.Sprintf("%s/bzz-rack:/", srv.URL), 965 method: "GET", 966 headers: map[string]string{}, 967 expectedStatusCode: http.StatusNotFound, 968 verbose: false, 969 }, 970 { 971 uri: fmt.Sprintf("%s/bzz-ls", srv.URL), 972 method: "GET", 973 headers: map[string]string{}, 974 expectedStatusCode: http.StatusNotFound, 975 verbose: false, 976 }} { 977 t.Run("GET "+testCase.uri, func(t *testing.T) { 978 res, body := httpDo(testCase.method, testCase.uri, nil, testCase.headers, testCase.verbose, t) 979 if res.StatusCode != testCase.expectedStatusCode { 980 t.Fatalf("expected status code %d but got %d", testCase.expectedStatusCode, res.StatusCode) 981 } 982 if testCase.assertResponseBody != "" && !strings.Contains(body, testCase.assertResponseBody) { 983 t.Fatalf("expected response to be: %s but got: %s", testCase.assertResponseBody, body) 984 } 985 }) 986 } 987 } 988 989 func TestModify(t *testing.T) { 990 srv := testutil.NewTestSwarmServer(t, serverFunc, nil) 991 defer srv.Close() 992 993 swarmClient := swarm.NewClient(srv.URL) 994 data := []byte("data") 995 file := &swarm.File{ 996 ReadCloser: ioutil.NopCloser(bytes.NewReader(data)), 997 ManifestEntry: api.ManifestEntry{ 998 Path: "", 999 ContentType: "text/plain", 1000 Size: int64(len(data)), 1001 }, 1002 } 1003 1004 hash, err := swarmClient.Upload(file, "", false) 1005 if err != nil { 1006 t.Fatal(err) 1007 } 1008 1009 for _, testCase := range []struct { 1010 uri string 1011 method string 1012 headers map[string]string 1013 requestBody []byte 1014 expectedStatusCode int 1015 assertResponseBody string 1016 assertResponseHeaders map[string]string 1017 verbose bool 1018 }{ 1019 { 1020 uri: fmt.Sprintf("%s/bzz:/%s", srv.URL, hash), 1021 method: "DELETE", 1022 headers: map[string]string{}, 1023 expectedStatusCode: http.StatusOK, 1024 assertResponseBody: "8b634aea26eec353ac0ecbec20c94f44d6f8d11f38d4578a4c207a84c74ef731", 1025 verbose: false, 1026 }, 1027 { 1028 uri: fmt.Sprintf("%s/bzz:/%s", srv.URL, hash), 1029 method: "PUT", 1030 headers: map[string]string{}, 1031 expectedStatusCode: http.StatusMethodNotAllowed, 1032 verbose: false, 1033 }, 1034 { 1035 uri: fmt.Sprintf("%s/bzz-raw:/%s", srv.URL, hash), 1036 method: "PUT", 1037 headers: map[string]string{}, 1038 expectedStatusCode: http.StatusMethodNotAllowed, 1039 verbose: false, 1040 }, 1041 { 1042 uri: fmt.Sprintf("%s/bzz:/%s", srv.URL, hash), 1043 method: "PATCH", 1044 headers: map[string]string{}, 1045 expectedStatusCode: http.StatusMethodNotAllowed, 1046 verbose: false, 1047 }, 1048 { 1049 uri: fmt.Sprintf("%s/bzz-raw:/", srv.URL), 1050 method: "POST", 1051 headers: map[string]string{}, 1052 requestBody: []byte("POSTdata"), 1053 expectedStatusCode: http.StatusOK, 1054 assertResponseHeaders: map[string]string{"Content-Length": "64"}, 1055 verbose: false, 1056 }, 1057 { 1058 uri: fmt.Sprintf("%s/bzz-raw:/encrypt", srv.URL), 1059 method: "POST", 1060 headers: map[string]string{}, 1061 requestBody: []byte("POSTdata"), 1062 expectedStatusCode: http.StatusOK, 1063 assertResponseHeaders: map[string]string{"Content-Length": "128"}, 1064 verbose: false, 1065 }, 1066 } { 1067 t.Run(testCase.method+" "+testCase.uri, func(t *testing.T) { 1068 reqBody := bytes.NewReader(testCase.requestBody) 1069 res, body := httpDo(testCase.method, testCase.uri, reqBody, testCase.headers, testCase.verbose, t) 1070 1071 if res.StatusCode != testCase.expectedStatusCode { 1072 t.Fatalf("expected status code %d but got %d, %s", testCase.expectedStatusCode, res.StatusCode, body) 1073 } 1074 if testCase.assertResponseBody != "" && !strings.Contains(body, testCase.assertResponseBody) { 1075 t.Log(body) 1076 t.Fatalf("expected response %s but got %s", testCase.assertResponseBody, body) 1077 } 1078 for key, value := range testCase.assertResponseHeaders { 1079 if res.Header.Get(key) != value { 1080 t.Logf("expected %s=%s in HTTP response header but got %s", key, value, res.Header.Get(key)) 1081 } 1082 } 1083 }) 1084 } 1085 } 1086 1087 func TestMultiPartUpload(t *testing.T) { 1088 // POST /bzz:/ Content-Type: multipart/form-data 1089 verbose := false 1090 // Setup Swarm 1091 srv := testutil.NewTestSwarmServer(t, serverFunc, nil) 1092 defer srv.Close() 1093 1094 url := fmt.Sprintf("%s/bzz:/", srv.URL) 1095 1096 buf := new(bytes.Buffer) 1097 form := multipart.NewWriter(buf) 1098 form.WriteField("name", "John Doe") 1099 file1, _ := form.CreateFormFile("cv", "cv.txt") 1100 file1.Write([]byte("John Doe's Credentials")) 1101 file2, _ := form.CreateFormFile("profile_picture", "profile.jpg") 1102 file2.Write([]byte("imaginethisisjpegdata")) 1103 form.Close() 1104 1105 headers := map[string]string{ 1106 "Content-Type": form.FormDataContentType(), 1107 "Content-Length": strconv.Itoa(buf.Len()), 1108 } 1109 res, body := httpDo("POST", url, buf, headers, verbose, t) 1110 1111 if res.StatusCode != http.StatusOK { 1112 t.Fatalf("expected POST multipart/form-data to return 200, but it returned %d", res.StatusCode) 1113 } 1114 if len(body) != 64 { 1115 t.Fatalf("expected POST multipart/form-data to return a 64 char manifest but the answer was %d chars long", len(body)) 1116 } 1117 } 1118 1119 // TestBzzGetFileWithResolver tests fetching a file using a mocked ENS resolver 1120 func TestBzzGetFileWithResolver(t *testing.T) { 1121 resolver := newTestResolveValidator("") 1122 srv := testutil.NewTestSwarmServer(t, serverFunc, resolver) 1123 defer srv.Close() 1124 fileNames := []string{"dir1/tmp1.txt", "dir2/tmp2.lock", "dir3/tmp3.rtf"} 1125 fileContents := []string{"tmp1textfilevalue", "tmp2lockfilelocked", "tmp3isjustaplaintextfile"} 1126 1127 buf := &bytes.Buffer{} 1128 tw := tar.NewWriter(buf) 1129 1130 for i, v := range fileNames { 1131 size := len(fileContents[i]) 1132 hdr := &tar.Header{ 1133 Name: v, 1134 Mode: 0644, 1135 Size: int64(size), 1136 ModTime: time.Now(), 1137 Xattrs: map[string]string{ 1138 "user.swarm.content-type": "text/plain", 1139 }, 1140 } 1141 if err := tw.WriteHeader(hdr); err != nil { 1142 t.Fatal(err) 1143 } 1144 1145 // copy the file into the tar stream 1146 n, err := io.WriteString(tw, fileContents[i]) 1147 if err != nil { 1148 t.Fatal(err) 1149 } else if n != size { 1150 t.Fatal("size mismatch") 1151 } 1152 } 1153 1154 if err := tw.Close(); err != nil { 1155 t.Fatal(err) 1156 } 1157 1158 //post tar stream 1159 url := srv.URL + "/bzz:/" 1160 1161 req, err := http.NewRequest("POST", url, buf) 1162 if err != nil { 1163 t.Fatal(err) 1164 } 1165 req.Header.Add("Content-Type", "application/x-tar") 1166 client := &http.Client{} 1167 serverResponse, err := client.Do(req) 1168 if err != nil { 1169 t.Fatal(err) 1170 } 1171 if serverResponse.StatusCode != http.StatusOK { 1172 t.Fatalf("err %s", serverResponse.Status) 1173 } 1174 swarmHash, err := ioutil.ReadAll(serverResponse.Body) 1175 serverResponse.Body.Close() 1176 if err != nil { 1177 t.Fatal(err) 1178 } 1179 // set the resolved hash to be the swarm hash of what we've just uploaded 1180 hash := common.HexToHash(string(swarmHash)) 1181 resolver.hash = &hash 1182 for _, v := range []struct { 1183 addr string 1184 path string 1185 expectedStatusCode int 1186 expectedContentType string 1187 expectedFileName string 1188 }{ 1189 { 1190 addr: string(swarmHash), 1191 path: fileNames[0], 1192 expectedStatusCode: http.StatusOK, 1193 expectedContentType: "text/plain", 1194 expectedFileName: path.Base(fileNames[0]), 1195 }, 1196 { 1197 addr: "somebogusensname", 1198 path: fileNames[0], 1199 expectedStatusCode: http.StatusOK, 1200 expectedContentType: "text/plain", 1201 expectedFileName: path.Base(fileNames[0]), 1202 }, 1203 } { 1204 req, err := http.NewRequest("GET", fmt.Sprintf(srv.URL+"/bzz:/%s/%s", v.addr, v.path), nil) 1205 if err != nil { 1206 t.Fatal(err) 1207 } 1208 serverResponse, err := client.Do(req) 1209 if err != nil { 1210 t.Fatal(err) 1211 } 1212 defer serverResponse.Body.Close() 1213 if serverResponse.StatusCode != v.expectedStatusCode { 1214 t.Fatalf("expected %d, got %d", v.expectedStatusCode, serverResponse.StatusCode) 1215 } 1216 1217 if h := serverResponse.Header.Get("Content-Type"); h != v.expectedContentType { 1218 t.Fatalf("Content-Type header expected: %s, got %s", v.expectedContentType, h) 1219 } 1220 1221 expectedContentDisposition := fmt.Sprintf("inline; filename=\"%s\"", v.expectedFileName) 1222 if h := serverResponse.Header.Get("Content-Disposition"); h != expectedContentDisposition { 1223 t.Fatalf("Content-Disposition header expected: %s, got: %s", expectedContentDisposition, h) 1224 } 1225 1226 } 1227 } 1228 1229 // testResolver implements the Resolver interface and either returns the given 1230 // hash if it is set, or returns a "name not found" error 1231 type testResolveValidator struct { 1232 hash *common.Hash 1233 } 1234 1235 func newTestResolveValidator(addr string) *testResolveValidator { 1236 r := &testResolveValidator{} 1237 if addr != "" { 1238 hash := common.HexToHash(addr) 1239 r.hash = &hash 1240 } 1241 return r 1242 } 1243 1244 func (t *testResolveValidator) Resolve(addr string) (common.Hash, error) { 1245 if t.hash == nil { 1246 return common.Hash{}, fmt.Errorf("DNS name not found: %q", addr) 1247 } 1248 return *t.hash, nil 1249 } 1250 1251 func (t *testResolveValidator) Owner(node [32]byte) (addr common.Address, err error) { 1252 return 1253 } 1254 func (t *testResolveValidator) HeaderByNumber(context.Context, *big.Int) (header *types.Header, err error) { 1255 return 1256 }