github.com/SmartMeshFoundation/Spectrum@v0.0.0-20220621030607-452a266fee1e/swarm/api/http/server_test.go (about) 1 // Copyright 2017 The Spectrum Authors 2 // This file is part of the Spectrum library. 3 // 4 // The Spectrum 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 Spectrum 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 Spectrum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package http_test 18 19 import ( 20 "bytes" 21 "errors" 22 "fmt" 23 "io/ioutil" 24 "net/http" 25 "strings" 26 "sync" 27 "testing" 28 29 "github.com/SmartMeshFoundation/Spectrum/common" 30 "github.com/SmartMeshFoundation/Spectrum/swarm/api" 31 swarm "github.com/SmartMeshFoundation/Spectrum/swarm/api/client" 32 "github.com/SmartMeshFoundation/Spectrum/swarm/storage" 33 "github.com/SmartMeshFoundation/Spectrum/swarm/testutil" 34 ) 35 36 func TestBzzGetPath(t *testing.T) { 37 38 var err error 39 40 testmanifest := []string{ 41 `{"entries":[{"path":"a/","hash":"674af7073604ebfc0282a4ab21e5ef1a3c22913866879ebc0816f8a89896b2ed","contentType":"application/bzz-manifest+json","status":0}]}`, 42 `{"entries":[{"path":"a","hash":"011b4d03dd8c01f1049143cf9c4c817e4b167f1d1b83e5c6f0f10d89ba1e7bce","contentType":"","status":0},{"path":"b/","hash":"0a87b1c3e4bf013686cdf107ec58590f2004610ee58cc2240f26939f691215f5","contentType":"application/bzz-manifest+json","status":0}]}`, 43 `{"entries":[{"path":"b","hash":"011b4d03dd8c01f1049143cf9c4c817e4b167f1d1b83e5c6f0f10d89ba1e7bce","contentType":"","status":0},{"path":"c","hash":"011b4d03dd8c01f1049143cf9c4c817e4b167f1d1b83e5c6f0f10d89ba1e7bce","contentType":"","status":0}]}`, 44 } 45 46 testrequests := make(map[string]int) 47 testrequests["/"] = 0 48 testrequests["/a/"] = 1 49 testrequests["/a/b/"] = 2 50 testrequests["/x"] = 0 51 testrequests[""] = 0 52 53 expectedfailrequests := []string{"", "/x"} 54 55 reader := [3]*bytes.Reader{} 56 57 key := [3]storage.Key{} 58 59 srv := testutil.NewTestSwarmServer(t) 60 defer srv.Close() 61 62 wg := &sync.WaitGroup{} 63 64 for i, mf := range testmanifest { 65 reader[i] = bytes.NewReader([]byte(mf)) 66 key[i], err = srv.Dpa.Store(reader[i], int64(len(mf)), wg, nil) 67 if err != nil { 68 t.Fatal(err) 69 } 70 wg.Wait() 71 } 72 73 _, err = http.Get(srv.URL + "/bzz-raw:/" + common.ToHex(key[0])[2:] + "/a") 74 if err != nil { 75 t.Fatalf("Failed to connect to proxy: %v", err) 76 } 77 78 for k, v := range testrequests { 79 var resp *http.Response 80 var respbody []byte 81 82 url := srv.URL + "/bzz-raw:/" 83 if k[:] != "" { 84 url += common.ToHex(key[0])[2:] + "/" + k[1:] + "?content_type=text/plain" 85 } 86 resp, err = http.Get(url) 87 if err != nil { 88 t.Fatalf("Request failed: %v", err) 89 } 90 defer resp.Body.Close() 91 respbody, err = ioutil.ReadAll(resp.Body) 92 93 if string(respbody) != testmanifest[v] { 94 isexpectedfailrequest := false 95 96 for _, r := range expectedfailrequests { 97 if k[:] == r { 98 isexpectedfailrequest = true 99 } 100 } 101 if !isexpectedfailrequest { 102 t.Fatalf("Response body does not match, expected: %v, got %v", testmanifest[v], string(respbody)) 103 } 104 } 105 } 106 107 for k, v := range testrequests { 108 var resp *http.Response 109 var respbody []byte 110 111 url := srv.URL + "/bzz-hash:/" 112 if k[:] != "" { 113 url += common.ToHex(key[0])[2:] + "/" + k[1:] 114 } 115 resp, err = http.Get(url) 116 if err != nil { 117 t.Fatalf("Request failed: %v", err) 118 } 119 defer resp.Body.Close() 120 respbody, err = ioutil.ReadAll(resp.Body) 121 if err != nil { 122 t.Fatalf("Read request body: %v", err) 123 } 124 125 if string(respbody) != key[v].String() { 126 isexpectedfailrequest := false 127 128 for _, r := range expectedfailrequests { 129 if k[:] == r { 130 isexpectedfailrequest = true 131 } 132 } 133 if !isexpectedfailrequest { 134 t.Fatalf("Response body does not match, expected: %v, got %v", key[v], string(respbody)) 135 } 136 } 137 } 138 139 for _, c := range []struct { 140 path string 141 json string 142 html string 143 }{ 144 { 145 path: "/", 146 json: `{"common_prefixes":["a/"]}`, 147 html: "<!DOCTYPE html>\n<html>\n<head>\n <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <title>Swarm index of bzz:/262e5c08c03c2789b6daef487dfa14b4d132f5340d781a3ecb1d5122ab65640c/</title>\n</head>\n\n<body>\n <h1>Swarm index of bzz:/262e5c08c03c2789b6daef487dfa14b4d132f5340d781a3ecb1d5122ab65640c/</h1>\n <hr>\n <table>\n <thead>\n <tr>\n\t<th>Path</th>\n\t<th>Type</th>\n\t<th>Size</th>\n </tr>\n </thead>\n\n <tbody>\n \n\t<tr>\n\t <td><a href=\"a/\">a/</a></td>\n\t <td>DIR</td>\n\t <td>-</td>\n\t</tr>\n \n\n \n </table>\n <hr>\n</body>\n", 148 }, 149 { 150 path: "/a/", 151 json: `{"common_prefixes":["a/b/"],"entries":[{"hash":"011b4d03dd8c01f1049143cf9c4c817e4b167f1d1b83e5c6f0f10d89ba1e7bce","path":"a/a","mod_time":"0001-01-01T00:00:00Z"}]}`, 152 html: "<!DOCTYPE html>\n<html>\n<head>\n <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <title>Swarm index of bzz:/262e5c08c03c2789b6daef487dfa14b4d132f5340d781a3ecb1d5122ab65640c/a/</title>\n</head>\n\n<body>\n <h1>Swarm index of bzz:/262e5c08c03c2789b6daef487dfa14b4d132f5340d781a3ecb1d5122ab65640c/a/</h1>\n <hr>\n <table>\n <thead>\n <tr>\n\t<th>Path</th>\n\t<th>Type</th>\n\t<th>Size</th>\n </tr>\n </thead>\n\n <tbody>\n \n\t<tr>\n\t <td><a href=\"b/\">b/</a></td>\n\t <td>DIR</td>\n\t <td>-</td>\n\t</tr>\n \n\n \n\t<tr>\n\t <td><a href=\"a\">a</a></td>\n\t <td></td>\n\t <td>0</td>\n\t</tr>\n \n </table>\n <hr>\n</body>\n", 153 }, 154 { 155 path: "/a/b/", 156 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"}]}`, 157 html: "<!DOCTYPE html>\n<html>\n<head>\n <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <title>Swarm index of bzz:/262e5c08c03c2789b6daef487dfa14b4d132f5340d781a3ecb1d5122ab65640c/a/b/</title>\n</head>\n\n<body>\n <h1>Swarm index of bzz:/262e5c08c03c2789b6daef487dfa14b4d132f5340d781a3ecb1d5122ab65640c/a/b/</h1>\n <hr>\n <table>\n <thead>\n <tr>\n\t<th>Path</th>\n\t<th>Type</th>\n\t<th>Size</th>\n </tr>\n </thead>\n\n <tbody>\n \n\n \n\t<tr>\n\t <td><a href=\"b\">b</a></td>\n\t <td></td>\n\t <td>0</td>\n\t</tr>\n \n\t<tr>\n\t <td><a href=\"c\">c</a></td>\n\t <td></td>\n\t <td>0</td>\n\t</tr>\n \n </table>\n <hr>\n</body>\n", 158 }, 159 { 160 path: "/x", 161 }, 162 { 163 path: "", 164 }, 165 } { 166 k := c.path 167 url := srv.URL + "/bzz-list:/" 168 if k[:] != "" { 169 url += common.ToHex(key[0])[2:] + "/" + k[1:] 170 } 171 t.Run("json list "+c.path, func(t *testing.T) { 172 resp, err := http.Get(url) 173 if err != nil { 174 t.Fatalf("HTTP request: %v", err) 175 } 176 defer resp.Body.Close() 177 respbody, err := ioutil.ReadAll(resp.Body) 178 if err != nil { 179 t.Fatalf("Read response body: %v", err) 180 } 181 182 body := strings.TrimSpace(string(respbody)) 183 if body != c.json { 184 isexpectedfailrequest := false 185 186 for _, r := range expectedfailrequests { 187 if k[:] == r { 188 isexpectedfailrequest = true 189 } 190 } 191 if !isexpectedfailrequest { 192 t.Errorf("Response list body %q does not match, expected: %v, got %v", k, c.json, body) 193 } 194 } 195 }) 196 t.Run("html list "+c.path, func(t *testing.T) { 197 req, err := http.NewRequest(http.MethodGet, url, nil) 198 if err != nil { 199 t.Fatalf("New request: %v", err) 200 } 201 req.Header.Set("Accept", "text/html") 202 resp, err := http.DefaultClient.Do(req) 203 if err != nil { 204 t.Fatalf("HTTP request: %v", err) 205 } 206 defer resp.Body.Close() 207 respbody, err := ioutil.ReadAll(resp.Body) 208 if err != nil { 209 t.Fatalf("Read response body: %v", err) 210 } 211 212 if string(respbody) != c.html { 213 isexpectedfailrequest := false 214 215 for _, r := range expectedfailrequests { 216 if k[:] == r { 217 isexpectedfailrequest = true 218 } 219 } 220 if !isexpectedfailrequest { 221 t.Errorf("Response list body %q does not match, expected: %q, got %q", k, c.html, string(respbody)) 222 } 223 } 224 }) 225 } 226 227 nonhashtests := []string{ 228 srv.URL + "/bzz:/name", 229 srv.URL + "/bzz-immutable:/nonhash", 230 srv.URL + "/bzz-raw:/nonhash", 231 srv.URL + "/bzz-list:/nonhash", 232 srv.URL + "/bzz-hash:/nonhash", 233 } 234 235 nonhashresponses := []string{ 236 "error resolving name: no DNS to resolve name: "name"", 237 "error resolving nonhash: immutable address not a content hash: "nonhash"", 238 "error resolving nonhash: no DNS to resolve name: "nonhash"", 239 "error resolving nonhash: no DNS to resolve name: "nonhash"", 240 "error resolving nonhash: no DNS to resolve name: "nonhash"", 241 } 242 243 for i, url := range nonhashtests { 244 var resp *http.Response 245 var respbody []byte 246 247 resp, err = http.Get(url) 248 249 if err != nil { 250 t.Fatalf("Request failed: %v", err) 251 } 252 defer resp.Body.Close() 253 respbody, err = ioutil.ReadAll(resp.Body) 254 if err != nil { 255 t.Fatalf("ReadAll failed: %v", err) 256 } 257 if !strings.Contains(string(respbody), nonhashresponses[i]) { 258 t.Fatalf("Non-Hash response body does not match, expected: %v, got: %v", nonhashresponses[i], string(respbody)) 259 } 260 } 261 262 } 263 264 // TestBzzRootRedirect tests that getting the root path of a manifest without 265 // a trailing slash gets redirected to include the trailing slash so that 266 // relative URLs work as expected. 267 func TestBzzRootRedirect(t *testing.T) { 268 srv := testutil.NewTestSwarmServer(t) 269 defer srv.Close() 270 271 // create a manifest with some data at the root path 272 client := swarm.NewClient(srv.URL) 273 data := []byte("data") 274 file := &swarm.File{ 275 ReadCloser: ioutil.NopCloser(bytes.NewReader(data)), 276 ManifestEntry: api.ManifestEntry{ 277 Path: "", 278 ContentType: "text/plain", 279 Size: int64(len(data)), 280 }, 281 } 282 hash, err := client.Upload(file, "") 283 if err != nil { 284 t.Fatal(err) 285 } 286 287 // define a CheckRedirect hook which ensures there is only a single 288 // redirect to the correct URL 289 redirected := false 290 httpClient := http.Client{ 291 CheckRedirect: func(req *http.Request, via []*http.Request) error { 292 if redirected { 293 return errors.New("too many redirects") 294 } 295 redirected = true 296 expectedPath := "/bzz:/" + hash + "/" 297 if req.URL.Path != expectedPath { 298 return fmt.Errorf("expected redirect to %q, got %q", expectedPath, req.URL.Path) 299 } 300 return nil 301 }, 302 } 303 304 // perform the GET request and assert the response 305 res, err := httpClient.Get(srv.URL + "/bzz:/" + hash) 306 if err != nil { 307 t.Fatal(err) 308 } 309 defer res.Body.Close() 310 if !redirected { 311 t.Fatal("expected GET /bzz:/<hash> to redirect to /bzz:/<hash>/ but it didn't") 312 } 313 gotData, err := ioutil.ReadAll(res.Body) 314 if err != nil { 315 t.Fatal(err) 316 } 317 if !bytes.Equal(gotData, data) { 318 t.Fatalf("expected response to equal %q, got %q", data, gotData) 319 } 320 }