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