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  }