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