upload.sh (31942B)
1 #!/bin/sh 2 3 set -eu 4 5 . "$(dirname "$0")/testlib.sh" 6 7 CGI=${1:-build/upload.cgi} 8 9 upload_get() { 10 env \ 11 STORAGE_ROOT="$TESTROOT/storage" \ 12 SIMPLEFILES_ROOT=./html \ 13 SIMPLEFILES_CONFIG="$TEST_CONFIG" \ 14 REMOTE_USER=bob \ 15 REQUEST_METHOD=GET \ 16 "$CGI" 17 } 18 19 upload_put() { 20 env \ 21 STORAGE_ROOT="$TESTROOT/storage" \ 22 SIMPLEFILES_CONFIG="$TEST_CONFIG" \ 23 REMOTE_USER=bob \ 24 REQUEST_METHOD=PUT \ 25 "$CGI" 26 } 27 28 upload_post() { 29 content_type=$1 30 content_length=$2 31 32 env \ 33 STORAGE_ROOT="$TESTROOT/storage" \ 34 UPLOAD_TMP_DIR="$TEST_UPLOAD_DIR" \ 35 REPOSITORY_LOCK_DIR="$TEST_LOCK_DIR" \ 36 SIMPLEFILES_CONFIG="$TEST_CONFIG" \ 37 HTTP_ORIGIN="$TEST_CSRF_ORIGIN" \ 38 REMOTE_USER=bob \ 39 REQUEST_METHOD=POST \ 40 CONTENT_TYPE="$content_type" \ 41 CONTENT_LENGTH="$content_length" \ 42 "$CGI" 43 } 44 45 upload_post_no_length() { 46 content_type=$1 47 48 env \ 49 STORAGE_ROOT="$TESTROOT/storage" \ 50 UPLOAD_TMP_DIR="$TEST_UPLOAD_DIR" \ 51 REPOSITORY_LOCK_DIR="$TEST_LOCK_DIR" \ 52 SIMPLEFILES_CONFIG="$TEST_CONFIG" \ 53 HTTP_ORIGIN="$TEST_CSRF_ORIGIN" \ 54 REMOTE_USER=bob \ 55 REQUEST_METHOD=POST \ 56 CONTENT_TYPE="$content_type" \ 57 "$CGI" 58 } 59 60 upload() { 61 body=$( 62 printf '%s\r\n' '--test' 63 printf '%s\r\n' \ 64 'Content-Disposition: form-data; name="file"; filename="hello world.mp4"' 65 printf '%s\r\n' 'Content-Type: video/mp4' 66 printf '\r\n' 67 printf '%s' 'dummy data' 68 printf '\r\n--test--\r\n' 69 ) 70 71 printf '%s' "$body" | 72 env \ 73 STORAGE_ROOT="$TESTROOT/storage" \ 74 UPLOAD_TMP_DIR="$TEST_UPLOAD_DIR" \ 75 REPOSITORY_LOCK_DIR="$TEST_LOCK_DIR" \ 76 SIMPLEFILES_CONFIG="$TEST_CONFIG" \ 77 HTTP_ORIGIN="$TEST_CSRF_ORIGIN" \ 78 REMOTE_USER=bob \ 79 REQUEST_METHOD=POST \ 80 CONTENT_TYPE="multipart/form-data; boundary=test" \ 81 CONTENT_LENGTH="$(printf '%s' "$body" | wc -c | tr -d ' ')" \ 82 "$CGI" 83 } 84 85 upload_boundary_in_file() { 86 body=$( 87 printf '%s\r\n' '--test' 88 printf '%s\r\n' \ 89 'Content-Disposition: form-data; name="file"; filename="boundary.txt"' 90 printf '%s\r\n' 'Content-Type: text/plain' 91 printf '\r\n' 92 printf '%s' 'before 93 --test-not-a-boundary 94 after' 95 printf '\r\n--test--\r\n' 96 ) 97 98 printf '%s' "$body" | 99 env \ 100 STORAGE_ROOT="$TESTROOT/storage" \ 101 UPLOAD_TMP_DIR="$TEST_UPLOAD_DIR" \ 102 REPOSITORY_LOCK_DIR="$TEST_LOCK_DIR" \ 103 SIMPLEFILES_CONFIG="$TEST_CONFIG" \ 104 HTTP_ORIGIN="$TEST_CSRF_ORIGIN" \ 105 REMOTE_USER=bob \ 106 REQUEST_METHOD=POST \ 107 CONTENT_TYPE="multipart/form-data; boundary=test" \ 108 CONTENT_LENGTH="$(printf '%s' "$body" | wc -c | tr -d ' ')" \ 109 "$CGI" 110 } 111 112 upload_boundary_split() { 113 tmp=$(mktemp) 114 115 { 116 printf '%s\r\n' '--test' 117 printf '%s\r\n' \ 118 'Content-Disposition: form-data; name="file"; filename="split.txt"' 119 printf '%s\r\n' 'Content-Type: text/plain' 120 printf '\r\n' 121 122 awk 'BEGIN { 123 for (i = 0; i < 8192; i++) 124 printf "A" 125 }' 126 127 printf '\r\n--test--\r\n' 128 } > "$tmp" 129 130 env \ 131 STORAGE_ROOT="$TESTROOT/storage" \ 132 UPLOAD_TMP_DIR="$TEST_UPLOAD_DIR" \ 133 REPOSITORY_LOCK_DIR="$TEST_LOCK_DIR" \ 134 SIMPLEFILES_CONFIG="$TEST_CONFIG" \ 135 HTTP_ORIGIN="$TEST_CSRF_ORIGIN" \ 136 REMOTE_USER=bob \ 137 REQUEST_METHOD=POST \ 138 CONTENT_TYPE="multipart/form-data; boundary=test" \ 139 CONTENT_LENGTH="$(wc -c < "$tmp" | tr -d ' ')" \ 140 "$CGI" < "$tmp" 141 142 rm -f "$tmp" 143 } 144 145 upload_quota_ok() { 146 tmp=$(mktemp) 147 148 { 149 printf '%s\r\n' '--test' 150 printf '%s\r\n' \ 151 'Content-Disposition: form-data; name="file"; filename="quota_ok.txt"' 152 printf '%s\r\n' 'Content-Type: application/octet-stream' 153 printf '\r\n' 154 awk 'BEGIN { for (i = 0; i < 100; i++) printf "A" }' 155 printf '\r\n--test--\r\n' 156 } > "$tmp" 157 158 env \ 159 REPO_QUOTA_BYTES=10240 \ 160 STORAGE_ROOT="$TESTROOT/storage" \ 161 UPLOAD_TMP_DIR="$TEST_UPLOAD_DIR" \ 162 REPOSITORY_LOCK_DIR="$TEST_LOCK_DIR" \ 163 SIMPLEFILES_CONFIG="$TEST_CONFIG" \ 164 HTTP_ORIGIN="$TEST_CSRF_ORIGIN" \ 165 REMOTE_USER=bob \ 166 REQUEST_METHOD=POST \ 167 CONTENT_TYPE="multipart/form-data; boundary=test" \ 168 CONTENT_LENGTH="$(wc -c < "$tmp" | tr -d ' ')" \ 169 "$CGI" < "$tmp" 170 171 rm -f "$tmp" 172 } 173 174 upload_quota_exceeds() { 175 tmp=$(mktemp) 176 177 { 178 printf '%s\r\n' '--test' 179 printf '%s\r\n' \ 180 'Content-Disposition: form-data; name="file"; filename="toobig.txt"' 181 printf '%s\r\n' 'Content-Type: application/octet-stream' 182 printf '\r\n' 183 dd if=/dev/zero bs=1 count=2000 2> /dev/null 184 printf '\r\n--test--\r\n' 185 } > "$tmp" 186 187 env \ 188 REPO_QUOTA_BYTES=10240 \ 189 MIN_FREE_BYTES=1024 \ 190 STORAGE_ROOT="$TESTROOT/storage" \ 191 UPLOAD_TMP_DIR="$TEST_UPLOAD_DIR" \ 192 REPOSITORY_LOCK_DIR="$TEST_LOCK_DIR" \ 193 SIMPLEFILES_CONFIG="$TEST_CONFIG" \ 194 HTTP_ORIGIN="$TEST_CSRF_ORIGIN" \ 195 REMOTE_USER=bob \ 196 REQUEST_METHOD=POST \ 197 CONTENT_TYPE="multipart/form-data; boundary=test" \ 198 CONTENT_LENGTH="$(wc -c < "$tmp" | tr -d ' ')" \ 199 "$CGI" < "$tmp" 200 201 rm -f "$tmp" 202 } 203 204 upload_exact_quota() { 205 quota=$1 206 tmp=$(mktemp) 207 208 { 209 printf '%s\r\n' '--test' 210 printf '%s\r\n' \ 211 'Content-Disposition: form-data; name="file"; filename="exact.txt"' 212 printf '%s\r\n' 'Content-Type: application/octet-stream' 213 printf '\r\n' 214 dd if=/dev/zero bs=1 count=1024 2> /dev/null 215 printf '\r\n--test--\r\n' 216 } > "$tmp" 217 218 env \ 219 REPO_QUOTA_BYTES="$quota" \ 220 STORAGE_ROOT="$TESTROOT/storage" \ 221 UPLOAD_TMP_DIR="$TEST_UPLOAD_DIR" \ 222 REPOSITORY_LOCK_DIR="$TEST_LOCK_DIR" \ 223 SIMPLEFILES_CONFIG="$TEST_CONFIG" \ 224 HTTP_ORIGIN="$TEST_CSRF_ORIGIN" \ 225 REMOTE_USER=bob \ 226 REQUEST_METHOD=POST \ 227 CONTENT_TYPE="multipart/form-data; boundary=test" \ 228 CONTENT_LENGTH="$(wc -c < "$tmp" | tr -d ' ')" \ 229 "$CGI" < "$tmp" 230 231 rm -f "$tmp" 232 } 233 234 upload_filename() { 235 filename=$1 236 237 body=$( 238 printf '%s\r\n' '--test' 239 printf '%s\r\n' \ 240 "Content-Disposition: form-data; name=\"file\"; filename=\"$filename\"" 241 printf '%s\r\n' 'Content-Type: video/mp4' 242 printf '\r\n' 243 printf '%s' 'dummy data' 244 printf '\r\n--test--\r\n' 245 ) 246 247 printf '%s' "$body" | 248 env \ 249 STORAGE_ROOT="$TESTROOT/storage" \ 250 UPLOAD_TMP_DIR="$TEST_UPLOAD_DIR" \ 251 REPOSITORY_LOCK_DIR="$TEST_LOCK_DIR" \ 252 SIMPLEFILES_CONFIG="$TEST_CONFIG" \ 253 HTTP_ORIGIN="$TEST_CSRF_ORIGIN" \ 254 REMOTE_USER=bob \ 255 REQUEST_METHOD=POST \ 256 CONTENT_TYPE="multipart/form-data; boundary=test" \ 257 CONTENT_LENGTH="$(printf '%s' "$body" | wc -c | tr -d ' ')" \ 258 "$CGI" 259 } 260 261 upload_truncated() { 262 body=$( 263 printf '%s\r\n' '--test' 264 printf '%s\r\n' \ 265 'Content-Disposition: form-data; name="file"; filename="truncated.txt"' 266 printf '%s\r\n' 'Content-Type: text/plain' 267 printf '\r\n' 268 printf '%s' 'partial data' 269 ) 270 271 printf '%s' "$body" | 272 env \ 273 UPLOAD_TMP_DIR="$TEST_UPLOAD_DIR" \ 274 STORAGE_ROOT="$TESTROOT/storage" \ 275 REPOSITORY_LOCK_DIR="$TEST_LOCK_DIR" \ 276 SIMPLEFILES_CONFIG="$TEST_CONFIG" \ 277 HTTP_ORIGIN="$TEST_CSRF_ORIGIN" \ 278 REMOTE_USER=bob \ 279 REQUEST_METHOD=POST \ 280 CONTENT_TYPE="multipart/form-data; boundary=test" \ 281 CONTENT_LENGTH="$(printf '%s' "$body" | wc -c | tr -d ' ')" \ 282 "$CGI" 283 } 284 285 upload_no_filename() { 286 body=$( 287 printf '%s\r\n' '--test' 288 printf '%s\r\n' \ 289 'Content-Disposition: form-data; name="file"' 290 printf '%s\r\n' 'Content-Type: text/plain' 291 printf '\r\n' 292 printf '%s' 'dummy data' 293 printf '\r\n--test--\r\n' 294 ) 295 296 printf '%s' "$body" | 297 env \ 298 UPLOAD_TMP_DIR="$TEST_UPLOAD_DIR" \ 299 STORAGE_ROOT="$TESTROOT/storage" \ 300 REPOSITORY_LOCK_DIR="$TEST_LOCK_DIR" \ 301 SIMPLEFILES_CONFIG="$TEST_CONFIG" \ 302 HTTP_ORIGIN="$TEST_CSRF_ORIGIN" \ 303 REMOTE_USER=bob \ 304 REQUEST_METHOD=POST \ 305 CONTENT_TYPE="multipart/form-data; boundary=test" \ 306 CONTENT_LENGTH="$(printf '%s' "$body" | wc -c | tr -d ' ')" \ 307 "$CGI" 308 } 309 310 upload_malformed() { 311 body=$( 312 printf '%s\r\n' '--test' 313 printf '%s\r\n' \ 314 'Content-Disposition: form-data; name="file"; filename="bad.txt"' 315 printf '%s\r\n' 'Content-Type: text/plain' 316 printf '%s\r\n' 'dummy data' 317 printf '%s\r\n' '--test--' 318 ) 319 320 printf '%s' "$body" | 321 env \ 322 UPLOAD_TMP_DIR="$TEST_UPLOAD_DIR" \ 323 STORAGE_ROOT="$TESTROOT/storage" \ 324 REPOSITORY_LOCK_DIR="$TEST_LOCK_DIR" \ 325 SIMPLEFILES_CONFIG="$TEST_CONFIG" \ 326 HTTP_ORIGIN="$TEST_CSRF_ORIGIN" \ 327 REMOTE_USER=bob \ 328 REQUEST_METHOD=POST \ 329 CONTENT_TYPE="multipart/form-data; boundary=test" \ 330 CONTENT_LENGTH="$(printf '%s' "$body" | wc -c | tr -d ' ')" \ 331 "$CGI" 332 } 333 334 upload_failed_after_data() { 335 tmp=$(mktemp) 336 337 { 338 printf '%s\r\n' '--test' 339 printf '%s\r\n' \ 340 'Content-Disposition: form-data; name="file"; filename="failed.txt"' 341 printf '%s\r\n' 'Content-Type: text/plain' 342 printf '\r\n' 343 344 awk 'BEGIN { 345 for (i = 0; i < 8192; i++) 346 printf "A" 347 }' 348 } > "$tmp" 349 350 env \ 351 UPLOAD_TMP_DIR="$TEST_UPLOAD_DIR" \ 352 STORAGE_ROOT="$TESTROOT/storage" \ 353 REPOSITORY_LOCK_DIR="$TEST_LOCK_DIR" \ 354 SIMPLEFILES_CONFIG="$TEST_CONFIG" \ 355 HTTP_ORIGIN="$TEST_CSRF_ORIGIN" \ 356 REMOTE_USER=bob \ 357 REQUEST_METHOD=POST \ 358 CONTENT_TYPE="multipart/form-data; boundary=test" \ 359 CONTENT_LENGTH="$(wc -c < "$tmp" | tr -d ' ')" \ 360 "$CGI" < "$tmp" 361 362 rm -f "$tmp" 363 } 364 365 upload_short_body() { 366 body='short body' 367 368 printf '%s' "$body" | 369 env \ 370 UPLOAD_TMP_DIR="$TEST_UPLOAD_DIR" \ 371 STORAGE_ROOT="$TESTROOT/storage" \ 372 REPOSITORY_LOCK_DIR="$TEST_LOCK_DIR" \ 373 SIMPLEFILES_CONFIG="$TEST_CONFIG" \ 374 HTTP_ORIGIN="$TEST_CSRF_ORIGIN" \ 375 REMOTE_USER=bob \ 376 REQUEST_METHOD=POST \ 377 CONTENT_TYPE="multipart/form-data; boundary=test" \ 378 CONTENT_LENGTH=999 \ 379 "$CGI" 380 } 381 382 upload_min_free_rejected() { 383 body=$( 384 printf '%s\r\n' '--test' 385 printf '%s\r\n' \ 386 'Content-Disposition: form-data; name="file"; filename="minfree.txt"' 387 printf '%s\r\n' 'Content-Type: text/plain' 388 printf '\r\n' 389 printf '%s' 'dummy data' 390 printf '\r\n--test--\r\n' 391 ) 392 393 printf '%s' "$body" | 394 env \ 395 STORAGE_ROOT="$TESTROOT/storage" \ 396 UPLOAD_TMP_DIR="$TEST_UPLOAD_DIR" \ 397 REPOSITORY_LOCK_DIR="$TEST_LOCK_DIR" \ 398 MIN_FREE_BYTES=9223372036854775807 \ 399 SIMPLEFILES_CONFIG="$TEST_CONFIG" \ 400 HTTP_ORIGIN="$TEST_CSRF_ORIGIN" \ 401 REMOTE_USER=bob \ 402 REQUEST_METHOD=POST \ 403 CONTENT_TYPE="multipart/form-data; boundary=test" \ 404 CONTENT_LENGTH="$(printf '%s' "$body" | wc -c | tr -d ' ')" \ 405 "$CGI" 406 } 407 408 upload_min_free_checked_before_spool() { 409 body=$( 410 printf '%s\r\n' '--test' 411 printf '%s\r\n' \ 412 'Content-Disposition: form-data; name="file"; filename="minfree.txt"' 413 printf '%s\r\n' 'Content-Type: text/plain' 414 printf '\r\n' 415 printf '%s' 'dummy data' 416 printf '\r\n--test--\r\n' 417 ) 418 419 bad_tmp="$TESTROOT/not-a-directory" 420 printf '%s' > "$bad_tmp" 421 422 printf '%s' "$body" | 423 env \ 424 STORAGE_ROOT="$TESTROOT/storage" \ 425 UPLOAD_TMP_DIR="$bad_tmp" \ 426 REPOSITORY_LOCK_DIR="$TEST_LOCK_DIR" \ 427 MIN_FREE_BYTES=9223372036854775807 \ 428 SIMPLEFILES_CONFIG="$TEST_CONFIG" \ 429 HTTP_ORIGIN="$TEST_CSRF_ORIGIN" \ 430 REMOTE_USER=bob \ 431 REQUEST_METHOD=POST \ 432 CONTENT_TYPE="multipart/form-data; boundary=test" \ 433 CONTENT_LENGTH="$(printf '%s' "$body" | wc -c | tr -d ' ')" \ 434 "$CGI" 435 } 436 437 upload_min_free_allows() { 438 body=$( 439 printf '%s\r\n' '--test' 440 printf '%s\r\n' \ 441 'Content-Disposition: form-data; name="file"; filename="minfree-ok.txt"' 442 printf '%s\r\n' 'Content-Type: text/plain' 443 printf '\r\n' 444 printf '%s' 'dummy data' 445 printf '\r\n--test--\r\n' 446 ) 447 448 printf '%s' "$body" | 449 env \ 450 STORAGE_ROOT="$TESTROOT/storage" \ 451 UPLOAD_TMP_DIR="$TEST_UPLOAD_DIR" \ 452 REPOSITORY_LOCK_DIR="$TEST_LOCK_DIR" \ 453 MIN_FREE_BYTES=1 \ 454 SIMPLEFILES_CONFIG="$TEST_CONFIG" \ 455 HTTP_ORIGIN="$TEST_CSRF_ORIGIN" \ 456 REMOTE_USER=bob \ 457 REQUEST_METHOD=POST \ 458 CONTENT_TYPE="multipart/form-data; boundary=test" \ 459 CONTENT_LENGTH="$(printf '%s' "$body" | wc -c | tr -d ' ')" \ 460 "$CGI" 461 } 462 463 upload_post_origin() { 464 origin=$1 465 466 body=$( 467 printf '%s\r\n' '--test' 468 printf '%s\r\n' \ 469 'Content-Disposition: form-data; name="file"; filename="csrf.txt"' 470 printf '%s\r\n' 'Content-Type: text/plain' 471 printf '\r\n' 472 printf '%s' 'csrf test' 473 printf '\r\n--test--\r\n' 474 ) 475 476 printf '%s' "$body" | 477 env \ 478 STORAGE_ROOT="$TESTROOT/storage" \ 479 UPLOAD_TMP_DIR="$TEST_UPLOAD_DIR" \ 480 REPOSITORY_LOCK_DIR="$TEST_LOCK_DIR" \ 481 SIMPLEFILES_CONFIG="$TEST_CONFIG" \ 482 HTTP_ORIGIN="$origin" \ 483 REMOTE_USER=bob \ 484 REQUEST_METHOD=POST \ 485 CONTENT_TYPE="multipart/form-data; boundary=test" \ 486 CONTENT_LENGTH="$(printf '%s' "$body" | wc -c | tr -d ' ')" \ 487 "$CGI" 488 } 489 490 upload_post_no_origin() { 491 body=$( 492 printf '%s\r\n' '--test' 493 printf '%s\r\n' \ 494 'Content-Disposition: form-data; name="file"; filename="csrf.txt"' 495 printf '%s\r\n' 'Content-Type: text/plain' 496 printf '\r\n' 497 printf '%s' 'csrf test' 498 printf '\r\n--test--\r\n' 499 ) 500 501 printf '%s' "$body" | 502 env \ 503 STORAGE_ROOT="$TESTROOT/storage" \ 504 UPLOAD_TMP_DIR="$TEST_UPLOAD_DIR" \ 505 REPOSITORY_LOCK_DIR="$TEST_LOCK_DIR" \ 506 SIMPLEFILES_CONFIG="$TEST_CONFIG" \ 507 REQUEST_METHOD=POST \ 508 REMOTE_USER=bob \ 509 CONTENT_TYPE="multipart/form-data; boundary=test" \ 510 CONTENT_LENGTH="$(printf '%s' "$body" | wc -c | tr -d ' ')" \ 511 "$CGI" 512 } 513 514 upload_binary() { 515 tmp=$(mktemp) 516 517 { 518 printf '%s\r\n' '--test' 519 printf '%s\r\n' \ 520 'Content-Disposition: form-data; name="file"; filename="binary.bin"' 521 printf '%s\r\n' 'Content-Type: application/octet-stream' 522 printf '\r\n' 523 printf '\000\001\002\003\377' 524 printf '\r\n--test--\r\n' 525 } > "$tmp" 526 527 env \ 528 STORAGE_ROOT="$TESTROOT/storage" \ 529 UPLOAD_TMP_DIR="$TEST_UPLOAD_DIR" \ 530 REPOSITORY_LOCK_DIR="$TEST_LOCK_DIR" \ 531 SIMPLEFILES_CONFIG="$TEST_CONFIG" \ 532 HTTP_ORIGIN="$TEST_CSRF_ORIGIN" \ 533 REMOTE_USER=bob \ 534 REQUEST_METHOD=POST \ 535 CONTENT_TYPE="multipart/form-data; boundary=test" \ 536 CONTENT_LENGTH="$(wc -c < "$tmp" | tr -d ' ')" \ 537 "$CGI" < "$tmp" 538 539 rm -f "$tmp" 540 } 541 542 upload_post_user() { 543 user=$1 544 545 body=$( 546 printf '%s\r\n' '--test' 547 printf '%s\r\n' \ 548 'Content-Disposition: form-data; name="file"; filename="upload.txt"' 549 printf '%s\r\n' 'Content-Type: text/plain' 550 printf '\r\n' 551 printf '%s' 'uploaded content' 552 printf '\r\n--test--\r\n' 553 ) 554 555 printf '%s' "$body" | 556 env \ 557 SCRIPT_NAME=/default/upload.cgi \ 558 STORAGE_ROOT="$TESTROOT/storage" \ 559 UPLOAD_TMP_DIR="$TEST_UPLOAD_DIR" \ 560 REPOSITORY_LOCK_DIR="$TEST_LOCK_DIR" \ 561 SIMPLEFILES_CONFIG="$TEST_CONFIG" \ 562 HTTP_ORIGIN="$TEST_CSRF_ORIGIN" \ 563 REMOTE_USER="$user" \ 564 REQUEST_METHOD=POST \ 565 CONTENT_TYPE="multipart/form-data; boundary=test" \ 566 CONTENT_LENGTH="$(printf '%s' "$body" | wc -c | tr -d ' ')" \ 567 "$CGI" 568 } 569 570 upload_post_no_user() { 571 body=$( 572 printf '%s\r\n' '--test' 573 printf '%s\r\n' \ 574 'Content-Disposition: form-data; name="file"; filename="upload.txt"' 575 printf '%s\r\n' 'Content-Type: text/plain' 576 printf '\r\n' 577 printf '%s' 'uploaded content' 578 printf '\r\n--test--\r\n' 579 ) 580 581 ( 582 unset REMOTE_USER 583 584 printf '%s' "$body" | 585 STORAGE_ROOT="$TESTROOT/storage" \ 586 UPLOAD_TMP_DIR="$TEST_UPLOAD_DIR" \ 587 REPOSITORY_LOCK_DIR="$TEST_LOCK_DIR" \ 588 SIMPLEFILES_CONFIG="$TEST_CONFIG" \ 589 HTTP_ORIGIN="$TEST_CSRF_ORIGIN" \ 590 SCRIPT_NAME=/default/upload.cgi \ 591 REQUEST_METHOD=POST \ 592 CONTENT_TYPE="multipart/form-data; boundary=test" \ 593 CONTENT_LENGTH="$(printf '%s' "$body" | wc -c | tr -d ' ')" \ 594 "$CGI" 595 ) 596 } 597 598 upload_filename_in_body() { 599 body=$( 600 printf '%s\r\n' '--test' 601 printf '%s\r\n' \ 602 'Content-Disposition: form-data; name="file"; filename="safe.txt"' 603 printf '%s\r\n' 'Content-Type: text/plain' 604 printf '\r\n' 605 printf '%s' 'filename="evil.txt"' 606 printf '\r\n--test--\r\n' 607 ) 608 609 printf '%s' "$body" | 610 env \ 611 STORAGE_ROOT="$TESTROOT/storage" \ 612 UPLOAD_TMP_DIR="$TEST_UPLOAD_DIR" \ 613 REPOSITORY_LOCK_DIR="$TEST_LOCK_DIR" \ 614 SIMPLEFILES_CONFIG="$TEST_CONFIG" \ 615 HTTP_ORIGIN="$TEST_CSRF_ORIGIN" \ 616 REMOTE_USER=bob \ 617 REQUEST_METHOD=POST \ 618 CONTENT_TYPE="multipart/form-data; boundary=test" \ 619 CONTENT_LENGTH="$(printf '%s' "$body" | wc -c | tr -d ' ')" \ 620 "$CGI" 621 } 622 623 upload_multiple_files() { 624 body=$( 625 printf '%s\r\n' '--test' 626 printf '%s\r\n' \ 627 'Content-Disposition: form-data; name="file"; filename="one.txt"' 628 printf '%s\r\n' 'Content-Type: text/plain' 629 printf '\r\n' 630 printf '%s\r\n' 'first file' 631 printf '%s\r\n' '--test' 632 printf '%s\r\n' \ 633 'Content-Disposition: form-data; name="file"; filename="two.txt"' 634 printf '%s\r\n' 'Content-Type: text/plain' 635 printf '\r\n' 636 printf '%s\r\n' 'second file' 637 printf '%s\r\n' '--test--' 638 ) 639 640 printf '%s' "$body" | 641 env \ 642 STORAGE_ROOT="$TESTROOT/storage" \ 643 UPLOAD_TMP_DIR="$TEST_UPLOAD_DIR" \ 644 REPOSITORY_LOCK_DIR="$TEST_LOCK_DIR" \ 645 SIMPLEFILES_CONFIG="$TEST_CONFIG" \ 646 HTTP_ORIGIN="$TEST_CSRF_ORIGIN" \ 647 REMOTE_USER=bob \ 648 REQUEST_METHOD=POST \ 649 CONTENT_TYPE="multipart/form-data; boundary=test" \ 650 CONTENT_LENGTH="$(printf '%s' "$body" | wc -c | tr -d ' ')" \ 651 "$CGI" 652 } 653 654 upload_cleanup_failure() { 655 body=$( 656 printf '%s\r\n' '--test' 657 printf '%s\r\n' \ 658 'Content-Disposition: form-data; name="file"; filename="cleanup-failure.txt"' 659 printf '%s\r\n' 'Content-Type: text/plain' 660 printf '\r\n' 661 printf '%s' 'published despite cleanup failure' 662 printf '\r\n--test--\r\n' 663 ) 664 665 printf '%s' "$body" | 666 env \ 667 TEST_FAIL_TEMP_UNLINK=1 \ 668 STORAGE_ROOT="$TESTROOT/storage" \ 669 UPLOAD_TMP_DIR="$TEST_UPLOAD_DIR" \ 670 REPOSITORY_LOCK_DIR="$TEST_LOCK_DIR" \ 671 SIMPLEFILES_CONFIG="$TEST_CONFIG" \ 672 HTTP_ORIGIN="$TEST_CSRF_ORIGIN" \ 673 REMOTE_USER=bob \ 674 REQUEST_METHOD=POST \ 675 CONTENT_TYPE="multipart/form-data; boundary=test" \ 676 CONTENT_LENGTH="$(printf '%s' "$body" | wc -c | tr -d ' ')" \ 677 "$CGI" 678 } 679 680 #################################################### 681 682 # 683 # Request validation 684 # 685 686 reset_testroot 687 688 write_default_repo_config "ADMIN_USER=bob" 689 690 check_status \ 691 "GET upload page" \ 692 "200 OK" \ 693 upload_get 694 695 check_status \ 696 "reject invalid method" \ 697 "405 Method Not Allowed" \ 698 upload_put 699 700 check_body \ 701 "invalid method message" \ 702 "GET or POST required" \ 703 upload_put 704 705 check_status \ 706 "reject missing content type" \ 707 "400 Bad Request" \ 708 upload_post \ 709 "" \ 710 10 711 712 check_status \ 713 "reject invalid content type" \ 714 "400 Bad Request" \ 715 upload_post \ 716 "text/plain; boundary=test" \ 717 10 718 719 check_status \ 720 "reject missing boundary" \ 721 "400 Bad Request" \ 722 upload_post \ 723 "multipart/form-data" \ 724 10 725 726 check_status \ 727 "reject invalid length" \ 728 "400 Bad Request" \ 729 upload_post \ 730 "multipart/form-data; boundary=test" \ 731 abc 732 733 check_status \ 734 "reject zero length" \ 735 "413 Payload Too Large" \ 736 upload_post \ 737 "multipart/form-data; boundary=test" \ 738 0 739 740 check_status \ 741 "reject oversized upload" \ 742 "413 Payload Too Large" \ 743 upload_post \ 744 "multipart/form-data; boundary=test" \ 745 999999999 746 747 check_status \ 748 "reject missing content length" \ 749 "411 Length Required" \ 750 upload_post_no_length \ 751 "multipart/form-data; boundary=test" 752 753 check_status \ 754 "reject empty boundary" \ 755 "400 Bad Request" \ 756 upload_post \ 757 "multipart/form-data; boundary=" \ 758 10 759 760 long_boundary=$(printf '%0129d' 0 | tr '0' 'a') 761 762 check_status \ 763 "reject oversized boundary" \ 764 "400 Bad Request" \ 765 upload_post \ 766 "multipart/form-data; boundary=$long_boundary" \ 767 10 768 769 #################################################### 770 771 # 772 # Multipart validation 773 # 774 775 check_status \ 776 "reject missing filename" \ 777 "400 Bad Request" \ 778 upload_no_filename 779 780 check_status \ 781 "reject malformed multipart body" \ 782 "400 Bad Request" \ 783 upload_malformed 784 785 check_status \ 786 "reject short request body" \ 787 "400 Bad Request" \ 788 upload_short_body 789 790 check_redirect \ 791 "boundary-like string inside file" \ 792 "303 See Other" \ 793 "./" \ 794 upload_boundary_in_file 795 796 check_file \ 797 "boundary-like string preserved" \ 798 "$TESTROOT/storage/default/boundary.txt" \ 799 "before 800 --test-not-a-boundary 801 after" 802 803 check_redirect \ 804 "multipart boundary across reads" \ 805 "303 See Other" \ 806 "./" \ 807 upload_boundary_split 808 809 check_file \ 810 "multipart data preserved across reads" \ 811 "$TESTROOT/storage/default/split.txt" \ 812 "$(awk 'BEGIN { 813 for (i = 0; i < 8192; i++) 814 printf "A" 815 }')" 816 817 check_redirect \ 818 "filename is extracted from headers only" \ 819 "303 See Other" \ 820 "./" \ 821 upload_filename_in_body 822 823 check_file \ 824 "filename from header is preserved" \ 825 "$TESTROOT/storage/default/safe.txt" \ 826 'filename="evil.txt"' 827 828 check_file_missing \ 829 "filename from body is not used" \ 830 "$TESTROOT/storage/default/evil.txt" 831 832 #################################################### 833 834 # 835 # Multipart cardinatlity 836 # 837 838 reset_testroot 839 840 write_default_repo_config "ADMIN_USER=bob" 841 842 check_status \ 843 "reject multiple file upload" \ 844 "400 Bad Request" \ 845 upload_multiple_files 846 847 check_file_missing \ 848 "multiple upload creates no first file" \ 849 "$TESTROOT/storage/default/one.txt" 850 851 check_file_missing \ 852 "multiple upload creates no second file" \ 853 "$TESTROOT/storage/default/two.txt" 854 855 #################################################### 856 857 # 858 # Standalone uploads 859 # 860 861 reset_testroot 862 write_default_repo_config "ADMIN_USER=bob" 863 864 check_redirect \ 865 "extract filename" \ 866 "303 See Other" \ 867 "./" \ 868 upload 869 870 check_file \ 871 "file created" \ 872 "$TESTROOT/storage/default/hello world.mp4" \ 873 "dummy data" 874 875 check_redirect \ 876 "duplicate upload redirects" \ 877 "303 See Other" \ 878 "./" \ 879 upload 880 881 check_file \ 882 "duplicate file created" \ 883 "$TESTROOT/storage/default/hello world.1.mp4" \ 884 "dummy data" 885 886 #################################################### 887 888 # 889 # Quota tests 890 # 891 892 reset_testroot 893 write_default_repo_config "ADMIN_USER=bob" 894 895 check_redirect \ 896 "upload within quota" \ 897 "303 See Other" \ 898 "./" \ 899 upload_quota_ok 900 901 check_file \ 902 "quota ok file created" \ 903 "$TESTROOT/storage/default/quota_ok.txt" \ 904 "$(awk 'BEGIN { for (i = 0; i < 100; i++) printf "A" }')" 905 906 reset_testroot 907 write_default_repo_config "ADMIN_USER=bob" 908 909 dd \ 910 if=/dev/zero \ 911 of="$TESTROOT/storage/default/filler.bin" \ 912 bs=1 \ 913 count=9000 \ 914 2> /dev/null 915 916 check_status \ 917 "upload exceeds repo quota" \ 918 "507 Insufficient Storage" \ 919 upload_quota_exceeds 920 921 #################################################### 922 923 # 924 # Upload at exact quota boundary 925 # 926 927 reset_testroot 928 929 write_default_repo_config "ADMIN_USER=bob" 930 931 dd \ 932 if=/dev/zero \ 933 of="$TESTROOT/storage/default/filler.bin" \ 934 bs=1 \ 935 count=9216 \ 936 2> /dev/null 937 938 config_size=$(wc -c < "$TESTROOT/storage/default/.config" | tr -d ' ') 939 exact_quota=$((9216 + 1024 + config_size)) 940 941 check_redirect \ 942 "upload at exact quota limit" \ 943 "303 See Other" \ 944 "./" \ 945 upload_exact_quota \ 946 "$exact_quota" 947 948 #################################################### 949 950 # 951 # Default quota 952 # 953 954 reset_testroot 955 write_default_repo_config "ADMIN_USER=bob" 956 957 check_redirect \ 958 "default quota allows upload" \ 959 "303 See Other" \ 960 "./" \ 961 upload 962 963 #################################################### 964 965 # 966 # Repository QUOTA 967 # 968 969 reset_testroot 970 971 write_default_repo_config 'ADMIN_USER=bob 972 QUOTA=1' 973 974 check_status \ 975 "config quota rejects upload" \ 976 "507 Insufficient Storage" \ 977 upload 978 979 reset_testroot 980 981 write_default_repo_config 'ADMIN_USER=bob 982 QUOTA=1024' 983 984 check_redirect \ 985 "config quota allows upload" \ 986 "303 See Other" \ 987 "./" \ 988 upload 989 990 #################################################### 991 992 # 993 # Repository MAX_UPLOAD 994 # 995 996 reset_testroot 997 998 write_default_repo_config 'ADMIN_USER=bob 999 MAX_UPLOAD=1' 1000 1001 check_status \ 1002 "config max upload rejects upload" \ 1003 "413 Payload Too Large" \ 1004 upload 1005 1006 reset_testroot 1007 1008 write_default_repo_config 'ADMIN_USER=bob 1009 MAX_UPLOAD=1024' 1010 1011 check_redirect \ 1012 "config max upload allows upload" \ 1013 "303 See Other" \ 1014 "./" \ 1015 upload 1016 1017 #################################################### 1018 1019 # 1020 # Repository MAX_UPLOAD + QUOTA 1021 # 1022 1023 reset_testroot 1024 1025 write_default_repo_config 'ADMIN_USER=bob 1026 MAX_UPLOAD=1024 1027 QUOTA=1024' 1028 1029 check_redirect \ 1030 "config max upload+quota" \ 1031 "303 See Other" \ 1032 "./" \ 1033 upload 1034 1035 reset_testroot 1036 1037 write_default_repo_config 'ADMIN_USER=bob 1038 MAX_UPLOAD=1024 1039 QUOTA=1' 1040 1041 check_status \ 1042 "config max upload+quota rejects upload" \ 1043 "507 Insufficient Storage" \ 1044 upload 1045 1046 #################################################### 1047 1048 # 1049 # Invalid filenames 1050 # 1051 1052 reset_testroot 1053 write_default_repo_config "ADMIN_USER=bob" 1054 1055 check_status \ 1056 "reject .config upload" \ 1057 "400 Bad Request" \ 1058 upload_filename \ 1059 ".config" 1060 1061 check_status \ 1062 "reject path traversal filename" \ 1063 "400 Bad Request" \ 1064 upload_filename \ 1065 "../passwd" 1066 1067 check_status \ 1068 "reject slash filename" \ 1069 "400 Bad Request" \ 1070 upload_filename \ 1071 "subdir/file.txt" 1072 1073 check_status \ 1074 "reject empty filename" \ 1075 "400 Bad Request" \ 1076 upload_filename \ 1077 "" 1078 1079 check_status \ 1080 "reject dot filename" \ 1081 "400 Bad Request" \ 1082 upload_filename \ 1083 "." 1084 1085 check_status \ 1086 "reject dot-dot filename" \ 1087 "400 Bad Request" \ 1088 upload_filename \ 1089 ".." 1090 1091 long_filename=$(awk 'BEGIN { for (i = 0; i < 300; i++) printf "a" }') 1092 1093 check_status \ 1094 "reject oversized filename" \ 1095 "400 Bad Request" \ 1096 upload_filename \ 1097 "$long_filename" 1098 1099 #################################################### 1100 1101 # 1102 # Temporary upload cleanup 1103 # 1104 1105 reset_testroot 1106 write_default_repo_config "ADMIN_USER=bob" 1107 1108 check_status \ 1109 "reject missing closing boundary" \ 1110 "400 Bad Request" \ 1111 upload_truncated 1112 1113 check_no_temp_uploads \ 1114 "no temporary upload remains" \ 1115 "$TEST_UPLOAD_DIR" 1116 1117 #################################################### 1118 1119 # 1120 # Failed upload cleanup 1121 # 1122 1123 reset_testroot 1124 write_default_repo_config "ADMIN_USER=bob" 1125 1126 check_status \ 1127 "reject failed upload" \ 1128 "400 Bad Request" \ 1129 upload_failed_after_data 1130 1131 check_file_missing \ 1132 "failed upload leaves no destination file" \ 1133 "$TESTROOT/storage/default/failed.txt" 1134 1135 check_no_temp_uploads \ 1136 "failed upload leaves no temporary file" \ 1137 "$TEST_UPLOAD_DIR" 1138 1139 #################################################### 1140 1141 # 1142 # Minimum free space 1143 # 1144 1145 reset_testroot 1146 write_default_repo_config "ADMIN_USER=bob" 1147 1148 check_status \ 1149 "reject upload when minimum free space is not available" \ 1150 "507 Insufficient Storage" \ 1151 upload_min_free_rejected 1152 1153 check_status \ 1154 "minimum-free check happens before spool creation" \ 1155 "507 Insufficient Storage" \ 1156 upload_min_free_checked_before_spool 1157 1158 reset_testroot 1159 write_default_repo_config "ADMIN_USER=bob" 1160 1161 check_redirect \ 1162 "upload with sufficient minimum free space" \ 1163 "303 See Other" \ 1164 "./" \ 1165 upload_min_free_allows 1166 1167 check_file \ 1168 "minimum-free upload created file" \ 1169 "$TESTROOT/storage/default/minfree-ok.txt" \ 1170 "dummy data" 1171 1172 #################################################### 1173 1174 # 1175 # CSRF Origin protection 1176 # 1177 1178 reset_testroot 1179 write_default_repo_config "ADMIN_USER=bob" 1180 1181 check_status \ 1182 "upload accepts configured Origin" \ 1183 "303 See Other" \ 1184 upload_post_origin \ 1185 "$TEST_CSRF_ORIGIN" 1186 1187 check_file \ 1188 "configured Origin upload creates file" \ 1189 "$TESTROOT/storage/default/csrf.txt" \ 1190 "csrf test" 1191 1192 reset_testroot 1193 write_default_repo_config "ADMIN_USER=bob" 1194 1195 check_status \ 1196 "upload rejects missing Origin" \ 1197 "403 Forbidden" \ 1198 upload_post_no_origin 1199 1200 check_file_missing \ 1201 "missing Origin creates no file" \ 1202 "$TESTROOT/storage/default/csrf.txt" 1203 1204 reset_testroot 1205 write_default_repo_config "ADMIN_USER=bob" 1206 1207 check_status \ 1208 "upload rejects wrong Origin" \ 1209 "403 Forbidden" \ 1210 upload_post_origin \ 1211 "https://evil.example" 1212 1213 check_file_missing \ 1214 "wrong Origin creates no file" \ 1215 "$TESTROOT/storage/default/csrf.txt" 1216 1217 reset_testroot 1218 write_default_repo_config "ADMIN_USER=bob" 1219 1220 check_status \ 1221 "upload rejects Origin with configured origin as prefix" \ 1222 "403 Forbidden" \ 1223 upload_post_origin \ 1224 "https://localhost.evil.example" 1225 1226 reset_testroot 1227 write_default_repo_config "ADMIN_USER=bob" 1228 1229 check_status \ 1230 "upload rejects wrong Origin scheme" \ 1231 "403 Forbidden" \ 1232 upload_post_origin \ 1233 "https://localhost:8000" 1234 1235 #################################################### 1236 1237 # 1238 # Binary upload 1239 # 1240 1241 reset_testroot 1242 write_default_repo_config "ADMIN_USER=bob" 1243 1244 check_redirect \ 1245 "binary upload succeeds" \ 1246 "303 See Other" \ 1247 "./" \ 1248 upload_binary 1249 1250 check_file_bytes \ 1251 "binary file preserved" \ 1252 "$TESTROOT/storage/default/binary.bin" \ 1253 '\000\001\002\003\377' 1254 1255 #################################################### 1256 1257 # 1258 # Read-only repository 1259 # 1260 1261 reset_testroot 1262 write_default_repo_config 'ADMIN_USER=bob 1263 READ_ONLY=1' 1264 1265 check_body \ 1266 "readonly GET upload rejected" \ 1267 "read-only" \ 1268 upload_get 1269 1270 check_file_missing \ 1271 "readonly GET does not create file" \ 1272 "$TESTROOT/storage/default/readonly.txt" 1273 1274 check_status \ 1275 "readonly POST upload rejected" \ 1276 "403 Forbidden" \ 1277 upload 1278 1279 #################################################### 1280 1281 # 1282 # Authentication 1283 # 1284 1285 # 1286 # ADMIN_USER configured: admin can upload. 1287 # 1288 1289 reset_testroot 1290 1291 write_default_repo_config "ADMIN_USER=bob" 1292 1293 check_status \ 1294 "admin user allowed to upload" \ 1295 "303 See Other" \ 1296 upload_post_user \ 1297 "bob" 1298 1299 check_file \ 1300 "admin upload succeeds" \ 1301 "$TESTROOT/storage/default/upload.txt" \ 1302 "uploaded content" 1303 1304 # 1305 # ADMIN_USER configured: non-admin is rejected. 1306 # 1307 1308 reset_testroot 1309 1310 write_default_repo_config "ADMIN_USER=bob" 1311 1312 check_status \ 1313 "non-admin user rejected from upload" \ 1314 "403 Forbidden" \ 1315 upload_post_user \ 1316 "alice" 1317 1318 check_file_missing \ 1319 "non-admin upload not created" \ 1320 "$TESTROOT/storage/default/upload.txt" 1321 1322 # 1323 # ADMIN_USER configured: anonymous is rejected. 1324 # 1325 1326 reset_testroot 1327 1328 write_default_repo_config "ADMIN_USER=bob" 1329 1330 check_status \ 1331 "anonymous upload rejected" \ 1332 "403 Forbidden" \ 1333 upload_post_no_user 1334 1335 check_file_missing \ 1336 "anonymous upload not created" \ 1337 "$TESTROOT/storage/default/upload.txt" 1338 1339 # 1340 # No ADMIN_USER: anonymous is rejected. 1341 # 1342 1343 reset_testroot 1344 1345 write_default_repo_config "" 1346 1347 check_status \ 1348 "anonymous upload rejected without ADMIN_USER" \ 1349 "403 Forbidden" \ 1350 upload_post_no_user 1351 1352 check_file_missing \ 1353 "anonymous upload without ADMIN_USER not created" \ 1354 "$TESTROOT/storage/default/upload.txt" 1355 1356 # 1357 # No ADMIN_USER: authenticated user is rejected. 1358 # 1359 1360 reset_testroot 1361 1362 write_default_repo_config "" 1363 1364 check_status \ 1365 "authenticated upload rejected without ADMIN_USER" \ 1366 "403 Forbidden" \ 1367 upload_post_user \ 1368 "alice" 1369 1370 check_file_missing \ 1371 "authenticated upload without ADMIN_USER not created" \ 1372 "$TESTROOT/storage/default/upload.txt" 1373 1374 # 1375 # Template failure handling 1376 # 1377 1378 reset_testroot 1379 write_default_repo_config "ADMIN_USER=bob" 1380 1381 check_status \ 1382 "template failure returns 500" \ 1383 "500 Internal Server Error" \ 1384 env \ 1385 STORAGE_ROOT="$TESTROOT/storage" \ 1386 SIMPLEFILES_CONFIG="$TEST_CONFIG" \ 1387 REMOTE_USER=bob \ 1388 REQUEST_METHOD=GET \ 1389 SIMPLEFILES_ROOT="$TESTROOT/does-not-exist" \ 1390 "$CGI" 1391 1392 check_body \ 1393 "template failure does not return 200" \ 1394 "Template error" \ 1395 env \ 1396 STORAGE_ROOT="$TESTROOT/storage" \ 1397 SIMPLEFILES_CONFIG="$TEST_CONFIG" \ 1398 REMOTE_USER=bob \ 1399 REQUEST_METHOD=GET \ 1400 SIMPLEFILES_ROOT="$TESTROOT/does-not-exist" \ 1401 "$CGI" 1402 1403 #################################################### 1404 1405 # 1406 # Temporary upload cleanup failure 1407 # 1408 1409 reset_testroot 1410 write_default_repo_config "ADMIN_USER=bob" 1411 1412 check_redirect \ 1413 "cleanup failure preserves published upload" \ 1414 "303 See Other" \ 1415 "./" \ 1416 upload_cleanup_failure 1417 1418 check_file \ 1419 "cleanup failure preserves published file" \ 1420 "$TESTROOT/storage/default/cleanup-failure.txt" \ 1421 "published despite cleanup failure" 1422 1423 test_result