download.sh (8132B)
1 #!/bin/sh 2 3 set -eu 4 5 . "$(dirname "$0")/testlib.sh" 6 7 CGI=${1:-build/download.cgi} 8 9 download() { 10 env \ 11 SCRIPT_NAME=/default/download.cgi \ 12 STORAGE_ROOT="$TESTROOT/storage" \ 13 SIMPLEFILES_CONFIG="$TEST_CONFIG" \ 14 REQUEST_METHOD=GET \ 15 QUERY_STRING="${1:-}" \ 16 REMOTE_USER=bob \ 17 "$CGI" 18 } 19 20 download_post() { 21 env \ 22 SCRIPT_NAME=/default/download.cgi \ 23 STORAGE_ROOT="$TESTROOT/storage" \ 24 SIMPLEFILES_CONFIG="$TEST_CONFIG" \ 25 REQUEST_METHOD=POST \ 26 CONTENT_LENGTH=0 \ 27 QUERY_STRING="${1:-}" \ 28 REMOTE_USER=bob \ 29 "$CGI" 30 } 31 32 download_user() { 33 user=$1 34 35 env \ 36 SCRIPT_NAME=/default/download.cgi \ 37 STORAGE_ROOT="$TESTROOT/storage" \ 38 SIMPLEFILES_CONFIG="$TEST_CONFIG" \ 39 REQUEST_METHOD=GET \ 40 QUERY_STRING="file=download.txt" \ 41 REMOTE_USER="$user" \ 42 "$CGI" 43 } 44 45 download_no_user() { 46 query=${1:-} 47 48 ( 49 unset REMOTE_USER 50 51 export SCRIPT_NAME=/default/download.cgi 52 export STORAGE_ROOT="$TESTROOT/storage" 53 export SIMPLEFILES_CONFIG="$TEST_CONFIG" 54 export REQUEST_METHOD=GET 55 export QUERY_STRING="$query" 56 57 "$CGI" 58 ) 59 } 60 61 #################################################### 62 63 reset_testroot 64 65 write_default_repo_config "ADMIN_USER=bob" 66 67 echo "hello world" > "$TESTROOT/storage/default/file1.txt" 68 echo "hello world" > "$TESTROOT/storage/default/file2.txt" 69 echo "special filename" > "$TESTROOT/storage/default/<test>&file.txt" 70 printf 'space filename\n' > "$TESTROOT/storage/default/hello world.txt" 71 72 dd if=/dev/urandom \ 73 of="$TESTROOT/storage/default/binary.bin" \ 74 bs=1024 count=1 2>/dev/null 75 76 # 77 # Valid download 78 # 79 80 check_status \ 81 "valid download status" \ 82 "200 OK" \ 83 download \ 84 "file=file1.txt" 85 86 check_header \ 87 "content-type header" \ 88 "Content-Type" \ 89 "application/octet-stream" \ 90 download \ 91 "file=file1.txt" 92 93 check_header \ 94 "content-disposition header" \ 95 "Content-Disposition" \ 96 'attachment; filename="file1.txt"' \ 97 download \ 98 "file=file1.txt" 99 100 check_header \ 101 "content-length header" \ 102 "Content-Length" \ 103 "12" \ 104 download \ 105 "file=file1.txt" 106 107 # 108 # Binary file 109 # 110 111 check_header \ 112 "binary content-length" \ 113 "Content-Length" \ 114 "1024" \ 115 download \ 116 "file=binary.bin" 117 118 # 119 # Missing / invalid files 120 # 121 122 check_body \ 123 "missing file" \ 124 "404 Not Found" \ 125 download \ 126 "file=nonexistent.txt" 127 128 printf 'QUOTA=1024\n' > "$TESTROOT/storage/default/.config" 129 130 check_body \ 131 ".config not downloadable" \ 132 "400 Bad Request" \ 133 download \ 134 "file=.config" 135 136 check_body \ 137 "path traversal ../" \ 138 "400 Bad Request" \ 139 download \ 140 "file=../passwd" 141 142 check_body \ 143 "path traversal .." \ 144 "400 Bad Request" \ 145 download \ 146 "file=.." 147 148 check_body \ 149 "path traversal with slash" \ 150 "400 Bad Request" \ 151 download \ 152 "file=subdir/file.txt" 153 154 check_body \ 155 "encoded dot traversal" \ 156 "400 Bad Request" \ 157 download \ 158 "file=%2e%2e/passwd" 159 160 check_body \ 161 "mixed encoded traversal" \ 162 "400 Bad Request" \ 163 download \ 164 "file=.%2e/passwd" 165 166 check_body \ 167 "mixed encoded slash traversal" \ 168 "400 Bad Request" \ 169 download \ 170 "file=..%2Fpasswd" 171 172 check_body \ 173 "encoded nested traversal" \ 174 "400 Bad Request" \ 175 download \ 176 "file=%2e%2e%2f%2e%2e%2fpasswd" 177 178 check_body \ 179 "encoded NUL" \ 180 "400 Bad Request" \ 181 download \ 182 "file=file%00.txt" 183 184 check_body \ 185 "malformed percent encoding" \ 186 "400 Bad Request" \ 187 download \ 188 "file=file%ZZ.txt" 189 190 check_body \ 191 "incomplete percent encoding" \ 192 "400 Bad Request" \ 193 download \ 194 "file=file%2.txt" 195 196 check_body \ 197 "empty filename" \ 198 "400 Bad Request" \ 199 download \ 200 "file=" 201 202 check_body \ 203 "missing query string" \ 204 "400 Bad Request" \ 205 download 206 207 check_body \ 208 "missing file parameter" \ 209 "Missing filename" \ 210 download \ 211 "foo=bar" 212 213 check_body \ 214 "file parameter without equals" \ 215 "Missing filename" \ 216 download \ 217 "file" 218 219 check_body \ 220 "empty file parameter" \ 221 "Invalid filename" \ 222 download \ 223 "file=" 224 225 check_body \ 226 "empty file among parameters" \ 227 "Invalid filename" \ 228 download \ 229 "foo=bar&file=&baz=qux" 230 231 check_status \ 232 "file parameter after another parameter" \ 233 "200 OK" \ 234 download \ 235 "foo=bar&file=file1.txt" 236 237 check_status \ 238 "file parameter before another parameter" \ 239 "200 OK" \ 240 download \ 241 "file=file1.txt&foo=bar" 242 243 check_status \ 244 "file parameter with extra query parameters" \ 245 "200 OK" \ 246 download \ 247 "foo=bar&file=file1.txt&baz=qux" 248 249 check_status \ 250 "reject duplicate file parameter" \ 251 "400 Bad Request" \ 252 download \ 253 "file=file1.txt&file=file2.txt" 254 255 check_status \ 256 "reject duplicate file parameter reversed" \ 257 "400 Bad Request" \ 258 download \ 259 "file=file2.txt&file=file1.txt" 260 261 # 262 # Invalid method 263 # 264 265 check_body \ 266 "post not allowed" \ 267 "GET required" \ 268 download_post \ 269 "file=file1.txt" 270 271 # 272 # Directory 273 # 274 275 mkdir -p "$TESTROOT/storage/default/subdir" 276 277 check_body \ 278 "directory not downloadable" \ 279 "404 Not Found" \ 280 download \ 281 "file=subdir" 282 283 check_body \ 284 "encoded path traversal" \ 285 "400 Bad Request" \ 286 download \ 287 "file=subdir%2ffile.txt" 288 289 # 290 # Special filenames 291 # 292 293 check_body \ 294 "encoded special filename" \ 295 "200 OK" \ 296 download \ 297 "file=%3Ctest%3E%26file.txt" 298 299 check_body \ 300 "plus decoded to space" \ 301 "200 OK" \ 302 download \ 303 "file=hello+world.txt" 304 305 check_body \ 306 "plus decoded filename content" \ 307 "space filename" \ 308 download \ 309 "file=hello+world.txt" 310 311 # 312 # Header injection 313 # 314 315 check_body \ 316 "encoded CR rejected" \ 317 "400 Bad Request" \ 318 download \ 319 "file=foo%0d%0aX-Test%3A%20injected.txt" 320 321 check_body \ 322 "encoded LF rejected" \ 323 "400 Bad Request" \ 324 download \ 325 "file=foo%0aX-Test%3A%20injected.txt" 326 327 # 328 # Symlink outside repository 329 # 330 331 reset_testroot 332 333 write_default_repo_config "ADMIN_USER=bob" 334 335 mkdir -p "$TESTROOT/storage/other" 336 337 printf 'secret\n' > "$TESTROOT/storage/other/secret.txt" 338 339 ln -s "$TESTROOT/storage/other/secret.txt" \ 340 "$TESTROOT/storage/default/linked.txt" 341 342 check_body \ 343 "symlink outside repository" \ 344 "404 Not Found" \ 345 download \ 346 "file=linked.txt" 347 348 check_not_body \ 349 "symlink target not exposed" \ 350 "secret" \ 351 download \ 352 "file=linked.txt" 353 354 # 355 # Authentication 356 # 357 358 reset_testroot 359 360 write_default_repo_config "" 361 362 printf '%s' 'download content' \ 363 > "$TESTROOT/storage/default/download.txt" 364 365 check_status \ 366 "anonymous download rejected without ADMIN_USER" \ 367 "403 Forbidden" \ 368 download_no_user \ 369 "file=download.txt" 370 371 check_file \ 372 "anonymous download preserves file" \ 373 "$TESTROOT/storage/default/download.txt" \ 374 "download content" 375 376 # 377 # ADMIN_USER configured: admin user allowed 378 # 379 380 reset_testroot 381 382 write_default_repo_config "ADMIN_USER=bob" 383 384 printf '%s' 'download content' \ 385 > "$TESTROOT/storage/default/download.txt" 386 387 check_status \ 388 "admin user allowed to download" \ 389 "200 OK" \ 390 download_user \ 391 "bob" 392 393 check_body \ 394 "admin download succeeds" \ 395 "download content" \ 396 download_user \ 397 "bob" 398 399 # 400 # ADMIN_USER configured: non-admin still has read access 401 # 402 403 reset_testroot 404 405 write_default_repo_config "ADMIN_USER=bob" 406 407 printf '%s' 'download content' \ 408 > "$TESTROOT/storage/default/download.txt" 409 410 check_status \ 411 "non-admin user allowed to download" \ 412 "200 OK" \ 413 download_user \ 414 "alice" 415 416 check_body \ 417 "non-admin download succeeds" \ 418 "download content" \ 419 download_user \ 420 "alice" 421 422 # 423 # Anonymous access with ADMIN_USER 424 # 425 426 reset_testroot 427 428 write_default_repo_config "ADMIN_USER=bob" 429 430 printf '%s' 'download content' \ 431 > "$TESTROOT/storage/default/download.txt" 432 433 check_status \ 434 "anonymous download rejected" \ 435 "403 Forbidden" \ 436 download_no_user \ 437 "file=download.txt" 438 439 check_file \ 440 "anonymous download preserves file" \ 441 "$TESTROOT/storage/default/download.txt" \ 442 "download content" 443 444 # 445 # Anonymous read access 446 # 447 448 reset_testroot 449 450 write_default_repo_config "ADMIN_USER=bob" 451 452 write_global_config "ANONYMOUS_READ=1" 453 454 printf '%s' 'download content' \ 455 > "$TESTROOT/storage/default/download.txt" 456 457 check_status \ 458 "anonymous download allowed with anonymous read" \ 459 "200 OK" \ 460 download_no_user \ 461 "file=download.txt" 462 463 check_body \ 464 "anonymous download succeeds with anonymous read" \ 465 "download content" \ 466 download_no_user \ 467 "file=download.txt" 468 469 test_result 470 471 test_result