github.com/vccomnet/occchain@v0.0.0-20181129092339-c57d4bab23fb/swarm/api/http/server_test.go (about) 1 // Copyright 2017 The go-blockchain Authors 2 // This file is part of the go-blockchain library. 3 // 4 // The go-blockchain 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-blockchain 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-blockchain 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/blockchain/go-blockchain/swarm/storage/feed/lookup" 42 43 "github.com/blockchain/go-blockchain/common" 44 "github.com/blockchain/go-blockchain/core/types" 45 "github.com/blockchain/go-blockchain/crypto" 46 "github.com/blockchain/go-blockchain/log" 47 "github.com/blockchain/go-blockchain/swarm/api" 48 swarm "github.com/blockchain/go-blockchain/swarm/api/client" 49 "github.com/blockchain/go-blockchain/swarm/multihash" 50 "github.com/blockchain/go-blockchain/swarm/storage" 51 "github.com/blockchain/go-blockchain/swarm/storage/feed" 52 "github.com/blockchain/go-blockchain/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 _, 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 goodQueryParameters := urlQuery.Encode() // save the query parameters for a second attempt 337 338 // create bad query parameters in which the signature is missing 339 urlQuery.Del("signature") 340 testUrl.RawQuery = urlQuery.Encode() 341 342 // 1st attempt with bad query parameters in which the signature is missing 343 resp, err = http.Post(testUrl.String(), "application/octet-stream", bytes.NewReader(body)) 344 if err != nil { 345 t.Fatal(err) 346 } 347 defer resp.Body.Close() 348 expectedCode := http.StatusBadRequest 349 if resp.StatusCode != expectedCode { 350 t.Fatalf("Update returned %s. Expected %d", resp.Status, expectedCode) 351 } 352 353 // 2nd attempt with bad query parameters in which the signature is of incorrect length 354 urlQuery.Set("signature", "0xabcd") // should be 130 hex chars 355 resp, err = http.Post(testUrl.String(), "application/octet-stream", bytes.NewReader(body)) 356 if err != nil { 357 t.Fatal(err) 358 } 359 defer resp.Body.Close() 360 expectedCode = http.StatusBadRequest 361 if resp.StatusCode != expectedCode { 362 t.Fatalf("Update returned %s. Expected %d", resp.Status, expectedCode) 363 } 364 365 // 3rd attempt, with good query parameters: 366 testUrl.RawQuery = goodQueryParameters 367 resp, err = http.Post(testUrl.String(), "application/octet-stream", bytes.NewReader(body)) 368 if err != nil { 369 t.Fatal(err) 370 } 371 defer resp.Body.Close() 372 expectedCode = http.StatusOK 373 if resp.StatusCode != expectedCode { 374 t.Fatalf("Update returned %s. Expected %d", resp.Status, expectedCode) 375 } 376 377 // get latest update through bzz-feed directly 378 log.Info("get update 1.2") 379 testBzzResUrl = fmt.Sprintf("%s/bzz-feed:/%s", srv.URL, correctManifestAddrHex) 380 resp, err = http.Get(testBzzResUrl) 381 if err != nil { 382 t.Fatal(err) 383 } 384 defer resp.Body.Close() 385 if resp.StatusCode != http.StatusOK { 386 t.Fatalf("err %s", resp.Status) 387 } 388 b, err = ioutil.ReadAll(resp.Body) 389 if err != nil { 390 t.Fatal(err) 391 } 392 if !bytes.Equal(update2Data, b) { 393 t.Fatalf("Expected body '%x', got '%x'", update2Data, b) 394 } 395 396 // test manifest-less queries 397 log.Info("get first update in update1Timestamp via direct query") 398 query := feed.NewQuery(&updateRequest.Feed, update1Timestamp, lookup.NoClue) 399 400 urlq, err := url.Parse(fmt.Sprintf("%s/bzz-feed:/", srv.URL)) 401 if err != nil { 402 t.Fatal(err) 403 } 404 405 values := urlq.Query() 406 query.AppendValues(values) // this adds feed query parameters 407 urlq.RawQuery = values.Encode() 408 resp, err = http.Get(urlq.String()) 409 if err != nil { 410 t.Fatal(err) 411 } 412 defer resp.Body.Close() 413 if resp.StatusCode != http.StatusOK { 414 t.Fatalf("err %s", resp.Status) 415 } 416 b, err = ioutil.ReadAll(resp.Body) 417 if err != nil { 418 t.Fatal(err) 419 } 420 if !bytes.Equal(update1Data, b) { 421 t.Fatalf("Expected body '%x', got '%x'", update1Data, b) 422 } 423 424 } 425 426 func TestBzzGetPath(t *testing.T) { 427 testBzzGetPath(false, t) 428 testBzzGetPath(true, t) 429 } 430 431 func testBzzGetPath(encrypted bool, t *testing.T) { 432 var err error 433 434 testmanifest := []string{ 435 `{"entries":[{"path":"b","hash":"011b4d03dd8c01f1049143cf9c4c817e4b167f1d1b83e5c6f0f10d89ba1e7bce","contentType":"","status":0},{"path":"c","hash":"011b4d03dd8c01f1049143cf9c4c817e4b167f1d1b83e5c6f0f10d89ba1e7bce","contentType":"","status":0}]}`, 436 `{"entries":[{"path":"a","hash":"011b4d03dd8c01f1049143cf9c4c817e4b167f1d1b83e5c6f0f10d89ba1e7bce","contentType":"","status":0},{"path":"b/","hash":"<key0>","contentType":"application/bzz-manifest+json","status":0}]}`, 437 `{"entries":[{"path":"a/","hash":"<key1>","contentType":"application/bzz-manifest+json","status":0}]}`, 438 } 439 440 testrequests := make(map[string]int) 441 testrequests["/"] = 2 442 testrequests["/a/"] = 1 443 testrequests["/a/b/"] = 0 444 testrequests["/x"] = 0 445 testrequests[""] = 0 446 447 expectedfailrequests := []string{"", "/x"} 448 449 reader := [3]*bytes.Reader{} 450 451 addr := [3]storage.Address{} 452 453 srv := testutil.NewTestSwarmServer(t, serverFunc, nil) 454 defer srv.Close() 455 456 for i, mf := range testmanifest { 457 reader[i] = bytes.NewReader([]byte(mf)) 458 var wait func(context.Context) error 459 ctx := context.TODO() 460 addr[i], wait, err = srv.FileStore.Store(ctx, reader[i], int64(len(mf)), encrypted) 461 if err != nil { 462 t.Fatal(err) 463 } 464 for j := i + 1; j < len(testmanifest); j++ { 465 testmanifest[j] = strings.Replace(testmanifest[j], fmt.Sprintf("<key%v>", i), addr[i].Hex(), -1) 466 } 467 err = wait(ctx) 468 if err != nil { 469 t.Fatal(err) 470 } 471 } 472 473 rootRef := addr[2].Hex() 474 475 _, err = http.Get(srv.URL + "/bzz-raw:/" + rootRef + "/a") 476 if err != nil { 477 t.Fatalf("Failed to connect to proxy: %v", err) 478 } 479 480 for k, v := range testrequests { 481 var resp *http.Response 482 var respbody []byte 483 484 url := srv.URL + "/bzz-raw:/" 485 if k != "" { 486 url += rootRef + "/" + k[1:] + "?content_type=text/plain" 487 } 488 resp, err = http.Get(url) 489 if err != nil { 490 t.Fatalf("Request failed: %v", err) 491 } 492 defer resp.Body.Close() 493 respbody, err = ioutil.ReadAll(resp.Body) 494 if err != nil { 495 t.Fatalf("Error while reading response body: %v", err) 496 } 497 498 if string(respbody) != testmanifest[v] { 499 isexpectedfailrequest := false 500 501 for _, r := range expectedfailrequests { 502 if k == r { 503 isexpectedfailrequest = true 504 } 505 } 506 if !isexpectedfailrequest { 507 t.Fatalf("Response body does not match, expected: %v, got %v", testmanifest[v], string(respbody)) 508 } 509 } 510 } 511 512 for k, v := range testrequests { 513 var resp *http.Response 514 var respbody []byte 515 516 url := srv.URL + "/bzz-hash:/" 517 if k != "" { 518 url += rootRef + "/" + k[1:] 519 } 520 resp, err = http.Get(url) 521 if err != nil { 522 t.Fatalf("Request failed: %v", err) 523 } 524 defer resp.Body.Close() 525 respbody, err = ioutil.ReadAll(resp.Body) 526 if err != nil { 527 t.Fatalf("Read request body: %v", err) 528 } 529 530 if string(respbody) != addr[v].Hex() { 531 isexpectedfailrequest := false 532 533 for _, r := range expectedfailrequests { 534 if k == r { 535 isexpectedfailrequest = true 536 } 537 } 538 if !isexpectedfailrequest { 539 t.Fatalf("Response body does not match, expected: %v, got %v", addr[v], string(respbody)) 540 } 541 } 542 } 543 544 ref := addr[2].Hex() 545 546 for _, c := range []struct { 547 path string 548 json string 549 pageFragments []string 550 }{ 551 { 552 path: "/", 553 json: `{"common_prefixes":["a/"]}`, 554 pageFragments: []string{ 555 fmt.Sprintf("Swarm index of bzz:/%s/", ref), 556 `<a class="normal-link" href="a/">a/</a>`, 557 }, 558 }, 559 { 560 path: "/a/", 561 json: `{"common_prefixes":["a/b/"],"entries":[{"hash":"011b4d03dd8c01f1049143cf9c4c817e4b167f1d1b83e5c6f0f10d89ba1e7bce","path":"a/a","mod_time":"0001-01-01T00:00:00Z"}]}`, 562 pageFragments: []string{ 563 fmt.Sprintf("Swarm index of bzz:/%s/a/", ref), 564 `<a class="normal-link" href="b/">b/</a>`, 565 fmt.Sprintf(`<a class="normal-link" href="/bzz:/%s/a/a">a</a>`, ref), 566 }, 567 }, 568 { 569 path: "/a/b/", 570 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"}]}`, 571 pageFragments: []string{ 572 fmt.Sprintf("Swarm index of bzz:/%s/a/b/", ref), 573 fmt.Sprintf(`<a class="normal-link" href="/bzz:/%s/a/b/b">b</a>`, ref), 574 fmt.Sprintf(`<a class="normal-link" href="/bzz:/%s/a/b/c">c</a>`, ref), 575 }, 576 }, 577 { 578 path: "/x", 579 }, 580 { 581 path: "", 582 }, 583 } { 584 k := c.path 585 url := srv.URL + "/bzz-list:/" 586 if k != "" { 587 url += rootRef + "/" + k[1:] 588 } 589 t.Run("json list "+c.path, func(t *testing.T) { 590 resp, err := http.Get(url) 591 if err != nil { 592 t.Fatalf("HTTP request: %v", err) 593 } 594 defer resp.Body.Close() 595 respbody, err := ioutil.ReadAll(resp.Body) 596 if err != nil { 597 t.Fatalf("Read response body: %v", err) 598 } 599 600 body := strings.TrimSpace(string(respbody)) 601 if body != c.json { 602 isexpectedfailrequest := false 603 604 for _, r := range expectedfailrequests { 605 if k == r { 606 isexpectedfailrequest = true 607 } 608 } 609 if !isexpectedfailrequest { 610 t.Errorf("Response list body %q does not match, expected: %v, got %v", k, c.json, body) 611 } 612 } 613 }) 614 t.Run("html list "+c.path, func(t *testing.T) { 615 req, err := http.NewRequest(http.MethodGet, url, nil) 616 if err != nil { 617 t.Fatalf("New request: %v", err) 618 } 619 req.Header.Set("Accept", "text/html") 620 resp, err := http.DefaultClient.Do(req) 621 if err != nil { 622 t.Fatalf("HTTP request: %v", err) 623 } 624 defer resp.Body.Close() 625 b, err := ioutil.ReadAll(resp.Body) 626 if err != nil { 627 t.Fatalf("Read response body: %v", err) 628 } 629 630 body := string(b) 631 632 for _, f := range c.pageFragments { 633 if !strings.Contains(body, f) { 634 isexpectedfailrequest := false 635 636 for _, r := range expectedfailrequests { 637 if k == r { 638 isexpectedfailrequest = true 639 } 640 } 641 if !isexpectedfailrequest { 642 t.Errorf("Response list body %q does not contain %q: body %q", k, f, body) 643 } 644 } 645 } 646 }) 647 } 648 649 nonhashtests := []string{ 650 srv.URL + "/bzz:/name", 651 srv.URL + "/bzz-immutable:/nonhash", 652 srv.URL + "/bzz-raw:/nonhash", 653 srv.URL + "/bzz-list:/nonhash", 654 srv.URL + "/bzz-hash:/nonhash", 655 } 656 657 nonhashresponses := []string{ 658 `cannot resolve name: no DNS to resolve name: "name"`, 659 `cannot resolve nonhash: no DNS to resolve name: "nonhash"`, 660 `cannot resolve nonhash: no DNS to resolve name: "nonhash"`, 661 `cannot resolve nonhash: no DNS to resolve name: "nonhash"`, 662 `cannot resolve nonhash: no DNS to resolve name: "nonhash"`, 663 } 664 665 for i, url := range nonhashtests { 666 var resp *http.Response 667 var respbody []byte 668 669 resp, err = http.Get(url) 670 671 if err != nil { 672 t.Fatalf("Request failed: %v", err) 673 } 674 defer resp.Body.Close() 675 respbody, err = ioutil.ReadAll(resp.Body) 676 if err != nil { 677 t.Fatalf("ReadAll failed: %v", err) 678 } 679 if !strings.Contains(string(respbody), nonhashresponses[i]) { 680 t.Fatalf("Non-Hash response body does not match, expected: %v, got: %v", nonhashresponses[i], string(respbody)) 681 } 682 } 683 } 684 685 func TestBzzTar(t *testing.T) { 686 testBzzTar(false, t) 687 testBzzTar(true, t) 688 } 689 690 func testBzzTar(encrypted bool, t *testing.T) { 691 srv := testutil.NewTestSwarmServer(t, serverFunc, nil) 692 defer srv.Close() 693 fileNames := []string{"tmp1.txt", "tmp2.lock", "tmp3.rtf"} 694 fileContents := []string{"tmp1textfilevalue", "tmp2lockfilelocked", "tmp3isjustaplaintextfile"} 695 696 buf := &bytes.Buffer{} 697 tw := tar.NewWriter(buf) 698 defer tw.Close() 699 700 for i, v := range fileNames { 701 size := int64(len(fileContents[i])) 702 hdr := &tar.Header{ 703 Name: v, 704 Mode: 0644, 705 Size: size, 706 ModTime: time.Now(), 707 Xattrs: map[string]string{ 708 "user.swarm.content-type": "text/plain", 709 }, 710 } 711 if err := tw.WriteHeader(hdr); err != nil { 712 t.Fatal(err) 713 } 714 715 // copy the file into the tar stream 716 n, err := io.Copy(tw, bytes.NewBufferString(fileContents[i])) 717 if err != nil { 718 t.Fatal(err) 719 } else if n != size { 720 t.Fatal("size mismatch") 721 } 722 } 723 724 //post tar stream 725 url := srv.URL + "/bzz:/" 726 if encrypted { 727 url = url + "encrypt" 728 } 729 req, err := http.NewRequest("POST", url, buf) 730 if err != nil { 731 t.Fatal(err) 732 } 733 req.Header.Add("Content-Type", "application/x-tar") 734 client := &http.Client{} 735 resp2, err := client.Do(req) 736 if err != nil { 737 t.Fatal(err) 738 } 739 if resp2.StatusCode != http.StatusOK { 740 t.Fatalf("err %s", resp2.Status) 741 } 742 swarmHash, err := ioutil.ReadAll(resp2.Body) 743 resp2.Body.Close() 744 if err != nil { 745 t.Fatal(err) 746 } 747 748 // now do a GET to get a tarball back 749 req, err = http.NewRequest("GET", fmt.Sprintf(srv.URL+"/bzz:/%s", string(swarmHash)), nil) 750 if err != nil { 751 t.Fatal(err) 752 } 753 req.Header.Add("Accept", "application/x-tar") 754 resp2, err = client.Do(req) 755 if err != nil { 756 t.Fatal(err) 757 } 758 defer resp2.Body.Close() 759 760 if h := resp2.Header.Get("Content-Type"); h != "application/x-tar" { 761 t.Fatalf("Content-Type header expected: application/x-tar, got: %s", h) 762 } 763 764 expectedFileName := string(swarmHash) + ".tar" 765 expectedContentDisposition := fmt.Sprintf("inline; filename=\"%s\"", expectedFileName) 766 if h := resp2.Header.Get("Content-Disposition"); h != expectedContentDisposition { 767 t.Fatalf("Content-Disposition header expected: %s, got: %s", expectedContentDisposition, h) 768 } 769 770 file, err := ioutil.TempFile("", "swarm-downloaded-tarball") 771 if err != nil { 772 t.Fatal(err) 773 } 774 defer os.Remove(file.Name()) 775 _, err = io.Copy(file, resp2.Body) 776 if err != nil { 777 t.Fatalf("error getting tarball: %v", err) 778 } 779 file.Sync() 780 file.Close() 781 782 tarFileHandle, err := os.Open(file.Name()) 783 if err != nil { 784 t.Fatal(err) 785 } 786 tr := tar.NewReader(tarFileHandle) 787 788 for { 789 hdr, err := tr.Next() 790 if err == io.EOF { 791 break 792 } else if err != nil { 793 t.Fatalf("error reading tar stream: %s", err) 794 } 795 bb := make([]byte, hdr.Size) 796 _, err = tr.Read(bb) 797 if err != nil && err != io.EOF { 798 t.Fatal(err) 799 } 800 passed := false 801 for i, v := range fileNames { 802 if v == hdr.Name { 803 if string(bb) == fileContents[i] { 804 passed = true 805 break 806 } 807 } 808 } 809 if !passed { 810 t.Fatalf("file %s did not pass content assertion", hdr.Name) 811 } 812 } 813 } 814 815 // TestBzzRootRedirect tests that getting the root path of a manifest without 816 // a trailing slash gets redirected to include the trailing slash so that 817 // relative URLs work as expected. 818 func TestBzzRootRedirect(t *testing.T) { 819 testBzzRootRedirect(false, t) 820 } 821 func TestBzzRootRedirectEncrypted(t *testing.T) { 822 testBzzRootRedirect(true, t) 823 } 824 825 func testBzzRootRedirect(toEncrypt bool, t *testing.T) { 826 srv := testutil.NewTestSwarmServer(t, serverFunc, nil) 827 defer srv.Close() 828 829 // create a manifest with some data at the root path 830 client := swarm.NewClient(srv.URL) 831 data := []byte("data") 832 file := &swarm.File{ 833 ReadCloser: ioutil.NopCloser(bytes.NewReader(data)), 834 ManifestEntry: api.ManifestEntry{ 835 Path: "", 836 ContentType: "text/plain", 837 Size: int64(len(data)), 838 }, 839 } 840 hash, err := client.Upload(file, "", toEncrypt) 841 if err != nil { 842 t.Fatal(err) 843 } 844 845 // define a CheckRedirect hook which ensures there is only a single 846 // redirect to the correct URL 847 redirected := false 848 httpClient := http.Client{ 849 CheckRedirect: func(req *http.Request, via []*http.Request) error { 850 if redirected { 851 return errors.New("too many redirects") 852 } 853 redirected = true 854 expectedPath := "/bzz:/" + hash + "/" 855 if req.URL.Path != expectedPath { 856 return fmt.Errorf("expected redirect to %q, got %q", expectedPath, req.URL.Path) 857 } 858 return nil 859 }, 860 } 861 862 // perform the GET request and assert the response 863 res, err := httpClient.Get(srv.URL + "/bzz:/" + hash) 864 if err != nil { 865 t.Fatal(err) 866 } 867 defer res.Body.Close() 868 if !redirected { 869 t.Fatal("expected GET /bzz:/<hash> to redirect to /bzz:/<hash>/ but it didn't") 870 } 871 gotData, err := ioutil.ReadAll(res.Body) 872 if err != nil { 873 t.Fatal(err) 874 } 875 if !bytes.Equal(gotData, data) { 876 t.Fatalf("expected response to equal %q, got %q", data, gotData) 877 } 878 } 879 880 func TestMethodsNotAllowed(t *testing.T) { 881 srv := testutil.NewTestSwarmServer(t, serverFunc, nil) 882 defer srv.Close() 883 databytes := "bar" 884 for _, c := range []struct { 885 url string 886 code int 887 }{ 888 { 889 url: fmt.Sprintf("%s/bzz-list:/", srv.URL), 890 code: http.StatusMethodNotAllowed, 891 }, { 892 url: fmt.Sprintf("%s/bzz-hash:/", srv.URL), 893 code: http.StatusMethodNotAllowed, 894 }, 895 { 896 url: fmt.Sprintf("%s/bzz-immutable:/", srv.URL), 897 code: http.StatusMethodNotAllowed, 898 }, 899 } { 900 res, _ := http.Post(c.url, "text/plain", bytes.NewReader([]byte(databytes))) 901 if res.StatusCode != c.code { 902 t.Fatalf("should have failed. requested url: %s, expected code %d, got %d", c.url, c.code, res.StatusCode) 903 } 904 } 905 906 } 907 908 func httpDo(httpMethod string, url string, reqBody io.Reader, headers map[string]string, verbose bool, t *testing.T) (*http.Response, string) { 909 // Build the Request 910 req, err := http.NewRequest(httpMethod, url, reqBody) 911 if err != nil { 912 t.Fatal(err) 913 } 914 for key, value := range headers { 915 req.Header.Set(key, value) 916 } 917 if verbose { 918 t.Log(req.Method, req.URL, req.Header, req.Body) 919 } 920 921 // Send Request out 922 httpClient := &http.Client{} 923 res, err := httpClient.Do(req) 924 if err != nil { 925 t.Fatal(err) 926 } 927 928 // Read the HTTP Body 929 buffer, err := ioutil.ReadAll(res.Body) 930 if err != nil { 931 t.Fatal(err) 932 } 933 defer res.Body.Close() 934 body := string(buffer) 935 936 return res, body 937 } 938 939 func TestGet(t *testing.T) { 940 srv := testutil.NewTestSwarmServer(t, serverFunc, nil) 941 defer srv.Close() 942 943 for _, testCase := range []struct { 944 uri string 945 method string 946 headers map[string]string 947 expectedStatusCode int 948 assertResponseBody string 949 verbose bool 950 }{ 951 { 952 uri: fmt.Sprintf("%s/", srv.URL), 953 method: "GET", 954 headers: map[string]string{"Accept": "text/html"}, 955 expectedStatusCode: http.StatusOK, 956 assertResponseBody: "Swarm: Serverless Hosting Incentivised Peer-To-Peer Storage And Content Distribution", 957 verbose: false, 958 }, 959 { 960 uri: fmt.Sprintf("%s/", srv.URL), 961 method: "GET", 962 headers: map[string]string{"Accept": "application/json"}, 963 expectedStatusCode: http.StatusOK, 964 assertResponseBody: "Swarm: Please request a valid ENS or swarm hash with the appropriate bzz scheme", 965 verbose: false, 966 }, 967 { 968 uri: fmt.Sprintf("%s/robots.txt", srv.URL), 969 method: "GET", 970 headers: map[string]string{"Accept": "text/html"}, 971 expectedStatusCode: http.StatusOK, 972 assertResponseBody: "User-agent: *\nDisallow: /", 973 verbose: false, 974 }, 975 { 976 uri: fmt.Sprintf("%s/nonexistent_path", srv.URL), 977 method: "GET", 978 headers: map[string]string{}, 979 expectedStatusCode: http.StatusNotFound, 980 verbose: false, 981 }, 982 { 983 uri: fmt.Sprintf("%s/bzz:asdf/", srv.URL), 984 method: "GET", 985 headers: map[string]string{}, 986 expectedStatusCode: http.StatusNotFound, 987 verbose: false, 988 }, 989 { 990 uri: fmt.Sprintf("%s/tbz2/", srv.URL), 991 method: "GET", 992 headers: map[string]string{}, 993 expectedStatusCode: http.StatusNotFound, 994 verbose: false, 995 }, 996 { 997 uri: fmt.Sprintf("%s/bzz-rack:/", srv.URL), 998 method: "GET", 999 headers: map[string]string{}, 1000 expectedStatusCode: http.StatusNotFound, 1001 verbose: false, 1002 }, 1003 { 1004 uri: fmt.Sprintf("%s/bzz-ls", srv.URL), 1005 method: "GET", 1006 headers: map[string]string{}, 1007 expectedStatusCode: http.StatusNotFound, 1008 verbose: false, 1009 }} { 1010 t.Run("GET "+testCase.uri, func(t *testing.T) { 1011 res, body := httpDo(testCase.method, testCase.uri, nil, testCase.headers, testCase.verbose, t) 1012 if res.StatusCode != testCase.expectedStatusCode { 1013 t.Fatalf("expected status code %d but got %d", testCase.expectedStatusCode, res.StatusCode) 1014 } 1015 if testCase.assertResponseBody != "" && !strings.Contains(body, testCase.assertResponseBody) { 1016 t.Fatalf("expected response to be: %s but got: %s", testCase.assertResponseBody, body) 1017 } 1018 }) 1019 } 1020 } 1021 1022 func TestModify(t *testing.T) { 1023 srv := testutil.NewTestSwarmServer(t, serverFunc, nil) 1024 defer srv.Close() 1025 1026 swarmClient := swarm.NewClient(srv.URL) 1027 data := []byte("data") 1028 file := &swarm.File{ 1029 ReadCloser: ioutil.NopCloser(bytes.NewReader(data)), 1030 ManifestEntry: api.ManifestEntry{ 1031 Path: "", 1032 ContentType: "text/plain", 1033 Size: int64(len(data)), 1034 }, 1035 } 1036 1037 hash, err := swarmClient.Upload(file, "", false) 1038 if err != nil { 1039 t.Fatal(err) 1040 } 1041 1042 for _, testCase := range []struct { 1043 uri string 1044 method string 1045 headers map[string]string 1046 requestBody []byte 1047 expectedStatusCode int 1048 assertResponseBody string 1049 assertResponseHeaders map[string]string 1050 verbose bool 1051 }{ 1052 { 1053 uri: fmt.Sprintf("%s/bzz:/%s", srv.URL, hash), 1054 method: "DELETE", 1055 headers: map[string]string{}, 1056 expectedStatusCode: http.StatusOK, 1057 assertResponseBody: "8b634aea26eec353ac0ecbec20c94f44d6f8d11f38d4578a4c207a84c74ef731", 1058 verbose: false, 1059 }, 1060 { 1061 uri: fmt.Sprintf("%s/bzz:/%s", srv.URL, hash), 1062 method: "PUT", 1063 headers: map[string]string{}, 1064 expectedStatusCode: http.StatusMethodNotAllowed, 1065 verbose: false, 1066 }, 1067 { 1068 uri: fmt.Sprintf("%s/bzz-raw:/%s", srv.URL, hash), 1069 method: "PUT", 1070 headers: map[string]string{}, 1071 expectedStatusCode: http.StatusMethodNotAllowed, 1072 verbose: false, 1073 }, 1074 { 1075 uri: fmt.Sprintf("%s/bzz:/%s", srv.URL, hash), 1076 method: "PATCH", 1077 headers: map[string]string{}, 1078 expectedStatusCode: http.StatusMethodNotAllowed, 1079 verbose: false, 1080 }, 1081 { 1082 uri: fmt.Sprintf("%s/bzz-raw:/", srv.URL), 1083 method: "POST", 1084 headers: map[string]string{}, 1085 requestBody: []byte("POSTdata"), 1086 expectedStatusCode: http.StatusOK, 1087 assertResponseHeaders: map[string]string{"Content-Length": "64"}, 1088 verbose: false, 1089 }, 1090 { 1091 uri: fmt.Sprintf("%s/bzz-raw:/encrypt", srv.URL), 1092 method: "POST", 1093 headers: map[string]string{}, 1094 requestBody: []byte("POSTdata"), 1095 expectedStatusCode: http.StatusOK, 1096 assertResponseHeaders: map[string]string{"Content-Length": "128"}, 1097 verbose: false, 1098 }, 1099 } { 1100 t.Run(testCase.method+" "+testCase.uri, func(t *testing.T) { 1101 reqBody := bytes.NewReader(testCase.requestBody) 1102 res, body := httpDo(testCase.method, testCase.uri, reqBody, testCase.headers, testCase.verbose, t) 1103 1104 if res.StatusCode != testCase.expectedStatusCode { 1105 t.Fatalf("expected status code %d but got %d, %s", testCase.expectedStatusCode, res.StatusCode, body) 1106 } 1107 if testCase.assertResponseBody != "" && !strings.Contains(body, testCase.assertResponseBody) { 1108 t.Log(body) 1109 t.Fatalf("expected response %s but got %s", testCase.assertResponseBody, body) 1110 } 1111 for key, value := range testCase.assertResponseHeaders { 1112 if res.Header.Get(key) != value { 1113 t.Logf("expected %s=%s in HTTP response header but got %s", key, value, res.Header.Get(key)) 1114 } 1115 } 1116 }) 1117 } 1118 } 1119 1120 func TestMultiPartUpload(t *testing.T) { 1121 // POST /bzz:/ Content-Type: multipart/form-data 1122 verbose := false 1123 // Setup Swarm 1124 srv := testutil.NewTestSwarmServer(t, serverFunc, nil) 1125 defer srv.Close() 1126 1127 url := fmt.Sprintf("%s/bzz:/", srv.URL) 1128 1129 buf := new(bytes.Buffer) 1130 form := multipart.NewWriter(buf) 1131 form.WriteField("name", "John Doe") 1132 file1, _ := form.CreateFormFile("cv", "cv.txt") 1133 file1.Write([]byte("John Doe's Credentials")) 1134 file2, _ := form.CreateFormFile("profile_picture", "profile.jpg") 1135 file2.Write([]byte("imaginethisisjpegdata")) 1136 form.Close() 1137 1138 headers := map[string]string{ 1139 "Content-Type": form.FormDataContentType(), 1140 "Content-Length": strconv.Itoa(buf.Len()), 1141 } 1142 res, body := httpDo("POST", url, buf, headers, verbose, t) 1143 1144 if res.StatusCode != http.StatusOK { 1145 t.Fatalf("expected POST multipart/form-data to return 200, but it returned %d", res.StatusCode) 1146 } 1147 if len(body) != 64 { 1148 t.Fatalf("expected POST multipart/form-data to return a 64 char manifest but the answer was %d chars long", len(body)) 1149 } 1150 } 1151 1152 // TestBzzGetFileWithResolver tests fetching a file using a mocked ENS resolver 1153 func TestBzzGetFileWithResolver(t *testing.T) { 1154 resolver := newTestResolveValidator("") 1155 srv := testutil.NewTestSwarmServer(t, serverFunc, resolver) 1156 defer srv.Close() 1157 fileNames := []string{"dir1/tmp1.txt", "dir2/tmp2.lock", "dir3/tmp3.rtf"} 1158 fileContents := []string{"tmp1textfilevalue", "tmp2lockfilelocked", "tmp3isjustaplaintextfile"} 1159 1160 buf := &bytes.Buffer{} 1161 tw := tar.NewWriter(buf) 1162 1163 for i, v := range fileNames { 1164 size := len(fileContents[i]) 1165 hdr := &tar.Header{ 1166 Name: v, 1167 Mode: 0644, 1168 Size: int64(size), 1169 ModTime: time.Now(), 1170 Xattrs: map[string]string{ 1171 "user.swarm.content-type": "text/plain", 1172 }, 1173 } 1174 if err := tw.WriteHeader(hdr); err != nil { 1175 t.Fatal(err) 1176 } 1177 1178 // copy the file into the tar stream 1179 n, err := io.WriteString(tw, fileContents[i]) 1180 if err != nil { 1181 t.Fatal(err) 1182 } else if n != size { 1183 t.Fatal("size mismatch") 1184 } 1185 } 1186 1187 if err := tw.Close(); err != nil { 1188 t.Fatal(err) 1189 } 1190 1191 //post tar stream 1192 url := srv.URL + "/bzz:/" 1193 1194 req, err := http.NewRequest("POST", url, buf) 1195 if err != nil { 1196 t.Fatal(err) 1197 } 1198 req.Header.Add("Content-Type", "application/x-tar") 1199 client := &http.Client{} 1200 serverResponse, err := client.Do(req) 1201 if err != nil { 1202 t.Fatal(err) 1203 } 1204 if serverResponse.StatusCode != http.StatusOK { 1205 t.Fatalf("err %s", serverResponse.Status) 1206 } 1207 swarmHash, err := ioutil.ReadAll(serverResponse.Body) 1208 serverResponse.Body.Close() 1209 if err != nil { 1210 t.Fatal(err) 1211 } 1212 // set the resolved hash to be the swarm hash of what we've just uploaded 1213 hash := common.HexToHash(string(swarmHash)) 1214 resolver.hash = &hash 1215 for _, v := range []struct { 1216 addr string 1217 path string 1218 expectedStatusCode int 1219 expectedContentType string 1220 expectedFileName string 1221 }{ 1222 { 1223 addr: string(swarmHash), 1224 path: fileNames[0], 1225 expectedStatusCode: http.StatusOK, 1226 expectedContentType: "text/plain", 1227 expectedFileName: path.Base(fileNames[0]), 1228 }, 1229 { 1230 addr: "somebogusensname", 1231 path: fileNames[0], 1232 expectedStatusCode: http.StatusOK, 1233 expectedContentType: "text/plain", 1234 expectedFileName: path.Base(fileNames[0]), 1235 }, 1236 } { 1237 req, err := http.NewRequest("GET", fmt.Sprintf(srv.URL+"/bzz:/%s/%s", v.addr, v.path), nil) 1238 if err != nil { 1239 t.Fatal(err) 1240 } 1241 serverResponse, err := client.Do(req) 1242 if err != nil { 1243 t.Fatal(err) 1244 } 1245 defer serverResponse.Body.Close() 1246 if serverResponse.StatusCode != v.expectedStatusCode { 1247 t.Fatalf("expected %d, got %d", v.expectedStatusCode, serverResponse.StatusCode) 1248 } 1249 1250 if h := serverResponse.Header.Get("Content-Type"); h != v.expectedContentType { 1251 t.Fatalf("Content-Type header expected: %s, got %s", v.expectedContentType, h) 1252 } 1253 1254 expectedContentDisposition := fmt.Sprintf("inline; filename=\"%s\"", v.expectedFileName) 1255 if h := serverResponse.Header.Get("Content-Disposition"); h != expectedContentDisposition { 1256 t.Fatalf("Content-Disposition header expected: %s, got: %s", expectedContentDisposition, h) 1257 } 1258 1259 } 1260 } 1261 1262 // testResolver implements the Resolver interface and either returns the given 1263 // hash if it is set, or returns a "name not found" error 1264 type testResolveValidator struct { 1265 hash *common.Hash 1266 } 1267 1268 func newTestResolveValidator(addr string) *testResolveValidator { 1269 r := &testResolveValidator{} 1270 if addr != "" { 1271 hash := common.HexToHash(addr) 1272 r.hash = &hash 1273 } 1274 return r 1275 } 1276 1277 func (t *testResolveValidator) Resolve(addr string) (common.Hash, error) { 1278 if t.hash == nil { 1279 return common.Hash{}, fmt.Errorf("DNS name not found: %q", addr) 1280 } 1281 return *t.hash, nil 1282 } 1283 1284 func (t *testResolveValidator) Owner(node [32]byte) (addr common.Address, err error) { 1285 return 1286 } 1287 func (t *testResolveValidator) HeaderByNumber(context.Context, *big.Int) (header *types.Header, err error) { 1288 return 1289 }