7 #include "stat_cache.h"
9 #include "http_chunk.h"
18 * this is a staticfile for a lighttpd plugin
24 /* plugin config for all request/connections */
28 unsigned short etags_used
;
29 unsigned short disable_pathinfo
;
37 plugin_config
**config_storage
;
42 /* init the plugin data */
43 INIT_FUNC(mod_staticfile_init
) {
46 p
= calloc(1, sizeof(*p
));
48 p
->range_buf
= buffer_init();
53 /* detroy the plugin data */
54 FREE_FUNC(mod_staticfile_free
) {
59 if (!p
) return HANDLER_GO_ON
;
61 if (p
->config_storage
) {
63 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
64 plugin_config
*s
= p
->config_storage
[i
];
66 if (NULL
== s
) continue;
68 array_free(s
->exclude_ext
);
72 free(p
->config_storage
);
74 buffer_free(p
->range_buf
);
81 /* handle plugin config and check values */
83 SETDEFAULTS_FUNC(mod_staticfile_set_defaults
) {
87 config_values_t cv
[] = {
88 { "static-file.exclude-extensions", NULL
, T_CONFIG_ARRAY
, T_CONFIG_SCOPE_CONNECTION
}, /* 0 */
89 { "static-file.etags", NULL
, T_CONFIG_BOOLEAN
, T_CONFIG_SCOPE_CONNECTION
}, /* 1 */
90 { "static-file.disable-pathinfo", NULL
, T_CONFIG_BOOLEAN
, T_CONFIG_SCOPE_CONNECTION
}, /* 2 */
91 { NULL
, NULL
, T_CONFIG_UNSET
, T_CONFIG_SCOPE_UNSET
}
94 if (!p
) return HANDLER_ERROR
;
96 p
->config_storage
= calloc(1, srv
->config_context
->used
* sizeof(plugin_config
*));
98 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
101 s
= calloc(1, sizeof(plugin_config
));
102 s
->exclude_ext
= array_init();
104 s
->disable_pathinfo
= 0;
106 cv
[0].destination
= s
->exclude_ext
;
107 cv
[1].destination
= &(s
->etags_used
);
108 cv
[2].destination
= &(s
->disable_pathinfo
);
110 p
->config_storage
[i
] = s
;
112 if (0 != config_insert_values_global(srv
, ((data_config
*)srv
->config_context
->data
[i
])->value
, cv
)) {
113 return HANDLER_ERROR
;
117 return HANDLER_GO_ON
;
122 static int mod_staticfile_patch_connection(server
*srv
, connection
*con
, plugin_data
*p
) {
124 plugin_config
*s
= p
->config_storage
[0];
128 PATCH(disable_pathinfo
);
130 /* skip the first, the global context */
131 for (i
= 1; i
< srv
->config_context
->used
; i
++) {
132 data_config
*dc
= (data_config
*)srv
->config_context
->data
[i
];
133 s
= p
->config_storage
[i
];
135 /* condition didn't match */
136 if (!config_check_cond(srv
, con
, dc
)) continue;
139 for (j
= 0; j
< dc
->value
->used
; j
++) {
140 data_unset
*du
= dc
->value
->data
[j
];
142 if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("static-file.exclude-extensions"))) {
144 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("static-file.etags"))) {
146 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("static-file.disable-pathinfo"))) {
147 PATCH(disable_pathinfo
);
156 static int http_response_parse_range(server
*srv
, connection
*con
, plugin_data
*p
) {
160 const char *s
, *minus
;
161 char *boundary
= "fkj49sn38dcn3";
163 stat_cache_entry
*sce
= NULL
;
164 buffer
*content_type
= NULL
;
166 if (HANDLER_ERROR
== stat_cache_get_entry(srv
, con
, con
->physical
.path
, &sce
)) {
171 end
= sce
->st
.st_size
- 1;
173 con
->response
.content_length
= 0;
175 if (NULL
!= (ds
= (data_string
*)array_get_element(con
->response
.headers
, "Content-Type"))) {
176 content_type
= ds
->value
;
179 for (s
= con
->request
.http_range
, error
= 0;
180 !error
&& *s
&& NULL
!= (minus
= strchr(s
, '-')); ) {
187 le
= strtoll(s
, &err
, 10);
190 /* RFC 2616 - 14.35.1 */
192 con
->http_status
= 416;
194 } else if (*err
== '\0') {
198 end
= sce
->st
.st_size
- 1;
199 start
= sce
->st
.st_size
+ le
;
200 } else if (*err
== ',') {
204 end
= sce
->st
.st_size
- 1;
205 start
= sce
->st
.st_size
+ le
;
210 } else if (*(minus
+1) == '\0' || *(minus
+1) == ',') {
213 la
= strtoll(s
, &err
, 10);
218 if (*(err
+ 1) == '\0') {
221 end
= sce
->st
.st_size
- 1;
224 } else if (*(err
+ 1) == ',') {
228 end
= sce
->st
.st_size
- 1;
240 la
= strtoll(s
, &err
, 10);
243 le
= strtoll(minus
+1, &err
, 10);
245 /* RFC 2616 - 14.35.1 */
256 } else if (*err
== ',') {
275 if (start
< 0) start
= 0;
277 /* RFC 2616 - 14.35.1 */
278 if (end
> sce
->st
.st_size
- 1) end
= sce
->st
.st_size
- 1;
280 if (start
> sce
->st
.st_size
- 1) {
283 con
->http_status
= 416;
289 /* write boundary-header */
290 buffer
*b
= buffer_init();
292 buffer_copy_string_len(b
, CONST_STR_LEN("\r\n--"));
293 buffer_append_string(b
, boundary
);
295 /* write Content-Range */
296 buffer_append_string_len(b
, CONST_STR_LEN("\r\nContent-Range: bytes "));
297 buffer_append_int(b
, start
);
298 buffer_append_string_len(b
, CONST_STR_LEN("-"));
299 buffer_append_int(b
, end
);
300 buffer_append_string_len(b
, CONST_STR_LEN("/"));
301 buffer_append_int(b
, sce
->st
.st_size
);
303 buffer_append_string_len(b
, CONST_STR_LEN("\r\nContent-Type: "));
304 buffer_append_string_buffer(b
, content_type
);
306 /* write END-OF-HEADER */
307 buffer_append_string_len(b
, CONST_STR_LEN("\r\n\r\n"));
309 con
->response
.content_length
+= buffer_string_length(b
);
310 chunkqueue_append_buffer(con
->write_queue
, b
);
314 chunkqueue_append_file(con
->write_queue
, con
->physical
.path
, start
, end
- start
+ 1);
315 con
->response
.content_length
+= end
- start
+ 1;
319 /* something went wrong */
320 if (error
) return -1;
323 /* add boundary end */
324 buffer
*b
= buffer_init();
326 buffer_copy_string_len(b
, "\r\n--", 4);
327 buffer_append_string(b
, boundary
);
328 buffer_append_string_len(b
, "--\r\n", 4);
330 con
->response
.content_length
+= buffer_string_length(b
);
331 chunkqueue_append_buffer(con
->write_queue
, b
);
334 /* set header-fields */
336 buffer_copy_string_len(p
->range_buf
, CONST_STR_LEN("multipart/byteranges; boundary="));
337 buffer_append_string(p
->range_buf
, boundary
);
339 /* overwrite content-type */
340 response_header_overwrite(srv
, con
, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p
->range_buf
));
342 /* add Content-Range-header */
344 buffer_copy_string_len(p
->range_buf
, CONST_STR_LEN("bytes "));
345 buffer_append_int(p
->range_buf
, start
);
346 buffer_append_string_len(p
->range_buf
, CONST_STR_LEN("-"));
347 buffer_append_int(p
->range_buf
, end
);
348 buffer_append_string_len(p
->range_buf
, CONST_STR_LEN("/"));
349 buffer_append_int(p
->range_buf
, sce
->st
.st_size
);
351 response_header_insert(srv
, con
, CONST_STR_LEN("Content-Range"), CONST_BUF_LEN(p
->range_buf
));
354 /* ok, the file is set-up */
358 URIHANDLER_FUNC(mod_staticfile_subrequest
) {
359 plugin_data
*p
= p_d
;
361 stat_cache_entry
*sce
= NULL
;
362 buffer
*mtime
= NULL
;
364 int allow_caching
= 1;
366 /* someone else has done a decision for us */
367 if (con
->http_status
!= 0) return HANDLER_GO_ON
;
368 if (buffer_is_empty(con
->uri
.path
)) return HANDLER_GO_ON
;
369 if (buffer_is_empty(con
->physical
.path
)) return HANDLER_GO_ON
;
371 /* someone else has handled this request */
372 if (con
->mode
!= DIRECT
) return HANDLER_GO_ON
;
374 /* we only handle GET, POST and HEAD */
375 switch(con
->request
.http_method
) {
376 case HTTP_METHOD_GET
:
377 case HTTP_METHOD_POST
:
378 case HTTP_METHOD_HEAD
:
381 return HANDLER_GO_ON
;
384 mod_staticfile_patch_connection(srv
, con
, p
);
386 if (p
->conf
.disable_pathinfo
&& !buffer_string_is_empty(con
->request
.pathinfo
)) {
387 if (con
->conf
.log_request_handling
) {
388 log_error_write(srv
, __FILE__
, __LINE__
, "s", "-- NOT handling file as static file, pathinfo forbidden");
390 return HANDLER_GO_ON
;
393 /* ignore certain extensions */
394 for (k
= 0; k
< p
->conf
.exclude_ext
->used
; k
++) {
395 ds
= (data_string
*)p
->conf
.exclude_ext
->data
[k
];
397 if (buffer_is_empty(ds
->value
)) continue;
399 if (buffer_is_equal_right_len(con
->physical
.path
, ds
->value
, buffer_string_length(ds
->value
))) {
400 if (con
->conf
.log_request_handling
) {
401 log_error_write(srv
, __FILE__
, __LINE__
, "s", "-- NOT handling file as static file, extension forbidden");
403 return HANDLER_GO_ON
;
408 if (con
->conf
.log_request_handling
) {
409 log_error_write(srv
, __FILE__
, __LINE__
, "s", "-- handling file as static file");
412 if (HANDLER_ERROR
== stat_cache_get_entry(srv
, con
, con
->physical
.path
, &sce
)) {
413 con
->http_status
= 403;
415 log_error_write(srv
, __FILE__
, __LINE__
, "sbsb",
416 "not a regular file:", con
->uri
.path
,
417 "->", con
->physical
.path
);
419 return HANDLER_FINISHED
;
422 /* we only handline regular files */
424 if ((sce
->is_symlink
== 1) && !con
->conf
.follow_symlink
) {
425 con
->http_status
= 403;
427 if (con
->conf
.log_request_handling
) {
428 log_error_write(srv
, __FILE__
, __LINE__
, "s", "-- access denied due symlink restriction");
429 log_error_write(srv
, __FILE__
, __LINE__
, "sb", "Path :", con
->physical
.path
);
432 buffer_reset(con
->physical
.path
);
433 return HANDLER_FINISHED
;
436 if (!S_ISREG(sce
->st
.st_mode
)) {
437 con
->http_status
= 404;
439 if (con
->conf
.log_file_not_found
) {
440 log_error_write(srv
, __FILE__
, __LINE__
, "sbsb",
441 "not a regular file:", con
->uri
.path
,
445 return HANDLER_FINISHED
;
448 /* mod_compress might set several data directly, don't overwrite them */
450 /* set response content-type, if not set already */
452 if (NULL
== array_get_element(con
->response
.headers
, "Content-Type")) {
453 if (buffer_string_is_empty(sce
->content_type
)) {
454 /* we are setting application/octet-stream, but also announce that
455 * this header field might change in the seconds few requests
457 * This should fix the aggressive caching of FF and the script download
458 * seen by the first installations
460 response_header_overwrite(srv
, con
, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("application/octet-stream"));
464 response_header_overwrite(srv
, con
, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(sce
->content_type
));
468 if (con
->conf
.range_requests
) {
469 response_header_overwrite(srv
, con
, CONST_STR_LEN("Accept-Ranges"), CONST_STR_LEN("bytes"));
473 if (p
->conf
.etags_used
&& con
->etag_flags
!= 0 && !buffer_string_is_empty(sce
->etag
)) {
474 if (NULL
== array_get_element(con
->response
.headers
, "ETag")) {
476 etag_mutate(con
->physical
.etag
, sce
->etag
);
478 response_header_overwrite(srv
, con
, CONST_STR_LEN("ETag"), CONST_BUF_LEN(con
->physical
.etag
));
483 if (NULL
== (ds
= (data_string
*)array_get_element(con
->response
.headers
, "Last-Modified"))) {
484 mtime
= strftime_cache_get(srv
, sce
->st
.st_mtime
);
485 response_header_overwrite(srv
, con
, CONST_STR_LEN("Last-Modified"), CONST_BUF_LEN(mtime
));
490 if (HANDLER_FINISHED
== http_response_handle_cachable(srv
, con
, mtime
)) {
491 return HANDLER_FINISHED
;
495 if (con
->request
.http_range
&& con
->conf
.range_requests
) {
496 int do_range_request
= 1;
497 /* check if we have a conditional GET */
499 if (NULL
!= (ds
= (data_string
*)array_get_element(con
->request
.headers
, "If-Range"))) {
500 /* if the value is the same as our ETag, we do a Range-request,
501 * otherwise a full 200 */
503 if (ds
->value
->ptr
[0] == '"') {
505 * client wants a ETag
507 if (!con
->physical
.etag
) {
508 do_range_request
= 0;
509 } else if (!buffer_is_equal(ds
->value
, con
->physical
.etag
)) {
510 do_range_request
= 0;
514 * we don't have a Last-Modified and can match the If-Range:
518 do_range_request
= 0;
519 } else if (!buffer_is_equal(ds
->value
, mtime
)) {
520 do_range_request
= 0;
524 if (do_range_request
) {
525 /* content prepared, I'm done */
526 con
->file_finished
= 1;
528 if (0 == http_response_parse_range(srv
, con
, p
)) {
529 con
->http_status
= 206;
531 return HANDLER_FINISHED
;
535 /* if we are still here, prepare body */
537 /* we add it here for all requests
538 * the HEAD request will drop it afterwards again
540 http_chunk_append_file(srv
, con
, con
->physical
.path
, 0, sce
->st
.st_size
);
542 con
->http_status
= 200;
543 con
->file_finished
= 1;
545 return HANDLER_FINISHED
;
548 /* this function is called at dlopen() time and inits the callbacks */
550 int mod_staticfile_plugin_init(plugin
*p
);
551 int mod_staticfile_plugin_init(plugin
*p
) {
552 p
->version
= LIGHTTPD_VERSION_ID
;
553 p
->name
= buffer_init_string("staticfile");
555 p
->init
= mod_staticfile_init
;
556 p
->handle_subrequest_start
= mod_staticfile_subrequest
;
557 p
->set_defaults
= mod_staticfile_set_defaults
;
558 p
->cleanup
= mod_staticfile_free
;