rename.sh (14992B)
1 #!/bin/sh 2 3 # 4 # Regression tests for rename.cgi 5 # 6 7 set -eu 8 9 . "$(dirname "$0")/testlib.sh" 10 11 CGI=${1:-build/rename.cgi} 12 13 ############################################################################## 14 # HTTP helpers 15 ############################################################################## 16 17 rename() { 18 env \ 19 SCRIPT_NAME=/default/rename.cgi \ 20 STORAGE_ROOT="$TESTROOT/storage" \ 21 SIMPLEFILES_ROOT=./html \ 22 REQUEST_METHOD=GET \ 23 QUERY_STRING="${1:-}" \ 24 REMOTE_USER=bob \ 25 "$CGI" 26 } 27 28 rename_user() { 29 user=$1 30 query=${2:-} 31 32 env \ 33 SCRIPT_NAME=/default/rename.cgi \ 34 STORAGE_ROOT="$TESTROOT/storage" \ 35 SIMPLEFILES_ROOT=./html \ 36 REQUEST_METHOD=GET \ 37 QUERY_STRING="$query" \ 38 REMOTE_USER="$user" \ 39 "$CGI" 40 } 41 42 rename_no_user() { 43 query=${1:-} 44 45 ( 46 unset REMOTE_USER 47 48 export SCRIPT_NAME=/default/rename.cgi 49 export STORAGE_ROOT="$TESTROOT/storage" 50 export SIMPLEFILES_ROOT=./html 51 export SIMPLEFILES_CONFIG="$TEST_CONFIG" 52 export REQUEST_METHOD=GET 53 export QUERY_STRING="$query" 54 55 "$CGI" 56 ) 57 } 58 59 rename_post() { 60 body=${1:-} 61 62 printf '%s' "$body" | 63 env \ 64 SCRIPT_NAME=/default/rename.cgi \ 65 STORAGE_ROOT="$TESTROOT/storage" \ 66 REPOSITORY_LOCK_DIR="$TEST_LOCK_DIR" \ 67 SIMPLEFILES_CONFIG="$TEST_CONFIG" \ 68 SIMPLEFILES_ROOT=./html \ 69 HTTP_ORIGIN="$TEST_CSRF_ORIGIN" \ 70 REMOTE_USER=bob \ 71 REQUEST_METHOD=POST \ 72 CONTENT_LENGTH="$(printf '%s' "$body" | wc -c | tr -d ' ')" \ 73 "$CGI" 74 } 75 76 rename_post_user() { 77 user=$1 78 body=${2:-} 79 80 printf '%s' "$body" | 81 env \ 82 SCRIPT_NAME=/default/rename.cgi \ 83 STORAGE_ROOT="$TESTROOT/storage" \ 84 REPOSITORY_LOCK_DIR="$TEST_LOCK_DIR" \ 85 SIMPLEFILES_CONFIG="$TEST_CONFIG" \ 86 SIMPLEFILES_ROOT=./html \ 87 HTTP_ORIGIN="$TEST_CSRF_ORIGIN" \ 88 REMOTE_USER="$user" \ 89 REQUEST_METHOD=POST \ 90 CONTENT_LENGTH="$(printf '%s' "$body" | wc -c | tr -d ' ')" \ 91 "$CGI" 92 } 93 94 rename_post_no_user() { 95 body=${1:-} 96 97 printf '%s' "$body" | 98 env \ 99 SCRIPT_NAME=/default/rename.cgi \ 100 STORAGE_ROOT="$TESTROOT/storage" \ 101 REPOSITORY_LOCK_DIR="$TEST_LOCK_DIR" \ 102 SIMPLEFILES_CONFIG="$TEST_CONFIG" \ 103 SIMPLEFILES_ROOT=./html \ 104 HTTP_ORIGIN="$TEST_CSRF_ORIGIN" \ 105 REQUEST_METHOD=POST \ 106 CONTENT_LENGTH="$(printf '%s' "$body" | wc -c | tr -d ' ')" \ 107 "$CGI" 108 } 109 110 rename_post_origin() { 111 origin=$1 112 body=${2:-} 113 114 printf '%s' "$body" | 115 env \ 116 SCRIPT_NAME=/default/rename.cgi \ 117 STORAGE_ROOT="$TESTROOT/storage" \ 118 REPOSITORY_LOCK_DIR="$TEST_LOCK_DIR" \ 119 SIMPLEFILES_CONFIG="$TEST_CONFIG" \ 120 SIMPLEFILES_ROOT=./html \ 121 HTTP_ORIGIN="$origin" \ 122 REMOTE_USER=bob \ 123 REQUEST_METHOD=POST \ 124 CONTENT_LENGTH="$(printf '%s' "$body" | wc -c | tr -d ' ')" \ 125 "$CGI" 126 } 127 128 rename_post_no_origin() { 129 body=${1:-} 130 131 printf '%s' "$body" | 132 env \ 133 SCRIPT_NAME=/default/rename.cgi \ 134 STORAGE_ROOT="$TESTROOT/storage" \ 135 REPOSITORY_LOCK_DIR="$TEST_LOCK_DIR" \ 136 SIMPLEFILES_CONFIG="$TEST_CONFIG" \ 137 SIMPLEFILES_ROOT=./html \ 138 REMOTE_USER=bob \ 139 REQUEST_METHOD=POST \ 140 CONTENT_LENGTH="$(printf '%s' "$body" | wc -c | tr -d ' ')" \ 141 "$CGI" 142 } 143 144 rename_template_failure() { 145 env \ 146 SCRIPT_NAME=/default/rename.cgi \ 147 STORAGE_ROOT="$TESTROOT/storage" \ 148 SIMPLEFILES_ROOT="$TESTROOT/html" \ 149 SIMPLEFILES_CONFIG="$TEST_CONFIG" \ 150 REQUEST_METHOD=GET \ 151 QUERY_STRING="file=file1.txt" \ 152 REMOTE_USER=bob \ 153 "$CGI" 154 } 155 156 ############################################################################## 157 # Basic functionality 158 ############################################################################## 159 160 reset_testroot 161 162 write_default_repo_config "ADMIN_USER=bob" 163 164 printf 'hello' > "$TESTROOT/storage/default/file1.txt" 165 printf 'second' > "$TESTROOT/storage/default/file2.txt" 166 167 check_status \ 168 "rename confirmation page" \ 169 "200 OK" \ 170 rename \ 171 "file=file1.txt" 172 173 check_body \ 174 "old filename shown" \ 175 "file1.txt" \ 176 rename \ 177 "file=file1.txt" 178 179 check_body \ 180 "rename form" \ 181 '<form method="post"' \ 182 rename \ 183 "file=file1.txt" 184 185 check_body \ 186 "new name field" \ 187 "new_filename" \ 188 rename \ 189 "file=file1.txt" 190 191 check_redirect \ 192 "rename redirects" \ 193 "303 See Other" \ 194 "./" \ 195 rename_post \ 196 "file=file1.txt&new_filename=renamed.txt" 197 198 check_file_exists \ 199 "new filename exists" \ 200 "$TESTROOT/storage/default/renamed.txt" 201 202 check_file_missing \ 203 "old filename removed" \ 204 "$TESTROOT/storage/default/file1.txt" 205 206 check_file \ 207 "contents preserved" \ 208 "$TESTROOT/storage/default/renamed.txt" \ 209 "hello" 210 211 ############################################################################## 212 # Parameter validation 213 ############################################################################## 214 215 check_body \ 216 "missing file parameter" \ 217 "Missing filename" \ 218 rename \ 219 "new_filename=file2.txt" 220 221 check_body \ 222 "empty file parameter" \ 223 "Invalid filename" \ 224 rename \ 225 "file=&new_filename=file2.txt" 226 227 check_body \ 228 "missing new name" \ 229 "Missing new filename" \ 230 rename_post \ 231 "file=file1.txt" 232 233 check_body \ 234 "empty new name" \ 235 "Invalid filename" \ 236 rename_post \ 237 "file=file1.txt&new_filename=" 238 239 check_status \ 240 "reject duplicate file parameter" \ 241 "400 Bad Request" \ 242 rename \ 243 "file=file1.txt&file=file2.txt" 244 245 check_status \ 246 "reject duplicate new_filename parameter" \ 247 "400 Bad Request" \ 248 rename_post \ 249 "file=file1.txt&new_filename=a.txt&new_filename=b.txt" 250 251 ############################################################################## 252 # Filename validation 253 ############################################################################## 254 255 check_body \ 256 "reject source traversal" \ 257 "400 Bad Request" \ 258 rename \ 259 "file=../passwd" 260 261 check_body \ 262 "reject destination traversal" \ 263 "400 Bad Request" \ 264 rename_post \ 265 "file=file2.txt&new_filename=../passwd" 266 267 check_body \ 268 "reject encoded traversal" \ 269 "400 Bad Request" \ 270 rename_post \ 271 "file=file2.txt&new_filename=%2e%2e/passwd" 272 273 check_body \ 274 "reject encoded NUL destination" \ 275 "400 Bad Request" \ 276 rename_post \ 277 "file=file2.txt&new_filename=test%00.txt" 278 279 check_body \ 280 "reject malformed percent encoding" \ 281 "400 Bad Request" \ 282 rename_post \ 283 "file=file2.txt&new_filename=%ZZ" 284 285 ############################################################################## 286 # Rename failures 287 ############################################################################## 288 289 printf 'original' > "$TESTROOT/storage/default/source.txt" 290 printf 'destination' > "$TESTROOT/storage/default/destination.txt" 291 292 check_body \ 293 "reject overwrite" \ 294 "Destination already exists" \ 295 rename_post \ 296 "file=source.txt&new_filename=destination.txt" 297 298 check_file \ 299 "destination unchanged" \ 300 "$TESTROOT/storage/default/destination.txt" \ 301 "destination" 302 303 check_file \ 304 "source survives failed overwrite" \ 305 "$TESTROOT/storage/default/source.txt" \ 306 "original" 307 308 check_body \ 309 "rename missing file" \ 310 "File not found" \ 311 rename_post \ 312 "file=missing.txt&new_filename=new.txt" 313 314 ############################################################################## 315 # Special filenames 316 ############################################################################## 317 318 special="$TESTROOT/storage/default/<test>&file.txt" 319 320 printf 'special\n' > "$special" 321 322 check_redirect \ 323 "rename encoded filename" \ 324 "303 See Other" \ 325 "./" \ 326 rename_post \ 327 "file=%3Ctest%3E%26file.txt&new_filename=special.txt" 328 329 check_file_exists \ 330 "encoded source renamed" \ 331 "$TESTROOT/storage/default/special.txt" 332 333 printf 'space\n' > "$TESTROOT/storage/default/hello world.txt" 334 335 check_redirect \ 336 "rename plus decoded source" \ 337 "303 See Other" \ 338 "./" \ 339 rename_post \ 340 "file=hello+world.txt&new_filename=space.txt" 341 342 check_file_exists \ 343 "space source renamed" \ 344 "$TESTROOT/storage/default/space.txt" 345 346 ############################################################################## 347 # .config protection 348 ############################################################################## 349 350 printf 'QUOTA=1024\n' >> "$TESTROOT/storage/default/.config" 351 352 check_status \ 353 "reject .config source rename" \ 354 "400 Bad Request" \ 355 rename \ 356 "file=.config" 357 358 check_status \ 359 "reject .config destination" \ 360 "400 Bad Request" \ 361 rename_post \ 362 "file=file2.txt&new_filename=.config" 363 364 check_file_exists \ 365 ".config survives" \ 366 "$TESTROOT/storage/default/.config" 367 368 ############################################################################## 369 # CSRF protection 370 ############################################################################## 371 372 reset_testroot 373 374 write_default_repo_config "ADMIN_USER=bob" 375 376 printf 'csrf\n' > "$TESTROOT/storage/default/csrf.txt" 377 378 check_status \ 379 "rename accepts configured Origin" \ 380 "303 See Other" \ 381 rename_post_origin \ 382 "http://localhost:8000" \ 383 "file=csrf.txt&new_filename=renamed.txt" 384 385 check_file_exists \ 386 "csrf rename succeeded" \ 387 "$TESTROOT/storage/default/renamed.txt" 388 389 reset_testroot 390 391 write_default_repo_config "ADMIN_USER=bob" 392 393 printf 'csrf\n' > "$TESTROOT/storage/default/csrf.txt" 394 395 check_status \ 396 "rename rejects missing Origin" \ 397 "403 Forbidden" \ 398 rename_post_no_origin \ 399 "file=csrf.txt&new_filename=renamed.txt" 400 401 check_file_exists \ 402 "missing Origin preserves source" \ 403 "$TESTROOT/storage/default/csrf.txt" 404 405 reset_testroot 406 407 write_default_repo_config "ADMIN_USER=bob" 408 409 printf 'csrf\n' > "$TESTROOT/storage/default/csrf.txt" 410 411 check_status \ 412 "rename rejects wrong Origin" \ 413 "403 Forbidden" \ 414 rename_post_origin \ 415 "https://evil.example" \ 416 "file=csrf.txt&new_filename=renamed.txt" 417 418 check_file_exists \ 419 "wrong Origin preserves source" \ 420 "$TESTROOT/storage/default/csrf.txt" 421 422 ############################################################################## 423 # Other valid renames 424 ############################################################################## 425 426 reset_testroot 427 428 write_default_repo_config "ADMIN_USER=bob" 429 430 printf 'hello' > "$TESTROOT/storage/default/same.txt" 431 432 check_redirect \ 433 "rename to same filename" \ 434 "303 See Other" \ 435 "./" \ 436 rename_post \ 437 "file=same.txt&new_filename=same.txt" 438 439 check_file \ 440 "same filename preserved" \ 441 "$TESTROOT/storage/default/same.txt" \ 442 "hello" 443 444 : > "$TESTROOT/storage/default/empty.txt" 445 446 check_redirect \ 447 "rename empty file" \ 448 "303 See Other" \ 449 "./" \ 450 rename_post \ 451 "file=empty.txt&new_filename=empty-renamed.txt" 452 453 check_file_exists \ 454 "empty file renamed" \ 455 "$TESTROOT/storage/default/empty-renamed.txt" 456 457 reset_testroot 458 459 write_default_repo_config "ADMIN_USER=bob" 460 461 printf 'extension' > "$TESTROOT/storage/default/file.txt" 462 463 check_redirect \ 464 "rename extension" \ 465 "303 See Other" \ 466 "./" \ 467 rename_post \ 468 "file=file.txt&new_filename=file.md" 469 470 check_file_exists \ 471 "renamed extension exists" \ 472 "$TESTROOT/storage/default/file.md" 473 474 check_file_missing \ 475 "old extension removed" \ 476 "$TESTROOT/storage/default/file.txt" 477 478 check_file \ 479 "extension rename preserves contents" \ 480 "$TESTROOT/storage/default/file.md" \ 481 "extension" 482 483 ############################################################################## 484 # Read-only repository 485 ############################################################################## 486 487 reset_testroot 488 489 printf 'readonly' > "$TESTROOT/storage/default/readonly.txt" 490 491 write_default_repo_config "READ_ONLY=1" 492 493 check_status \ 494 "reject rename in readonly repository" \ 495 "403 Forbidden" \ 496 rename_post \ 497 "file=readonly.txt&new_filename=renamed.txt" 498 499 check_file_exists \ 500 "readonly source preserved" \ 501 "$TESTROOT/storage/default/readonly.txt" 502 503 check_file_missing \ 504 "readonly destination not created" \ 505 "$TESTROOT/storage/default/renamed.txt" 506 507 check_body \ 508 "readonly GET rename rejected" \ 509 "read-only" \ 510 rename \ 511 "file=readonly.txt" 512 513 check_file \ 514 "readonly source contents preserved" \ 515 "$TESTROOT/storage/default/readonly.txt" \ 516 "readonly" 517 518 check_status \ 519 "readonly rename rejects missing Origin" \ 520 "403 Forbidden" \ 521 rename_post_no_origin \ 522 "file=readonly.txt&new_filename=new.txt" 523 524 ############################################################################## 525 # Authorization 526 ############################################################################## 527 528 reset_testroot 529 530 printf 'readonly\n' > "$TESTROOT/storage/default/readonly.txt" 531 532 write_default_repo_config "ADMIN_USER=bob" 533 534 check_status \ 535 "rename rejected for non-admin user" \ 536 "403 Forbidden" \ 537 rename_post_user \ 538 "alice" \ 539 "file=readonly.txt&new_filename=renamed.txt" 540 541 check_file_exists \ 542 "non-admin source preserved" \ 543 "$TESTROOT/storage/default/readonly.txt" 544 545 check_file_missing \ 546 "non-admin destination not created" \ 547 "$TESTROOT/storage/default/renamed.txt" 548 549 check_status \ 550 "non-admin rename GET rejected" \ 551 "403 Forbidden" \ 552 rename_user \ 553 "alice" \ 554 "file=readonly.txt" 555 556 reset_testroot 557 558 printf 'anonymous\n' > "$TESTROOT/storage/default/anonymous.txt" 559 560 write_default_repo_config "ADMIN_USER=bob" 561 562 check_status \ 563 "anonymous rename POST rejected" \ 564 "403 Forbidden" \ 565 rename_post_no_user \ 566 "file=anonymous.txt&new_filename=renamed.txt" 567 568 check_file_exists \ 569 "anonymous source preserved" \ 570 "$TESTROOT/storage/default/anonymous.txt" 571 572 check_file_missing \ 573 "anonymous destination not created" \ 574 "$TESTROOT/storage/default/renamed.txt" 575 576 check_status \ 577 "anonymous rename GET rejected" \ 578 "403 Forbidden" \ 579 rename_no_user \ 580 "file=anonymous.txt" 581 582 ############################################################################## 583 # Anonymous read does not grant write access 584 ############################################################################## 585 586 reset_testroot 587 588 write_default_repo_config "ADMIN_USER=bob" 589 590 printf 'hello' > "$TESTROOT/storage/default/file.txt" 591 592 write_global_config "ANONYMOUS_READ=1" 593 594 check_status \ 595 "anonymous rename GET rejected despite anonymous read" \ 596 "403 Forbidden" \ 597 rename_no_user \ 598 "file=file.txt" 599 600 check_status \ 601 "anonymous rename POST rejected despite anonymous read" \ 602 "403 Forbidden" \ 603 rename_post_no_user \ 604 "file=file.txt&new_filename=renamed.txt" 605 606 check_file_exists \ 607 "anonymous rename source preserved" \ 608 "$TESTROOT/storage/default/file.txt" 609 610 check_file_missing \ 611 "anonymous rename destination not created" \ 612 "$TESTROOT/storage/default/renamed.txt" 613 614 ############################################################################## 615 # Authentication alone does not grant write access 616 ############################################################################## 617 618 reset_testroot 619 620 write_default_repo_config "" 621 622 printf 'hello' > "$TESTROOT/storage/default/file.txt" 623 624 check_status \ 625 "authenticated user cannot rename without ADMIN_USER" \ 626 "403 Forbidden" \ 627 rename_post_user \ 628 "alice" \ 629 "file=file.txt&new_filename=renamed.txt" 630 631 check_file_exists \ 632 "source preserved without ADMIN_USER" \ 633 "$TESTROOT/storage/default/file.txt" 634 635 check_file_missing \ 636 "destination not created without ADMIN_USER" \ 637 "$TESTROOT/storage/default/renamed.txt" 638 639 # 640 # Response generation 641 # 642 643 reset_testroot 644 645 write_default_repo_config "ADMIN_USER=bob" 646 647 printf 'hello\n' > "$TESTROOT/storage/default/file1.txt" 648 649 mkdir -p "$TESTROOT/html" 650 651 printf '{{UNKNOWN_VARIABLE}}' > "$TESTROOT/html/rename.html" 652 653 check_status \ 654 "template failure returns 500" \ 655 "500 Internal Server Error" \ 656 rename_template_failure 657 658 check_not_body \ 659 "template failure does not return 200" \ 660 "Status: 200 OK" \ 661 rename_template_failure 662 663 test_result