xref: /lighttpd1.4/src/t/test_mod_staticfile.c (revision f3052d6a)
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