9 #include "stat_cache.h"
11 #include "http_chunk.h"
20 * this is a staticfile for a lighttpd plugin
26 /* plugin config for all request/connections */
30 unsigned short etags_used
;
31 unsigned short disable_pathinfo
;
39 plugin_config
**config_storage
;
44 /* init the plugin data */
45 INIT_FUNC(mod_staticfile_init
) {
48 p
= calloc(1, sizeof(*p
));
50 p
->range_buf
= buffer_init();
55 /* detroy the plugin data */
56 FREE_FUNC(mod_staticfile_free
) {
61 if (!p
) return HANDLER_GO_ON
;
63 if (p
->config_storage
) {
65 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
66 plugin_config
*s
= p
->config_storage
[i
];
68 if (NULL
== s
) continue;
70 array_free(s
->exclude_ext
);
74 free(p
->config_storage
);
76 buffer_free(p
->range_buf
);
83 /* handle plugin config and check values */
85 SETDEFAULTS_FUNC(mod_staticfile_set_defaults
) {
89 config_values_t cv
[] = {
90 { "static-file.exclude-extensions", NULL
, T_CONFIG_ARRAY
, T_CONFIG_SCOPE_CONNECTION
}, /* 0 */
91 { "static-file.etags", NULL
, T_CONFIG_BOOLEAN
, T_CONFIG_SCOPE_CONNECTION
}, /* 1 */
92 { "static-file.disable-pathinfo", NULL
, T_CONFIG_BOOLEAN
, T_CONFIG_SCOPE_CONNECTION
}, /* 2 */
93 { NULL
, NULL
, T_CONFIG_UNSET
, T_CONFIG_SCOPE_UNSET
}
96 if (!p
) return HANDLER_ERROR
;
98 p
->config_storage
= calloc(1, srv
->config_context
->used
* sizeof(plugin_config
*));
100 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
101 data_config
const* config
= (data_config
const*)srv
->config_context
->data
[i
];
104 s
= calloc(1, sizeof(plugin_config
));
105 s
->exclude_ext
= array_init();
107 s
->disable_pathinfo
= 0;
109 cv
[0].destination
= s
->exclude_ext
;
110 cv
[1].destination
= &(s
->etags_used
);
111 cv
[2].destination
= &(s
->disable_pathinfo
);
113 p
->config_storage
[i
] = s
;
115 if (0 != config_insert_values_global(srv
, config
->value
, cv
, i
== 0 ? T_CONFIG_SCOPE_SERVER
: T_CONFIG_SCOPE_CONNECTION
)) {
116 return HANDLER_ERROR
;
120 return HANDLER_GO_ON
;
125 static int mod_staticfile_patch_connection(server
*srv
, connection
*con
, plugin_data
*p
) {
127 plugin_config
*s
= p
->config_storage
[0];
131 PATCH(disable_pathinfo
);
133 /* skip the first, the global context */
134 for (i
= 1; i
< srv
->config_context
->used
; i
++) {
135 data_config
*dc
= (data_config
*)srv
->config_context
->data
[i
];
136 s
= p
->config_storage
[i
];
138 /* condition didn't match */
139 if (!config_check_cond(srv
, con
, dc
)) continue;
142 for (j
= 0; j
< dc
->value
->used
; j
++) {
143 data_unset
*du
= dc
->value
->data
[j
];
145 if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("static-file.exclude-extensions"))) {
147 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("static-file.etags"))) {
149 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("static-file.disable-pathinfo"))) {
150 PATCH(disable_pathinfo
);
159 static int http_response_parse_range(server
*srv
, connection
*con
, plugin_data
*p
) {
163 const char *s
, *minus
;
164 char *boundary
= "fkj49sn38dcn3";
166 stat_cache_entry
*sce
= NULL
;
167 buffer
*content_type
= NULL
;
169 if (HANDLER_ERROR
== stat_cache_get_entry(srv
, con
, con
->physical
.path
, &sce
)) {
174 end
= sce
->st
.st_size
- 1;
176 con
->response
.content_length
= 0;
178 if (NULL
!= (ds
= (data_string
*)array_get_element(con
->response
.headers
, "Content-Type"))) {
179 content_type
= ds
->value
;
182 for (s
= con
->request
.http_range
, error
= 0;
183 !error
&& *s
&& NULL
!= (minus
= strchr(s
, '-')); ) {
190 le
= strtoll(s
, &err
, 10);
193 /* RFC 2616 - 14.35.1 */
195 con
->http_status
= 416;
197 } else if (*err
== '\0') {
201 end
= sce
->st
.st_size
- 1;
202 start
= sce
->st
.st_size
+ le
;
203 } else if (*err
== ',') {
207 end
= sce
->st
.st_size
- 1;
208 start
= sce
->st
.st_size
+ le
;
213 } else if (*(minus
+1) == '\0' || *(minus
+1) == ',') {
216 la
= strtoll(s
, &err
, 10);
221 if (*(err
+ 1) == '\0') {
224 end
= sce
->st
.st_size
- 1;
227 } else if (*(err
+ 1) == ',') {
231 end
= sce
->st
.st_size
- 1;
243 la
= strtoll(s
, &err
, 10);
246 le
= strtoll(minus
+1, &err
, 10);
248 /* RFC 2616 - 14.35.1 */
259 } else if (*err
== ',') {
278 if (start
< 0) start
= 0;
280 /* RFC 2616 - 14.35.1 */
281 if (end
> sce
->st
.st_size
- 1) end
= sce
->st
.st_size
- 1;
283 if (start
> sce
->st
.st_size
- 1) {
286 con
->http_status
= 416;
292 /* write boundary-header */
293 buffer
*b
= buffer_init();
295 buffer_copy_string_len(b
, CONST_STR_LEN("\r\n--"));
296 buffer_append_string(b
, boundary
);
298 /* write Content-Range */
299 buffer_append_string_len(b
, CONST_STR_LEN("\r\nContent-Range: bytes "));
300 buffer_append_int(b
, start
);
301 buffer_append_string_len(b
, CONST_STR_LEN("-"));
302 buffer_append_int(b
, end
);
303 buffer_append_string_len(b
, CONST_STR_LEN("/"));
304 buffer_append_int(b
, sce
->st
.st_size
);
306 buffer_append_string_len(b
, CONST_STR_LEN("\r\nContent-Type: "));
307 buffer_append_string_buffer(b
, content_type
);
309 /* write END-OF-HEADER */
310 buffer_append_string_len(b
, CONST_STR_LEN("\r\n\r\n"));
312 con
->response
.content_length
+= buffer_string_length(b
);
313 chunkqueue_append_buffer(con
->write_queue
, b
);
317 chunkqueue_append_file(con
->write_queue
, con
->physical
.path
, start
, end
- start
+ 1);
318 con
->response
.content_length
+= end
- start
+ 1;
322 /* something went wrong */
323 if (error
) return -1;
326 /* add boundary end */
327 buffer
*b
= buffer_init();
329 buffer_copy_string_len(b
, "\r\n--", 4);
330 buffer_append_string(b
, boundary
);
331 buffer_append_string_len(b
, "--\r\n", 4);
333 con
->response
.content_length
+= buffer_string_length(b
);
334 chunkqueue_append_buffer(con
->write_queue
, b
);
337 /* set header-fields */
339 buffer_copy_string_len(p
->range_buf
, CONST_STR_LEN("multipart/byteranges; boundary="));
340 buffer_append_string(p
->range_buf
, boundary
);
342 /* overwrite content-type */
343 response_header_overwrite(srv
, con
, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p
->range_buf
));
345 /* add Content-Range-header */
347 buffer_copy_string_len(p
->range_buf
, CONST_STR_LEN("bytes "));
348 buffer_append_int(p
->range_buf
, start
);
349 buffer_append_string_len(p
->range_buf
, CONST_STR_LEN("-"));
350 buffer_append_int(p
->range_buf
, end
);
351 buffer_append_string_len(p
->range_buf
, CONST_STR_LEN("/"));
352 buffer_append_int(p
->range_buf
, sce
->st
.st_size
);
354 response_header_insert(srv
, con
, CONST_STR_LEN("Content-Range"), CONST_BUF_LEN(p
->range_buf
));
357 /* ok, the file is set-up */
361 URIHANDLER_FUNC(mod_staticfile_subrequest
) {
362 plugin_data
*p
= p_d
;
364 stat_cache_entry
*sce
= NULL
;
365 buffer
*mtime
= NULL
;
367 int allow_caching
= 1;
369 /* someone else has done a decision for us */
370 if (con
->http_status
!= 0) return HANDLER_GO_ON
;
371 if (buffer_is_empty(con
->uri
.path
)) return HANDLER_GO_ON
;
372 if (buffer_is_empty(con
->physical
.path
)) return HANDLER_GO_ON
;
374 /* someone else has handled this request */
375 if (con
->mode
!= DIRECT
) return HANDLER_GO_ON
;
377 /* we only handle GET, POST and HEAD */
378 switch(con
->request
.http_method
) {
379 case HTTP_METHOD_GET
:
380 case HTTP_METHOD_POST
:
381 case HTTP_METHOD_HEAD
:
384 return HANDLER_GO_ON
;
387 mod_staticfile_patch_connection(srv
, con
, p
);
389 if (p
->conf
.disable_pathinfo
&& !buffer_string_is_empty(con
->request
.pathinfo
)) {
390 if (con
->conf
.log_request_handling
) {
391 log_error_write(srv
, __FILE__
, __LINE__
, "s", "-- NOT handling file as static file, pathinfo forbidden");
393 return HANDLER_GO_ON
;
396 /* ignore certain extensions */
397 for (k
= 0; k
< p
->conf
.exclude_ext
->used
; k
++) {
398 ds
= (data_string
*)p
->conf
.exclude_ext
->data
[k
];
400 if (buffer_is_empty(ds
->value
)) continue;
402 if (buffer_is_equal_right_len(con
->physical
.path
, ds
->value
, buffer_string_length(ds
->value
))) {
403 if (con
->conf
.log_request_handling
) {
404 log_error_write(srv
, __FILE__
, __LINE__
, "s", "-- NOT handling file as static file, extension forbidden");
406 return HANDLER_GO_ON
;
411 if (con
->conf
.log_request_handling
) {
412 log_error_write(srv
, __FILE__
, __LINE__
, "s", "-- handling file as static file");
415 if (HANDLER_ERROR
== stat_cache_get_entry(srv
, con
, con
->physical
.path
, &sce
)) {
416 con
->http_status
= 403;
418 log_error_write(srv
, __FILE__
, __LINE__
, "sbsb",
419 "not a regular file:", con
->uri
.path
,
420 "->", con
->physical
.path
);
422 return HANDLER_FINISHED
;
425 /* we only handline regular files */
427 if ((sce
->is_symlink
== 1) && !con
->conf
.follow_symlink
) {
428 con
->http_status
= 403;
430 if (con
->conf
.log_request_handling
) {
431 log_error_write(srv
, __FILE__
, __LINE__
, "s", "-- access denied due symlink restriction");
432 log_error_write(srv
, __FILE__
, __LINE__
, "sb", "Path :", con
->physical
.path
);
435 buffer_reset(con
->physical
.path
);
436 return HANDLER_FINISHED
;
439 if (!S_ISREG(sce
->st
.st_mode
)) {
440 con
->http_status
= 404;
442 if (con
->conf
.log_file_not_found
) {
443 log_error_write(srv
, __FILE__
, __LINE__
, "sbsb",
444 "not a regular file:", con
->uri
.path
,
448 return HANDLER_FINISHED
;
451 /* mod_compress might set several data directly, don't overwrite them */
453 /* set response content-type, if not set already */
455 if (NULL
== array_get_element(con
->response
.headers
, "Content-Type")) {
456 if (buffer_string_is_empty(sce
->content_type
)) {
457 /* we are setting application/octet-stream, but also announce that
458 * this header field might change in the seconds few requests
460 * This should fix the aggressive caching of FF and the script download
461 * seen by the first installations
463 response_header_overwrite(srv
, con
, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("application/octet-stream"));
467 response_header_overwrite(srv
, con
, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(sce
->content_type
));
471 if (con
->conf
.range_requests
) {
472 response_header_overwrite(srv
, con
, CONST_STR_LEN("Accept-Ranges"), CONST_STR_LEN("bytes"));
476 if (p
->conf
.etags_used
&& con
->etag_flags
!= 0 && !buffer_string_is_empty(sce
->etag
)) {
477 if (NULL
== array_get_element(con
->response
.headers
, "ETag")) {
479 etag_mutate(con
->physical
.etag
, sce
->etag
);
481 response_header_overwrite(srv
, con
, CONST_STR_LEN("ETag"), CONST_BUF_LEN(con
->physical
.etag
));
486 if (NULL
== (ds
= (data_string
*)array_get_element(con
->response
.headers
, "Last-Modified"))) {
487 mtime
= strftime_cache_get(srv
, sce
->st
.st_mtime
);
488 response_header_overwrite(srv
, con
, CONST_STR_LEN("Last-Modified"), CONST_BUF_LEN(mtime
));
493 if (HANDLER_FINISHED
== http_response_handle_cachable(srv
, con
, mtime
)) {
494 return HANDLER_FINISHED
;
498 if (con
->request
.http_range
&& con
->conf
.range_requests
) {
499 int do_range_request
= 1;
500 /* check if we have a conditional GET */
502 if (NULL
!= (ds
= (data_string
*)array_get_element(con
->request
.headers
, "If-Range"))) {
503 /* if the value is the same as our ETag, we do a Range-request,
504 * otherwise a full 200 */
506 if (ds
->value
->ptr
[0] == '"') {
508 * client wants a ETag
510 if (!con
->physical
.etag
) {
511 do_range_request
= 0;
512 } else if (!buffer_is_equal(ds
->value
, con
->physical
.etag
)) {
513 do_range_request
= 0;
517 * we don't have a Last-Modified and can match the If-Range:
521 do_range_request
= 0;
522 } else if (!buffer_is_equal(ds
->value
, mtime
)) {
523 do_range_request
= 0;
527 if (do_range_request
) {
528 /* content prepared, I'm done */
529 con
->file_finished
= 1;
531 if (0 == http_response_parse_range(srv
, con
, p
)) {
532 con
->http_status
= 206;
534 return HANDLER_FINISHED
;
538 /* if we are still here, prepare body */
540 /* we add it here for all requests
541 * the HEAD request will drop it afterwards again
543 http_chunk_append_file(srv
, con
, con
->physical
.path
, 0, sce
->st
.st_size
);
545 con
->http_status
= 200;
546 con
->file_finished
= 1;
548 return HANDLER_FINISHED
;
551 /* this function is called at dlopen() time and inits the callbacks */
553 int mod_staticfile_plugin_init(plugin
*p
);
554 int mod_staticfile_plugin_init(plugin
*p
) {
555 p
->version
= LIGHTTPD_VERSION_ID
;
556 p
->name
= buffer_init_string("staticfile");
558 p
->init
= mod_staticfile_init
;
559 p
->handle_subrequest_start
= mod_staticfile_subrequest
;
560 p
->set_defaults
= mod_staticfile_set_defaults
;
561 p
->cleanup
= mod_staticfile_free
;