1 #include "first.h" 2 3 #undef NDEBUG 4 #include <sys/types.h> 5 #include <assert.h> 6 #include <stdlib.h> 7 #include <stdio.h> 8 9 #include "mod_staticfile.c" 10 #include "fdlog.h" 11 #include "http_date.h" 12 #include "http_etag.h" 13 #include "http_header.h" 14 15 __attribute_noinline__ 16 static void test_mod_staticfile_reset (request_st * const r) 17 { 18 r->http_status = 0; 19 r->resp_htags = 0; 20 array_reset_data_strings(&r->resp_headers); 21 http_response_body_clear(r, 0); 22 r->conf.etag_flags = ETAG_USE_INODE | ETAG_USE_MTIME | ETAG_USE_SIZE; 23 } 24 25 __attribute_noinline__ 26 static void 27 run_http_response_send_file (request_st * const r, int line, int status, const char *desc) 28 { 29 http_response_send_file(r, &r->physical.path, NULL); 30 if (r->http_status != status) { 31 fprintf(stderr, 32 "%s.%d: %s() failed: expected '%d', got '%d' for test %s\n", 33 __FILE__, line, "http_response_send_file", status, 34 r->http_status, desc); 35 fflush(stderr); 36 abort(); 37 } 38 } 39 40 static void 41 test_http_response_send_file (request_st * const r, time_t lmtime) 42 { 43 test_mod_staticfile_reset(r); 44 const buffer *vb; 45 46 /*(mismatch test must be first, else stat_cache will have cached mimetype)*/ 47 array * const mimetypes_empty = array_init(0); 48 const array * const mimetypes_orig = r->conf.mimetypes; 49 r->conf.mimetypes = mimetypes_empty; 50 run_http_response_send_file(r, __LINE__, 200, 51 "basic static file (w/o mimetype match)"); 52 vb = http_header_response_get(r, HTTP_HEADER_CONTENT_TYPE, 53 CONST_STR_LEN("Content-Type")); 54 assert(vb && buffer_eq_slen(vb, CONST_STR_LEN("application/octet-stream"))); 55 test_mod_staticfile_reset(r); 56 r->conf.mimetypes = mimetypes_orig; 57 array_free(mimetypes_empty); 58 59 run_http_response_send_file(r, __LINE__, 200, 60 "basic static file (w/ mimetype match)"); 61 vb = http_header_response_get(r, HTTP_HEADER_CONTENT_TYPE, 62 CONST_STR_LEN("Content-Type")); 63 assert(vb && buffer_eq_slen(vb, CONST_STR_LEN("text/plain"))); 64 vb = http_header_response_get(r, HTTP_HEADER_ETAG, 65 CONST_STR_LEN("ETag")); 66 assert(vb && vb->ptr[0] == '"' && vb->ptr[buffer_clen(vb)-1] == '"'); 67 vb = http_header_response_get(r, HTTP_HEADER_LAST_MODIFIED, 68 CONST_STR_LEN("Last-Modified")); 69 assert(vb); 70 test_mod_staticfile_reset(r); 71 72 const uint32_t plen = buffer_clen(&r->physical.path); 73 buffer_append_string_len(&r->physical.path, CONST_STR_LEN("-nonexistent")); 74 run_http_response_send_file(r, __LINE__, 404, 75 "non-existent file"); 76 test_mod_staticfile_reset(r); 77 buffer_truncate(&r->physical.path, plen); 78 79 http_header_request_set(r, HTTP_HEADER_IF_MODIFIED_SINCE, 80 CONST_STR_LEN("If-Modified-Since"), 81 CONST_STR_LEN("")); 82 run_http_response_send_file(r, __LINE__, 200, 83 "if-modified-since invalid (empty)"); 84 test_mod_staticfile_reset(r); 85 86 http_header_request_set(r, HTTP_HEADER_IF_MODIFIED_SINCE, 87 CONST_STR_LEN("If-Modified-Since"), 88 CONST_STR_LEN("foobar")); 89 run_http_response_send_file(r, __LINE__, 200, 90 "if-modified-since invalid (not time string)"); 91 test_mod_staticfile_reset(r); 92 93 http_header_request_set(r, HTTP_HEADER_IF_MODIFIED_SINCE, 94 CONST_STR_LEN("If-Modified-Since"), 95 CONST_STR_LEN("this string is too long to be a valid timestamp")); 96 run_http_response_send_file(r, __LINE__, 200, 97 "if-modified-since invalid (too long to be valid time string)"); 98 test_mod_staticfile_reset(r); 99 100 char lmtime_str[HTTP_DATE_SZ]; 101 uint32_t lmtime_len; 102 103 lmtime_len = http_date_time_to_str(lmtime_str,sizeof(lmtime_str), 104 lmtime ? lmtime-1 : lmtime); 105 http_header_request_set(r, HTTP_HEADER_IF_MODIFIED_SINCE, 106 CONST_STR_LEN("If-Modified-Since"), 107 lmtime_str, lmtime_len); 108 run_http_response_send_file(r, __LINE__, 200, 109 "if-modified-since older than st_mtime"); 110 test_mod_staticfile_reset(r); 111 112 lmtime_len = http_date_time_to_str(lmtime_str,sizeof(lmtime_str),lmtime); 113 http_header_request_set(r, HTTP_HEADER_IF_MODIFIED_SINCE, 114 CONST_STR_LEN("If-Modified-Since"), 115 lmtime_str, lmtime_len); 116 run_http_response_send_file(r, __LINE__, 304, 117 "if-modified-since matches st_mtime"); 118 test_mod_staticfile_reset(r); 119 120 lmtime_len = http_date_time_to_str(lmtime_str,sizeof(lmtime_str),lmtime+1); 121 http_header_request_set(r, HTTP_HEADER_IF_MODIFIED_SINCE, 122 CONST_STR_LEN("If-Modified-Since"), 123 lmtime_str, lmtime_len); 124 run_http_response_send_file(r, __LINE__, 304, 125 "if-modified-since newer than st_mtime"); 126 test_mod_staticfile_reset(r); 127 128 #ifdef __COVERITY__ /* Coverity misses that this is set a few lines above */ 129 force_assert(http_header_request_get(r, HTTP_HEADER_IF_MODIFIED_SINCE, 130 CONST_STR_LEN("If-Modified-Since"))); 131 #endif 132 buffer_append_string_len( 133 http_header_request_get(r, HTTP_HEADER_IF_MODIFIED_SINCE, 134 CONST_STR_LEN("If-Modified-Since")), 135 CONST_STR_LEN("; foo")); 136 run_http_response_send_file(r, __LINE__, 200, 137 "if-modified-since newer but overload (invalid)"); 138 test_mod_staticfile_reset(r); 139 140 http_header_request_unset(r, HTTP_HEADER_IF_MODIFIED_SINCE, 141 CONST_STR_LEN("If-Modified-Since")); 142 143 buffer *etag = buffer_init(); 144 http_header_request_set(r, HTTP_HEADER_IF_NONE_MATCH, 145 CONST_STR_LEN("If-None-Match"), 146 CONST_STR_LEN("foo")); 147 run_http_response_send_file(r, __LINE__, 200, 148 "if-none-match (etag mismatch)"); 149 vb = http_header_response_get(r, HTTP_HEADER_ETAG, 150 CONST_STR_LEN("ETag")); 151 assert(vb); 152 buffer_copy_buffer(etag, vb); 153 test_mod_staticfile_reset(r); 154 155 http_header_request_set(r, HTTP_HEADER_IF_NONE_MATCH, 156 CONST_STR_LEN("If-None-Match"), 157 CONST_BUF_LEN(etag)); 158 run_http_response_send_file(r, __LINE__, 304, 159 "if-none-match (etag match)"); 160 test_mod_staticfile_reset(r); 161 162 r->conf.etag_flags = 0; 163 http_header_request_set(r, HTTP_HEADER_IF_NONE_MATCH, 164 CONST_STR_LEN("If-None-Match"), 165 CONST_BUF_LEN(etag)); 166 run_http_response_send_file(r, __LINE__, 200, 167 "if-none-match (etag would match, but etags disabled in config)"); 168 test_mod_staticfile_reset(r); 169 r->conf.etag_flags = ETAG_USE_INODE | ETAG_USE_MTIME | ETAG_USE_SIZE; 170 171 http_header_request_set(r, HTTP_HEADER_IF_NONE_MATCH, 172 CONST_STR_LEN("If-None-Match"), 173 CONST_BUF_LEN(etag)); 174 lmtime_len = http_date_time_to_str(lmtime_str,sizeof(lmtime_str), 175 lmtime ? lmtime-1 : lmtime); 176 http_header_request_set(r, HTTP_HEADER_IF_MODIFIED_SINCE, 177 CONST_STR_LEN("If-Modified-Since"), 178 lmtime_str, lmtime_len); 179 run_http_response_send_file(r, __LINE__, 304, 180 "if-none-match (etag match), " 181 "if-modified-since (old) (should be ignored)"); 182 test_mod_staticfile_reset(r); 183 184 http_header_request_set(r, HTTP_HEADER_IF_NONE_MATCH, 185 CONST_STR_LEN("If-None-Match"), 186 CONST_BUF_LEN(etag)); 187 lmtime_len = http_date_time_to_str(lmtime_str,sizeof(lmtime_str),lmtime); 188 http_header_request_set(r, HTTP_HEADER_IF_MODIFIED_SINCE, 189 CONST_STR_LEN("If-Modified-Since"), 190 lmtime_str, lmtime_len); 191 run_http_response_send_file(r, __LINE__, 304, 192 "if-none-match (etag match), " 193 "if-modified-since (now) (should be ignored)"); 194 test_mod_staticfile_reset(r); 195 196 http_header_request_set(r, HTTP_HEADER_IF_NONE_MATCH, 197 CONST_STR_LEN("If-None-Match"), 198 CONST_BUF_LEN(etag)); 199 http_header_request_set(r, HTTP_HEADER_IF_MODIFIED_SINCE, 200 CONST_STR_LEN("If-Modified-Since"), 201 CONST_STR_LEN("Sun, 01 Jan 1970 00:00:01 GMT foo")); 202 run_http_response_send_file(r, __LINE__, 304, 203 "if-none-match (etag match), " 204 "if-modified-since (overlong; invalid) (should be ignored)"); 205 test_mod_staticfile_reset(r); 206 207 http_header_request_set(r, HTTP_HEADER_IF_NONE_MATCH, 208 CONST_STR_LEN("If-None-Match"), 209 CONST_STR_LEN("foo")); 210 lmtime_len = http_date_time_to_str(lmtime_str,sizeof(lmtime_str), 211 lmtime ? lmtime-1 : lmtime); 212 http_header_request_set(r, HTTP_HEADER_IF_MODIFIED_SINCE, 213 CONST_STR_LEN("If-Modified-Since"), 214 lmtime_str, lmtime_len); 215 run_http_response_send_file(r, __LINE__, 200, 216 "if-none-match (etag mismatch), " 217 "if-modified-since (old) (should be ignored)"); 218 test_mod_staticfile_reset(r); 219 220 http_header_request_set(r, HTTP_HEADER_IF_NONE_MATCH, 221 CONST_STR_LEN("If-None-Match"), 222 CONST_STR_LEN("foo")); 223 lmtime_len = http_date_time_to_str(lmtime_str,sizeof(lmtime_str),lmtime); 224 http_header_request_set(r, HTTP_HEADER_IF_MODIFIED_SINCE, 225 CONST_STR_LEN("If-Modified-Since"), 226 lmtime_str, lmtime_len); 227 run_http_response_send_file(r, __LINE__, 200, 228 "if-none-match (etag mismatch), " 229 "if-modified-since (now) (should be ignored)"); 230 test_mod_staticfile_reset(r); 231 232 http_header_request_unset(r, HTTP_HEADER_IF_MODIFIED_SINCE, 233 CONST_STR_LEN("If-Modified-Since")); 234 235 http_header_request_set(r, HTTP_HEADER_IF_NONE_MATCH, 236 CONST_STR_LEN("If-None-Match"), 237 etag->ptr, buffer_clen(etag)-1); 238 run_http_response_send_file(r, __LINE__, 200, 239 "if-none-match (etag invalid; mismatched quotes)"); 240 test_mod_staticfile_reset(r); 241 242 http_header_request_set(r, HTTP_HEADER_IF_NONE_MATCH, 243 CONST_STR_LEN("If-None-Match"), 244 etag->ptr+1, buffer_clen(etag)-2); 245 run_http_response_send_file(r, __LINE__, 200, 246 "if-none-match (etag invalid; no quotes)"); 247 test_mod_staticfile_reset(r); 248 249 http_header_request_set(r, HTTP_HEADER_IF_NONE_MATCH, 250 CONST_STR_LEN("If-None-Match"), 251 CONST_STR_LEN("*")); 252 run_http_response_send_file(r, __LINE__, 304, 253 "if-none-match (etag * (unquoted) matches any ETag)"); 254 test_mod_staticfile_reset(r); 255 256 http_header_request_set(r, HTTP_HEADER_IF_NONE_MATCH, 257 CONST_STR_LEN("If-None-Match"), 258 CONST_STR_LEN("\"*\"")); 259 run_http_response_send_file(r, __LINE__, 200, 260 "if-none-match (etag \"*\" (quoted) is a regular ETag)"); 261 test_mod_staticfile_reset(r); 262 263 buffer * const rqst_etag = 264 http_header_request_set_ptr(r, HTTP_HEADER_IF_NONE_MATCH, 265 CONST_STR_LEN("If-None-Match")); 266 267 buffer_copy_string_len(rqst_etag, CONST_STR_LEN("W/")); 268 buffer_append_buffer(rqst_etag, etag); 269 run_http_response_send_file(r, __LINE__, 304, 270 "if-none-match (weak etag) matches like ETag for GET and HEAD)"); 271 test_mod_staticfile_reset(r); 272 273 /*(200 expected here instead of 206 since Range is handled later)*/ 274 http_header_request_set(r, HTTP_HEADER_RANGE, 275 CONST_STR_LEN("Range"), 276 CONST_STR_LEN("bytes=0-0")); 277 run_http_response_send_file(r, __LINE__, 200, 278 "if-none-match (weak etag) does not match for Range request)"); 279 test_mod_staticfile_reset(r); 280 http_header_request_unset(r, HTTP_HEADER_RANGE, CONST_STR_LEN("Range")); 281 282 buffer_copy_string_len(rqst_etag, CONST_STR_LEN("W/\"12345\"")); 283 run_http_response_send_file(r, __LINE__, 200, 284 "if-none-match (weak etag no match)"); 285 test_mod_staticfile_reset(r); 286 287 buffer_append_string_len(rqst_etag, CONST_STR_LEN(", ")); 288 buffer_append_buffer(rqst_etag, etag); 289 run_http_response_send_file(r, __LINE__, 304, 290 "if-none-match (etag list, second etag matches)"); 291 test_mod_staticfile_reset(r); 292 293 buffer_append_string_len(rqst_etag, CONST_STR_LEN(", W/")); 294 buffer_append_buffer(rqst_etag, etag); 295 run_http_response_send_file(r, __LINE__, 304, 296 "if-none-match (etag list, second etag matches weakly)"); 297 test_mod_staticfile_reset(r); 298 299 buffer_copy_string_len(rqst_etag, CONST_STR_LEN("\"12345\",, ,, , ")); 300 buffer_append_buffer(rqst_etag, etag); 301 run_http_response_send_file(r, __LINE__, 304, 302 "if-none-match (etag list non-normalized, ending with etag match)"); 303 test_mod_staticfile_reset(r); 304 305 buffer_copy_string_len(rqst_etag, CONST_STR_LEN("\"1234\", ")); 306 buffer_append_buffer(rqst_etag, etag); 307 buffer_append_string_len(rqst_etag, CONST_STR_LEN(", \"brokentrailing")); 308 run_http_response_send_file(r, __LINE__, 304, 309 "if-none-match (etag list with etag match then invalid trailing data)"); 310 test_mod_staticfile_reset(r); 311 312 http_header_request_unset(r, HTTP_HEADER_IF_NONE_MATCH, 313 CONST_STR_LEN("If-None-Match")); 314 315 buffer_free(etag); 316 } 317 318 __attribute_noinline__ 319 static void 320 run_mod_staticfile_process (request_st * const r, plugin_config * const pconf, int line, int status, const char *desc) 321 { 322 handler_t rc = mod_staticfile_process(r, pconf); 323 if (r->http_status != status 324 || rc != (status ? HANDLER_FINISHED : HANDLER_GO_ON)) { 325 fprintf(stderr, 326 "%s.%d: %s() failed: expected '%d', got '%d' for test %s\n", 327 __FILE__, line, "mod_staticfile_process", status, 328 r->http_status, desc); 329 fflush(stderr); 330 abort(); 331 } 332 } 333 334 static void 335 test_mod_staticfile_process (request_st * const r, plugin_config * const pconf) 336 { 337 test_mod_staticfile_reset(r); 338 339 pconf->disable_pathinfo = 0; 340 buffer_copy_string_len(&r->pathinfo, CONST_STR_LEN("/pathinfo")); 341 run_mod_staticfile_process(r, pconf, __LINE__, 200, 342 "pathinfo allowed and present"); 343 test_mod_staticfile_reset(r); 344 pconf->disable_pathinfo = 1; 345 run_mod_staticfile_process(r, pconf, __LINE__, 0, 346 "pathinfo denied and present"); 347 test_mod_staticfile_reset(r); 348 buffer_clear(&r->pathinfo); 349 run_mod_staticfile_process(r, pconf, __LINE__, 200, 350 "pathinfo denied and not present"); 351 test_mod_staticfile_reset(r); 352 pconf->disable_pathinfo = 0; 353 354 array * const a = array_init(1); 355 array_insert_value(a, CONST_STR_LEN(".exe")); 356 pconf->exclude_ext = a; 357 run_mod_staticfile_process(r, pconf, __LINE__, 200, 358 "extension disallowed (no match)"); 359 test_mod_staticfile_reset(r); 360 buffer_append_string_len(&r->physical.path, CONST_STR_LEN(".exe")); 361 run_mod_staticfile_process(r, pconf, __LINE__, 0, 362 "extension disallowed (match)"); 363 test_mod_staticfile_reset(r); 364 pconf->exclude_ext = NULL; 365 array_free(a); 366 } 367 368 #include <unistd.h> /* unlink() */ 369 370 void test_mod_staticfile (void); 371 void test_mod_staticfile (void) 372 { 373 char fn[] = "/tmp/lighttpd_mod_staticfile.XXXXXX"; 374 #ifdef __COVERITY__ 375 /* POSIX-2008 requires mkstemp create file with 0600 perms */ 376 umask(0600); 377 #endif 378 /* coverity[secure_temp : FALSE] */ 379 int fd = mkstemp(fn); 380 if (fd < 0) { 381 perror("mkstemp()"); 382 exit(1); 383 } 384 struct stat st; 385 if (0 != fstat(fd, &st)) { 386 perror("fstat()"); 387 exit(1); 388 } 389 390 plugin_data * const p = mod_staticfile_init(); 391 assert(NULL != p); 392 p->conf.etags_used = 1; 393 394 request_st r; 395 396 memset(&r, 0, sizeof(request_st)); 397 r.http_method = HTTP_METHOD_GET; 398 r.http_version = HTTP_VERSION_1_1; 399 r.tmp_buf = buffer_init(); 400 r.conf.errh = fdlog_init(NULL, -1, FDLOG_FD); 401 r.conf.errh->fd = -1; /* (disable) */ 402 r.conf.follow_symlink = 1; 403 buffer_copy_string_len(&r.uri.path, CONST_STR_LEN("/")); 404 array * const mimetypes = array_init(1); 405 r.conf.mimetypes = mimetypes; 406 array_set_key_value(mimetypes, fn+sizeof(fn)-8, 7, 407 CONST_STR_LEN("text/plain")); 408 409 strftime_cache_reset(); 410 411 buffer_copy_string_len(&r.physical.path, fn, sizeof(fn)-1); 412 test_http_response_send_file(&r, st.st_mtime); 413 414 r.rqst_htags = 0; 415 array_reset_data_strings(&r.rqst_headers); 416 417 buffer_copy_string_len(&r.physical.path, fn, sizeof(fn)-1); 418 test_mod_staticfile_process(&r, &p->conf); 419 420 array_free(mimetypes); 421 fdlog_free(r.conf.errh); 422 buffer_free(r.tmp_buf); 423 chunkqueue_reset(&r.write_queue); 424 425 free(r.uri.path.ptr); 426 free(r.physical.path.ptr); 427 free(r.physical.rel_path.ptr); 428 free(r.physical.doc_root.ptr); 429 430 free(p); 431 stat_cache_free(); 432 unlink(fn); 433 } 434