github.com/xhghs/rclone@v1.51.1-0.20200430155106-e186a28cced8/backend/http/http_internal_test.go (about) 1 package http 2 3 import ( 4 "context" 5 "fmt" 6 "io/ioutil" 7 "net/http" 8 "net/http/httptest" 9 "net/url" 10 "os" 11 "path/filepath" 12 "sort" 13 "strings" 14 "testing" 15 "time" 16 17 "github.com/rclone/rclone/fs" 18 "github.com/rclone/rclone/fs/config" 19 "github.com/rclone/rclone/fs/config/configmap" 20 "github.com/rclone/rclone/fstest" 21 "github.com/rclone/rclone/lib/rest" 22 "github.com/stretchr/testify/assert" 23 "github.com/stretchr/testify/require" 24 ) 25 26 var ( 27 remoteName = "TestHTTP" 28 testPath = "test" 29 filesPath = filepath.Join(testPath, "files") 30 headers = []string{"X-Potato", "sausage", "X-Rhubarb", "cucumber"} 31 ) 32 33 // prepareServer the test server and return a function to tidy it up afterwards 34 func prepareServer(t *testing.T) (configmap.Simple, func()) { 35 // file server for test/files 36 fileServer := http.FileServer(http.Dir(filesPath)) 37 38 // test the headers are there then pass on to fileServer 39 handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 40 what := fmt.Sprintf("%s %s: Header ", r.Method, r.URL.Path) 41 assert.Equal(t, headers[1], r.Header.Get(headers[0]), what+headers[0]) 42 assert.Equal(t, headers[3], r.Header.Get(headers[2]), what+headers[2]) 43 fileServer.ServeHTTP(w, r) 44 }) 45 46 // Make the test server 47 ts := httptest.NewServer(handler) 48 49 // Configure the remote 50 config.LoadConfig() 51 // fs.Config.LogLevel = fs.LogLevelDebug 52 // fs.Config.DumpHeaders = true 53 // fs.Config.DumpBodies = true 54 // config.FileSet(remoteName, "type", "http") 55 // config.FileSet(remoteName, "url", ts.URL) 56 57 m := configmap.Simple{ 58 "type": "http", 59 "url": ts.URL, 60 "headers": strings.Join(headers, ","), 61 } 62 63 // return a function to tidy up 64 return m, ts.Close 65 } 66 67 // prepare the test server and return a function to tidy it up afterwards 68 func prepare(t *testing.T) (fs.Fs, func()) { 69 m, tidy := prepareServer(t) 70 71 // Instantiate it 72 f, err := NewFs(remoteName, "", m) 73 require.NoError(t, err) 74 75 return f, tidy 76 } 77 78 func testListRoot(t *testing.T, f fs.Fs, noSlash bool) { 79 entries, err := f.List(context.Background(), "") 80 require.NoError(t, err) 81 82 sort.Sort(entries) 83 84 require.Equal(t, 4, len(entries)) 85 86 e := entries[0] 87 assert.Equal(t, "four", e.Remote()) 88 assert.Equal(t, int64(-1), e.Size()) 89 _, ok := e.(fs.Directory) 90 assert.True(t, ok) 91 92 e = entries[1] 93 assert.Equal(t, "one%.txt", e.Remote()) 94 assert.Equal(t, int64(6), e.Size()) 95 _, ok = e.(*Object) 96 assert.True(t, ok) 97 98 e = entries[2] 99 assert.Equal(t, "three", e.Remote()) 100 assert.Equal(t, int64(-1), e.Size()) 101 _, ok = e.(fs.Directory) 102 assert.True(t, ok) 103 104 e = entries[3] 105 assert.Equal(t, "two.html", e.Remote()) 106 if noSlash { 107 assert.Equal(t, int64(-1), e.Size()) 108 _, ok = e.(fs.Directory) 109 assert.True(t, ok) 110 } else { 111 assert.Equal(t, int64(41), e.Size()) 112 _, ok = e.(*Object) 113 assert.True(t, ok) 114 } 115 } 116 117 func TestListRoot(t *testing.T) { 118 f, tidy := prepare(t) 119 defer tidy() 120 testListRoot(t, f, false) 121 } 122 123 func TestListRootNoSlash(t *testing.T) { 124 f, tidy := prepare(t) 125 f.(*Fs).opt.NoSlash = true 126 defer tidy() 127 128 testListRoot(t, f, true) 129 } 130 131 func TestListSubDir(t *testing.T) { 132 f, tidy := prepare(t) 133 defer tidy() 134 135 entries, err := f.List(context.Background(), "three") 136 require.NoError(t, err) 137 138 sort.Sort(entries) 139 140 assert.Equal(t, 1, len(entries)) 141 142 e := entries[0] 143 assert.Equal(t, "three/underthree.txt", e.Remote()) 144 assert.Equal(t, int64(9), e.Size()) 145 _, ok := e.(*Object) 146 assert.True(t, ok) 147 } 148 149 func TestNewObject(t *testing.T) { 150 f, tidy := prepare(t) 151 defer tidy() 152 153 o, err := f.NewObject(context.Background(), "four/under four.txt") 154 require.NoError(t, err) 155 156 assert.Equal(t, "four/under four.txt", o.Remote()) 157 assert.Equal(t, int64(9), o.Size()) 158 _, ok := o.(*Object) 159 assert.True(t, ok) 160 161 // Test the time is correct on the object 162 163 tObj := o.ModTime(context.Background()) 164 165 fi, err := os.Stat(filepath.Join(filesPath, "four", "under four.txt")) 166 require.NoError(t, err) 167 tFile := fi.ModTime() 168 169 dt, ok := fstest.CheckTimeEqualWithPrecision(tObj, tFile, time.Second) 170 assert.True(t, ok, fmt.Sprintf("%s: Modification time difference too big |%s| > %s (%s vs %s) (precision %s)", o.Remote(), dt, time.Second, tObj, tFile, time.Second)) 171 172 // check object not found 173 o, err = f.NewObject(context.Background(), "not found.txt") 174 assert.Nil(t, o) 175 assert.Equal(t, fs.ErrorObjectNotFound, err) 176 } 177 178 func TestOpen(t *testing.T) { 179 f, tidy := prepare(t) 180 defer tidy() 181 182 o, err := f.NewObject(context.Background(), "four/under four.txt") 183 require.NoError(t, err) 184 185 // Test normal read 186 fd, err := o.Open(context.Background()) 187 require.NoError(t, err) 188 data, err := ioutil.ReadAll(fd) 189 require.NoError(t, err) 190 require.NoError(t, fd.Close()) 191 assert.Equal(t, "beetroot\n", string(data)) 192 193 // Test with range request 194 fd, err = o.Open(context.Background(), &fs.RangeOption{Start: 1, End: 5}) 195 require.NoError(t, err) 196 data, err = ioutil.ReadAll(fd) 197 require.NoError(t, err) 198 require.NoError(t, fd.Close()) 199 assert.Equal(t, "eetro", string(data)) 200 } 201 202 func TestMimeType(t *testing.T) { 203 f, tidy := prepare(t) 204 defer tidy() 205 206 o, err := f.NewObject(context.Background(), "four/under four.txt") 207 require.NoError(t, err) 208 209 do, ok := o.(fs.MimeTyper) 210 require.True(t, ok) 211 assert.Equal(t, "text/plain; charset=utf-8", do.MimeType(context.Background())) 212 } 213 214 func TestIsAFileRoot(t *testing.T) { 215 m, tidy := prepareServer(t) 216 defer tidy() 217 218 f, err := NewFs(remoteName, "one%.txt", m) 219 assert.Equal(t, err, fs.ErrorIsFile) 220 221 testListRoot(t, f, false) 222 } 223 224 func TestIsAFileSubDir(t *testing.T) { 225 m, tidy := prepareServer(t) 226 defer tidy() 227 228 f, err := NewFs(remoteName, "three/underthree.txt", m) 229 assert.Equal(t, err, fs.ErrorIsFile) 230 231 entries, err := f.List(context.Background(), "") 232 require.NoError(t, err) 233 234 sort.Sort(entries) 235 236 assert.Equal(t, 1, len(entries)) 237 238 e := entries[0] 239 assert.Equal(t, "underthree.txt", e.Remote()) 240 assert.Equal(t, int64(9), e.Size()) 241 _, ok := e.(*Object) 242 assert.True(t, ok) 243 } 244 245 func TestParseName(t *testing.T) { 246 for i, test := range []struct { 247 base string 248 val string 249 wantErr error 250 want string 251 }{ 252 {"http://example.com/", "potato", nil, "potato"}, 253 {"http://example.com/dir/", "potato", nil, "potato"}, 254 {"http://example.com/dir/", "potato?download=true", errFoundQuestionMark, ""}, 255 {"http://example.com/dir/", "../dir/potato", nil, "potato"}, 256 {"http://example.com/dir/", "..", errNotUnderRoot, ""}, 257 {"http://example.com/dir/", "http://example.com/", errNotUnderRoot, ""}, 258 {"http://example.com/dir/", "http://example.com/dir/", errNameIsEmpty, ""}, 259 {"http://example.com/dir/", "http://example.com/dir/potato", nil, "potato"}, 260 {"http://example.com/dir/", "https://example.com/dir/potato", errSchemeMismatch, ""}, 261 {"http://example.com/dir/", "http://notexample.com/dir/potato", errHostMismatch, ""}, 262 {"http://example.com/dir/", "/dir/", errNameIsEmpty, ""}, 263 {"http://example.com/dir/", "/dir/potato", nil, "potato"}, 264 {"http://example.com/dir/", "subdir/potato", errNameContainsSlash, ""}, 265 {"http://example.com/dir/", "With percent %25.txt", nil, "With percent %.txt"}, 266 {"http://example.com/dir/", "With colon :", errURLJoinFailed, ""}, 267 {"http://example.com/dir/", rest.URLPathEscape("With colon :"), nil, "With colon :"}, 268 {"http://example.com/Dungeons%20%26%20Dragons/", "/Dungeons%20&%20Dragons/D%26D%20Basic%20%28Holmes%2C%20B%2C%20X%2C%20BECMI%29/", nil, "D&D Basic (Holmes, B, X, BECMI)/"}, 269 } { 270 u, err := url.Parse(test.base) 271 require.NoError(t, err) 272 got, gotErr := parseName(u, test.val) 273 what := fmt.Sprintf("test %d base=%q, val=%q", i, test.base, test.val) 274 assert.Equal(t, test.wantErr, gotErr, what) 275 assert.Equal(t, test.want, got, what) 276 } 277 } 278 279 // Load HTML from the file given and parse it, checking it against the entries passed in 280 func parseHTML(t *testing.T, name string, base string, want []string) { 281 in, err := os.Open(filepath.Join(testPath, "index_files", name)) 282 require.NoError(t, err) 283 defer func() { 284 require.NoError(t, in.Close()) 285 }() 286 if base == "" { 287 base = "http://example.com/" 288 } 289 u, err := url.Parse(base) 290 require.NoError(t, err) 291 entries, err := parse(u, in) 292 require.NoError(t, err) 293 assert.Equal(t, want, entries) 294 } 295 296 func TestParseEmpty(t *testing.T) { 297 parseHTML(t, "empty.html", "", []string(nil)) 298 } 299 300 func TestParseApache(t *testing.T) { 301 parseHTML(t, "apache.html", "http://example.com/nick/pub/", []string{ 302 "SWIG-embed.tar.gz", 303 "avi2dvd.pl", 304 "cambert.exe", 305 "cambert.gz", 306 "fedora_demo.gz", 307 "gchq-challenge/", 308 "mandelterm/", 309 "pgp-key.txt", 310 "pymath/", 311 "rclone", 312 "readdir.exe", 313 "rush_hour_solver_cut_down.py", 314 "snake-puzzle/", 315 "stressdisk/", 316 "timer-test", 317 "words-to-regexp.pl", 318 "Now 100% better.mp3", 319 "Now better.mp3", 320 }) 321 } 322 323 func TestParseMemstore(t *testing.T) { 324 parseHTML(t, "memstore.html", "", []string{ 325 "test/", 326 "v1.35/", 327 "v1.36-01-g503cd84/", 328 "rclone-beta-latest-freebsd-386.zip", 329 "rclone-beta-latest-freebsd-amd64.zip", 330 "rclone-beta-latest-windows-amd64.zip", 331 }) 332 } 333 334 func TestParseNginx(t *testing.T) { 335 parseHTML(t, "nginx.html", "", []string{ 336 "deltas/", 337 "objects/", 338 "refs/", 339 "state/", 340 "config", 341 "summary", 342 }) 343 } 344 345 func TestParseCaddy(t *testing.T) { 346 parseHTML(t, "caddy.html", "", []string{ 347 "mimetype.zip", 348 "rclone-delete-empty-dirs.py", 349 "rclone-show-empty-dirs.py", 350 "stat-windows-386.zip", 351 "v1.36-155-gcf29ee8b-team-driveβ/", 352 "v1.36-156-gca76b3fb-team-driveβ/", 353 "v1.36-156-ge1f0e0f5-team-driveβ/", 354 "v1.36-22-g06ea13a-ssh-agentβ/", 355 }) 356 }