github.com/divyam234/rclone@v1.64.1/fs/fspath/path_test.go (about) 1 package fspath 2 3 import ( 4 "flag" 5 "fmt" 6 "os" 7 "path/filepath" 8 "runtime" 9 "testing" 10 11 "github.com/divyam234/rclone/fs/config/configmap" 12 "github.com/stretchr/testify/assert" 13 "github.com/stretchr/testify/require" 14 ) 15 16 var ( 17 makeCorpus = flag.Bool("make-corpus", false, "Set to make the fuzzing corpus") 18 ) 19 20 func TestCheckConfigName(t *testing.T) { 21 for _, test := range []struct { 22 in string 23 problem error 24 fixed string 25 }{ 26 {"remote", nil, "remote"}, 27 {"REMOTE", nil, "REMOTE"}, 28 {"", errInvalidCharacters, "_"}, 29 {":remote:", errInvalidCharacters, "_remote_"}, 30 {"remote:", errInvalidCharacters, "remote_"}, 31 {"rem:ote", errInvalidCharacters, "rem_ote"}, 32 {"rem/ote", errInvalidCharacters, "rem_ote"}, 33 {"rem\\ote", errInvalidCharacters, "rem_ote"}, 34 {"[remote", errInvalidCharacters, "_remote"}, 35 {"*", errInvalidCharacters, "_"}, 36 {"-remote", errInvalidCharacters, "_remote"}, 37 {"r-emote-", nil, "r-emote-"}, 38 {"---rem:::ote???", errInvalidCharacters, "_rem_ote_"}, 39 {"_rem_ote_", nil, "_rem_ote_"}, 40 {".", nil, "."}, 41 {"..", nil, ".."}, 42 {".r.e.m.o.t.e.", nil, ".r.e.m.o.t.e."}, 43 {"rem ote", nil, "rem ote"}, 44 {"user@example.com", nil, "user@example.com"}, 45 {"user+junkmail@example.com", nil, "user+junkmail@example.com"}, 46 {"blåbær", nil, "blåbær"}, 47 {"chữ Quốc ngữ", nil, "chữ Quốc ngữ"}, 48 {"remote ", errInvalidCharacters, "remote_"}, 49 {" remote", errInvalidCharacters, "_remote"}, 50 {" remote ", errInvalidCharacters, "_remote_"}, 51 } { 52 problem := CheckConfigName(test.in) 53 assert.Equal(t, test.problem, problem, test.in) 54 fixed := MakeConfigName(test.in) 55 assert.Equal(t, test.fixed, fixed, test.in) 56 } 57 } 58 59 func TestCheckRemoteName(t *testing.T) { 60 for _, test := range []struct { 61 in string 62 want error 63 }{ 64 {":remote:", nil}, 65 {":REMOTE:", nil}, 66 {":s3:", nil}, 67 {"remote:", nil}, 68 {".:", nil}, 69 {"..:", nil}, 70 {".r.e.m.o.t.e.:", nil}, 71 {"-r-emote-:", errInvalidCharacters}, 72 {"rem ote:", nil}, 73 {"user@example.com:", nil}, 74 {"user+junkmail@example.com:", nil}, 75 {"blåbær:", nil}, 76 {"chữ Quốc ngữ:", nil}, 77 {"remote :", errInvalidCharacters}, 78 {" remote:", errInvalidCharacters}, 79 {" remote :", errInvalidCharacters}, 80 {"", errInvalidCharacters}, 81 {"rem:ote", errInvalidCharacters}, 82 {"rem:ote:", errInvalidCharacters}, 83 {"remote", errInvalidCharacters}, 84 {"rem/ote:", errInvalidCharacters}, 85 {"rem\\ote:", errInvalidCharacters}, 86 {"[remote:", errInvalidCharacters}, 87 {"*:", errInvalidCharacters}, 88 } { 89 got := checkRemoteName(test.in) 90 assert.Equal(t, test.want, got, test.in) 91 } 92 } 93 94 func TestParse(t *testing.T) { 95 for testNumber, test := range []struct { 96 in string 97 wantParsed Parsed 98 wantErr error 99 win bool // only run these tests on Windows 100 noWin bool // only run these tests on !Windows 101 }{ 102 { 103 in: "", 104 wantErr: errCantBeEmpty, 105 }, { 106 in: ":", 107 wantErr: errConfigName, 108 }, { 109 in: "::", 110 wantErr: errConfigNameEmpty, 111 }, { 112 in: ":/:", 113 wantErr: errInvalidCharacters, 114 }, { 115 in: "/:", 116 wantParsed: Parsed{ 117 ConfigString: "", 118 Path: "/:", 119 }, 120 }, { 121 in: "\\backslash:", 122 wantParsed: Parsed{ 123 ConfigString: "", 124 Path: "/backslash:", 125 }, 126 win: true, 127 }, { 128 in: "\\backslash:", 129 wantParsed: Parsed{ 130 ConfigString: "", 131 Path: "\\backslash:", 132 }, 133 noWin: true, 134 }, { 135 in: "/slash:", 136 wantParsed: Parsed{ 137 ConfigString: "", 138 Path: "/slash:", 139 }, 140 }, { 141 in: "with\\backslash:", 142 wantParsed: Parsed{ 143 ConfigString: "", 144 Path: "with/backslash:", 145 }, 146 win: true, 147 }, { 148 in: "with\\backslash:", 149 wantParsed: Parsed{ 150 ConfigString: "", 151 Path: "with\\backslash:", 152 }, 153 noWin: true, 154 }, { 155 in: "with/slash:", 156 wantParsed: Parsed{ 157 ConfigString: "", 158 Path: "with/slash:", 159 }, 160 }, { 161 in: "/path/to/file", 162 wantParsed: Parsed{ 163 ConfigString: "", 164 Path: "/path/to/file", 165 }, 166 }, { 167 in: "/path:/to/file", 168 wantParsed: Parsed{ 169 ConfigString: "", 170 Path: "/path:/to/file", 171 }, 172 }, { 173 in: "./path:/to/file", 174 wantParsed: Parsed{ 175 ConfigString: "", 176 Path: "./path:/to/file", 177 }, 178 }, { 179 in: "./:colon.txt", 180 wantParsed: Parsed{ 181 ConfigString: "", 182 Path: "./:colon.txt", 183 }, 184 }, { 185 in: "path/to/file", 186 wantParsed: Parsed{ 187 ConfigString: "", 188 Path: "path/to/file", 189 }, 190 }, { 191 in: ".:", 192 wantParsed: Parsed{ 193 ConfigString: ".", 194 Name: ".", 195 Path: "", 196 }, 197 }, { 198 in: "..:", 199 wantParsed: Parsed{ 200 ConfigString: "..", 201 Name: "..", 202 Path: "", 203 }, 204 }, { 205 in: ".:colon.txt", 206 wantParsed: Parsed{ 207 ConfigString: ".", 208 Name: ".", 209 Path: "colon.txt", 210 }, 211 }, { 212 in: "remote:path/to/file", 213 wantParsed: Parsed{ 214 ConfigString: "remote", 215 Name: "remote", 216 Path: "path/to/file", 217 }, 218 }, { 219 in: "rem*ote:path/to/file", 220 wantErr: errInvalidCharacters, 221 }, { 222 in: "remote:/path/to/file", 223 wantParsed: Parsed{ 224 ConfigString: "remote", 225 Name: "remote", 226 Path: "/path/to/file", 227 }, 228 }, { 229 in: "rem.ote:/path/to/file", 230 wantParsed: Parsed{ 231 ConfigString: "rem.ote", 232 Name: "rem.ote", 233 Path: "/path/to/file", 234 }, 235 }, { 236 in: "rem ote:/path/to/file", 237 wantParsed: Parsed{ 238 ConfigString: "rem ote", 239 Name: "rem ote", 240 Path: "/path/to/file", 241 }, 242 }, { 243 in: "remote :/path/to/file", 244 wantErr: errInvalidCharacters, 245 }, { 246 in: " remote:/path/to/file", 247 wantErr: errInvalidCharacters, 248 }, { 249 in: " remote :/path/to/file", 250 wantErr: errInvalidCharacters, 251 }, { 252 in: "rem#ote:/path/to/file", 253 wantErr: errInvalidCharacters, 254 }, { 255 in: ":backend:/path/to/file", 256 wantParsed: Parsed{ 257 ConfigString: ":backend", 258 Name: ":backend", 259 Path: "/path/to/file", 260 }, 261 }, { 262 in: ":back.end:/path/to/file", 263 wantParsed: Parsed{ 264 ConfigString: ":back.end", 265 Name: ":back.end", 266 Path: "/path/to/file", 267 }, 268 }, { 269 in: ":bac*kend:/path/to/file", 270 wantErr: errInvalidCharacters, 271 }, { 272 in: `C:\path\to\file`, 273 wantParsed: Parsed{ 274 Name: "", 275 Path: `C:/path/to/file`, 276 }, 277 win: true, 278 }, { 279 in: `C:\path\to\file`, 280 wantParsed: Parsed{ 281 Name: "C", 282 ConfigString: "C", 283 Path: `\path\to\file`, 284 }, 285 noWin: true, 286 }, { 287 in: `\path\to\file`, 288 wantParsed: Parsed{ 289 Name: "", 290 Path: `/path/to/file`, 291 }, 292 win: true, 293 }, { 294 in: `\path\to\file`, 295 wantParsed: Parsed{ 296 Name: "", 297 Path: `\path\to\file`, 298 }, 299 noWin: true, 300 }, { 301 in: `.`, 302 wantParsed: Parsed{ 303 Name: "", 304 Path: `.`, 305 }, 306 noWin: true, 307 }, { 308 in: `..`, 309 wantParsed: Parsed{ 310 Name: "", 311 Path: `..`, 312 }, 313 noWin: true, 314 }, { 315 in: `remote:\path\to\file`, 316 wantParsed: Parsed{ 317 Name: "remote", 318 ConfigString: "remote", 319 Path: `/path/to/file`, 320 }, 321 win: true, 322 }, { 323 in: `remote:\path\to\file`, 324 wantParsed: Parsed{ 325 Name: "remote", 326 ConfigString: "remote", 327 Path: `\path\to\file`, 328 }, 329 noWin: true, 330 }, { 331 in: `D:/path/to/file`, 332 wantParsed: Parsed{ 333 Name: "", 334 Path: `D:/path/to/file`, 335 }, 336 win: true, 337 }, { 338 in: `D:/path/to/file`, 339 wantParsed: Parsed{ 340 Name: "D", 341 ConfigString: "D", 342 Path: `/path/to/file`, 343 }, 344 noWin: true, 345 }, { 346 in: `:backend,param1:/path/to/file`, 347 wantParsed: Parsed{ 348 ConfigString: `:backend,param1`, 349 Name: ":backend", 350 Path: "/path/to/file", 351 Config: configmap.Simple{ 352 "param1": "true", 353 }, 354 }, 355 }, { 356 in: `:backend,param1=value:/path/to/file`, 357 wantParsed: Parsed{ 358 ConfigString: `:backend,param1=value`, 359 Name: ":backend", 360 Path: "/path/to/file", 361 Config: configmap.Simple{ 362 "param1": "value", 363 }, 364 }, 365 }, { 366 in: `:backend,param1=value1,param2,param3=value3:/path/to/file`, 367 wantParsed: Parsed{ 368 ConfigString: `:backend,param1=value1,param2,param3=value3`, 369 Name: ":backend", 370 Path: "/path/to/file", 371 Config: configmap.Simple{ 372 "param1": "value1", 373 "param2": "true", 374 "param3": "value3", 375 }, 376 }, 377 }, { 378 in: `:backend,param1=value1,param2="value2",param3='value3':/path/to/file`, 379 wantParsed: Parsed{ 380 ConfigString: `:backend,param1=value1,param2="value2",param3='value3'`, 381 Name: ":backend", 382 Path: "/path/to/file", 383 Config: configmap.Simple{ 384 "param1": "value1", 385 "param2": "value2", 386 "param3": "value3", 387 }, 388 }, 389 }, { 390 in: `:backend,param-1=value:/path/to/file`, 391 wantErr: errBadConfigParam, 392 }, { 393 in: `:backend,param1="value"x:/path/to/file`, 394 wantErr: errAfterQuote, 395 }, { 396 in: `:backend,`, 397 wantErr: errParam, 398 }, { 399 in: `:backend,param=value`, 400 wantErr: errValue, 401 }, { 402 in: `:backend,param="value'`, 403 wantErr: errQuotedValue, 404 }, { 405 in: `:backend,param1="value"`, 406 wantErr: errAfterQuote, 407 }, { 408 in: `:backend,=value:`, 409 wantErr: errEmptyConfigParam, 410 }, { 411 in: `:backend,:`, 412 wantErr: errEmptyConfigParam, 413 }, { 414 in: `:backend,,:`, 415 wantErr: errEmptyConfigParam, 416 }, { 417 in: `:backend,param=:path`, 418 wantParsed: Parsed{ 419 ConfigString: `:backend,param=`, 420 Name: ":backend", 421 Path: "path", 422 Config: configmap.Simple{ 423 "param": "", 424 }, 425 }, 426 }, { 427 in: `:backend,param="with""quote":path`, 428 wantParsed: Parsed{ 429 ConfigString: `:backend,param="with""quote"`, 430 Name: ":backend", 431 Path: "path", 432 Config: configmap.Simple{ 433 "param": `with"quote`, 434 }, 435 }, 436 }, { 437 in: `:backend,param='''''':`, 438 wantParsed: Parsed{ 439 ConfigString: `:backend,param=''''''`, 440 Name: ":backend", 441 Path: "", 442 Config: configmap.Simple{ 443 "param": `''`, 444 }, 445 }, 446 }, { 447 in: `:backend,param=''bad'':`, 448 wantErr: errAfterQuote, 449 }, 450 } { 451 gotParsed, gotErr := Parse(test.in) 452 if runtime.GOOS == "windows" && test.noWin { 453 continue 454 } 455 if runtime.GOOS != "windows" && test.win { 456 continue 457 } 458 assert.Equal(t, test.wantErr, gotErr, test.in) 459 if test.wantErr == nil { 460 assert.Equal(t, test.wantParsed, gotParsed, test.in) 461 } 462 if *makeCorpus { 463 // write the test corpus for fuzzing 464 require.NoError(t, os.MkdirAll("corpus", 0777)) 465 require.NoError(t, os.WriteFile(fmt.Sprintf("corpus/%02d", testNumber), []byte(test.in), 0666)) 466 } 467 468 } 469 } 470 471 func TestSplitFs(t *testing.T) { 472 for _, test := range []struct { 473 remote, wantRemoteName, wantRemotePath string 474 wantErr error 475 }{ 476 {"", "", "", errCantBeEmpty}, 477 478 {"remote:", "remote:", "", nil}, 479 {"remote:potato", "remote:", "potato", nil}, 480 {"remote:/", "remote:", "/", nil}, 481 {"remote:/potato", "remote:", "/potato", nil}, 482 {"remote:/potato/potato", "remote:", "/potato/potato", nil}, 483 {"remote:potato/sausage", "remote:", "potato/sausage", nil}, 484 {"rem.ote:potato/sausage", "rem.ote:", "potato/sausage", nil}, 485 486 {"rem ote:", "rem ote:", "", nil}, 487 {"remote :", "", "", errInvalidCharacters}, 488 {" remote:", "", "", errInvalidCharacters}, 489 {" remote :", "", "", errInvalidCharacters}, 490 491 {".:", ".:", "", nil}, 492 {"..:", "..:", "", nil}, 493 {".:potato/sausage", ".:", "potato/sausage", nil}, 494 {"..:potato/sausage", "..:", "potato/sausage", nil}, 495 496 {":remote:", ":remote:", "", nil}, 497 {":remote:potato", ":remote:", "potato", nil}, 498 {":remote:/", ":remote:", "/", nil}, 499 {":remote:/potato", ":remote:", "/potato", nil}, 500 {":remote:/potato/potato", ":remote:", "/potato/potato", nil}, 501 {":remote:potato/sausage", ":remote:", "potato/sausage", nil}, 502 {":rem.ote:potato/sausage", ":rem.ote:", "potato/sausage", nil}, 503 {":rem[ote:potato/sausage", "", "", errInvalidCharacters}, 504 505 {":.:", ":.:", "", nil}, 506 {":..:", ":..:", "", nil}, 507 {":.:potato/sausage", ":.:", "potato/sausage", nil}, 508 {":..:potato/sausage", ":..:", "potato/sausage", nil}, 509 510 {"/", "", "/", nil}, 511 {"/root", "", "/root", nil}, 512 {"/a/b", "", "/a/b", nil}, 513 {"root", "", "root", nil}, 514 {"a/b", "", "a/b", nil}, 515 {"root/", "", "root/", nil}, 516 {"a/b/", "", "a/b/", nil}, 517 } { 518 gotRemoteName, gotRemotePath, gotErr := SplitFs(test.remote) 519 assert.Equal(t, test.wantErr, gotErr) 520 assert.Equal(t, test.wantRemoteName, gotRemoteName, test.remote) 521 assert.Equal(t, test.wantRemotePath, gotRemotePath, test.remote) 522 if gotErr == nil { 523 assert.Equal(t, test.remote, gotRemoteName+gotRemotePath, fmt.Sprintf("%s: %q + %q != %q", test.remote, gotRemoteName, gotRemotePath, test.remote)) 524 } 525 } 526 } 527 528 func TestSplit(t *testing.T) { 529 for _, test := range []struct { 530 remote, wantParent, wantLeaf string 531 wantErr error 532 }{ 533 {"", "", "", errCantBeEmpty}, 534 535 {"remote:", "remote:", "", nil}, 536 {"remote:potato", "remote:", "potato", nil}, 537 {"remote:/", "remote:/", "", nil}, 538 {"remote:/potato", "remote:/", "potato", nil}, 539 {"remote:/potato/potato", "remote:/potato/", "potato", nil}, 540 {"remote:potato/sausage", "remote:potato/", "sausage", nil}, 541 {"rem.ote:potato/sausage", "rem.ote:potato/", "sausage", nil}, 542 543 {"rem ote:", "rem ote:", "", nil}, 544 {"remote :", "", "", errInvalidCharacters}, 545 {" remote:", "", "", errInvalidCharacters}, 546 {" remote :", "", "", errInvalidCharacters}, 547 548 {".:", ".:", "", nil}, 549 {"..:", "..:", "", nil}, 550 {".:potato/sausage", ".:potato/", "sausage", nil}, 551 {"..:potato/sausage", "..:potato/", "sausage", nil}, 552 553 {":remote:", ":remote:", "", nil}, 554 {":remote:potato", ":remote:", "potato", nil}, 555 {":remote:/", ":remote:/", "", nil}, 556 {":remote:/potato", ":remote:/", "potato", nil}, 557 {":remote:/potato/potato", ":remote:/potato/", "potato", nil}, 558 {":remote:potato/sausage", ":remote:potato/", "sausage", nil}, 559 {":rem.ote:potato/sausage", ":rem.ote:potato/", "sausage", nil}, 560 {":rem[ote:potato/sausage", "", "", errInvalidCharacters}, 561 562 {":.:", ":.:", "", nil}, 563 {":..:", ":..:", "", nil}, 564 {":.:potato/sausage", ":.:potato/", "sausage", nil}, 565 {":..:potato/sausage", ":..:potato/", "sausage", nil}, 566 567 {"/", "/", "", nil}, 568 {"/root", "/", "root", nil}, 569 {"/a/b", "/a/", "b", nil}, 570 {"root", "", "root", nil}, 571 {"a/b", "a/", "b", nil}, 572 {"root/", "root/", "", nil}, 573 {"a/b/", "a/b/", "", nil}, 574 } { 575 gotParent, gotLeaf, gotErr := Split(test.remote) 576 assert.Equal(t, test.wantErr, gotErr) 577 assert.Equal(t, test.wantParent, gotParent, test.remote) 578 assert.Equal(t, test.wantLeaf, gotLeaf, test.remote) 579 if gotErr == nil { 580 assert.Equal(t, test.remote, gotParent+gotLeaf, fmt.Sprintf("%s: %q + %q != %q", test.remote, gotParent, gotLeaf, test.remote)) 581 } 582 } 583 } 584 585 func TestMakeAbsolute(t *testing.T) { 586 for _, test := range []struct { 587 in string 588 want string 589 }{ 590 {"", ""}, 591 {".", ""}, 592 {"/.", "/"}, 593 {"../potato", "potato"}, 594 {"/../potato", "/potato"}, 595 {"./../potato", "potato"}, 596 {"//../potato", "/potato"}, 597 {"././../potato", "potato"}, 598 {"././potato/../../onion", "onion"}, 599 } { 600 got := makeAbsolute(test.in) 601 assert.Equal(t, test.want, got, test) 602 } 603 } 604 605 func TestJoinRootPath(t *testing.T) { 606 for _, test := range []struct { 607 remote string 608 filePath string 609 want string 610 }{ 611 {"", "", ""}, 612 {"", "/", "/"}, 613 {"/", "", "/"}, 614 {"/", "/", "/"}, 615 {"/", "//", "/"}, 616 {"/root", "", "/root"}, 617 {"/root", "/", "/root"}, 618 {"/root", "//", "/root"}, 619 {"/a/b", "", "/a/b"}, 620 {"//", "/", "//"}, 621 {"//server", "path", "//server/path"}, 622 {"//server/sub", "path", "//server/sub/path"}, 623 {"//server", "//path", "//server/path"}, 624 {"//server/sub", "//path", "//server/sub/path"}, 625 {"//", "/", "//"}, 626 {"//server", "path", "//server/path"}, 627 {"//server/sub", "path", "//server/sub/path"}, 628 {"//server", "//path", "//server/path"}, 629 {"//server/sub", "//path", "//server/sub/path"}, 630 {filepath.FromSlash("//server/sub"), filepath.FromSlash("//path"), "//server/sub/path"}, 631 {"s3:", "", "s3:"}, 632 {"s3:", ".", "s3:"}, 633 {"s3:.", ".", "s3:"}, 634 {"s3:", "..", "s3:"}, 635 {"s3:dir", "sub", "s3:dir/sub"}, 636 {"s3:dir", "/sub", "s3:dir/sub"}, 637 {"s3:dir", "./sub", "s3:dir/sub"}, 638 {"s3:/dir", "/sub/", "s3:/dir/sub"}, 639 {"s3:dir", "..", "s3:dir"}, 640 {"s3:dir", "/..", "s3:dir"}, 641 {"s3:dir", "/../", "s3:dir"}, 642 } { 643 got := JoinRootPath(test.remote, test.filePath) 644 assert.Equal(t, test.want, got, test) 645 } 646 }