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__
test_mod_staticfile_reset(request_st * const r)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
run_http_response_send_file(request_st * const r,int line,int status,const char * desc)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
test_http_response_send_file(request_st * const r,time_t lmtime)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
run_mod_staticfile_process(request_st * const r,plugin_config * const pconf,int line,int status,const char * desc)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
test_mod_staticfile_process(request_st * const r,plugin_config * const pconf)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);
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.pathinfo.ptr);
427 free(r.physical.path.ptr);
428 free(r.physical.rel_path.ptr);
429 free(r.physical.doc_root.ptr);
430 array_free_data(&r.rqst_headers);
431 array_free_data(&r.resp_headers);
432
433 free(p);
434 stat_cache_free();
435 unlink(fn);
436 }
437