diff --git a/src/http-header-glue.c b/src/http-header-glue.c index 5cbb8097..15ea8291 100644 --- a/src/http-header-glue.c +++ b/src/http-header-glue.c @@ -5,7 +5,9 @@ #include "buffer.h" #include "log.h" #include "etag.h" +#include "http_chunk.h" #include "response.h" +#include "stat_cache.h" #include #include @@ -323,3 +325,343 @@ int http_response_handle_cachable(server *srv, connection *con, buffer *mtime) { return HANDLER_GO_ON; } + + +static int http_response_parse_range(server *srv, connection *con, buffer *path, stat_cache_entry *sce) { + int multipart = 0; + int error; + off_t start, end; + const char *s, *minus; + char *boundary = "fkj49sn38dcn3"; + data_string *ds; + buffer *content_type = NULL; + + start = 0; + end = sce->st.st_size - 1; + + con->response.content_length = 0; + + if (NULL != (ds = (data_string *)array_get_element(con->response.headers, "Content-Type"))) { + content_type = ds->value; + } + + for (s = con->request.http_range, error = 0; + !error && *s && NULL != (minus = strchr(s, '-')); ) { + char *err; + off_t la, le; + + if (s == minus) { + /* - */ + + le = strtoll(s, &err, 10); + + if (le == 0) { + /* RFC 2616 - 14.35.1 */ + + con->http_status = 416; + error = 1; + } else if (*err == '\0') { + /* end */ + s = err; + + end = sce->st.st_size - 1; + start = sce->st.st_size + le; + } else if (*err == ',') { + multipart = 1; + s = err + 1; + + end = sce->st.st_size - 1; + start = sce->st.st_size + le; + } else { + error = 1; + } + + } else if (*(minus+1) == '\0' || *(minus+1) == ',') { + /* - */ + + la = strtoll(s, &err, 10); + + if (err == minus) { + /* ok */ + + if (*(err + 1) == '\0') { + s = err + 1; + + end = sce->st.st_size - 1; + start = la; + + } else if (*(err + 1) == ',') { + multipart = 1; + s = err + 2; + + end = sce->st.st_size - 1; + start = la; + } else { + error = 1; + } + } else { + /* error */ + error = 1; + } + } else { + /* - */ + + la = strtoll(s, &err, 10); + + if (err == minus) { + le = strtoll(minus+1, &err, 10); + + /* RFC 2616 - 14.35.1 */ + if (la > le) { + error = 1; + } + + if (*err == '\0') { + /* ok, end*/ + s = err; + + end = le; + start = la; + } else if (*err == ',') { + multipart = 1; + s = err + 1; + + end = le; + start = la; + } else { + /* error */ + + error = 1; + } + } else { + /* error */ + + error = 1; + } + } + + if (!error) { + if (start < 0) start = 0; + + /* RFC 2616 - 14.35.1 */ + if (end > sce->st.st_size - 1) end = sce->st.st_size - 1; + + if (start > sce->st.st_size - 1) { + error = 1; + + con->http_status = 416; + } + } + + if (!error) { + if (multipart) { + /* write boundary-header */ + buffer *b = buffer_init(); + + buffer_copy_string_len(b, CONST_STR_LEN("\r\n--")); + buffer_append_string(b, boundary); + + /* write Content-Range */ + buffer_append_string_len(b, CONST_STR_LEN("\r\nContent-Range: bytes ")); + buffer_append_int(b, start); + buffer_append_string_len(b, CONST_STR_LEN("-")); + buffer_append_int(b, end); + buffer_append_string_len(b, CONST_STR_LEN("/")); + buffer_append_int(b, sce->st.st_size); + + buffer_append_string_len(b, CONST_STR_LEN("\r\nContent-Type: ")); + buffer_append_string_buffer(b, content_type); + + /* write END-OF-HEADER */ + buffer_append_string_len(b, CONST_STR_LEN("\r\n\r\n")); + + con->response.content_length += buffer_string_length(b); + chunkqueue_append_buffer(con->write_queue, b); + buffer_free(b); + } + + chunkqueue_append_file(con->write_queue, path, start, end - start + 1); + con->response.content_length += end - start + 1; + } + } + + /* something went wrong */ + if (error) return -1; + + if (multipart) { + /* add boundary end */ + buffer *b = buffer_init(); + + buffer_copy_string_len(b, "\r\n--", 4); + buffer_append_string(b, boundary); + buffer_append_string_len(b, "--\r\n", 4); + + con->response.content_length += buffer_string_length(b); + chunkqueue_append_buffer(con->write_queue, b); + buffer_free(b); + + /* set header-fields */ + + buffer_copy_string_len(srv->tmp_buf, CONST_STR_LEN("multipart/byteranges; boundary=")); + buffer_append_string(srv->tmp_buf, boundary); + + /* overwrite content-type */ + response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(srv->tmp_buf)); + } else { + /* add Content-Range-header */ + + buffer_copy_string_len(srv->tmp_buf, CONST_STR_LEN("bytes ")); + buffer_append_int(srv->tmp_buf, start); + buffer_append_string_len(srv->tmp_buf, CONST_STR_LEN("-")); + buffer_append_int(srv->tmp_buf, end); + buffer_append_string_len(srv->tmp_buf, CONST_STR_LEN("/")); + buffer_append_int(srv->tmp_buf, sce->st.st_size); + + response_header_insert(srv, con, CONST_STR_LEN("Content-Range"), CONST_BUF_LEN(srv->tmp_buf)); + } + + /* ok, the file is set-up */ + return 0; +} + + +void http_response_send_file (server *srv, connection *con, buffer *path) { + stat_cache_entry *sce = NULL; + buffer *mtime = NULL; + data_string *ds; + int allow_caching = 1; + + if (HANDLER_ERROR == stat_cache_get_entry(srv, con, path, &sce)) { + con->http_status = (errno == ENOENT) ? 404 : 403; + + log_error_write(srv, __FILE__, __LINE__, "sbsb", + "not a regular file:", con->uri.path, + "->", path); + + return; + } + + /* we only handline regular files */ +#ifdef HAVE_LSTAT + if ((sce->is_symlink == 1) && !con->conf.follow_symlink) { + con->http_status = 403; + + if (con->conf.log_request_handling) { + log_error_write(srv, __FILE__, __LINE__, "s", "-- access denied due symlink restriction"); + log_error_write(srv, __FILE__, __LINE__, "sb", "Path :", path); + } + + return; + } +#endif + if (!S_ISREG(sce->st.st_mode)) { + con->http_status = 403; + + if (con->conf.log_file_not_found) { + log_error_write(srv, __FILE__, __LINE__, "sbsb", + "not a regular file:", con->uri.path, + "->", sce->name); + } + + return; + } + + /* mod_compress might set several data directly, don't overwrite them */ + + /* set response content-type, if not set already */ + + if (NULL == array_get_element(con->response.headers, "Content-Type")) { + if (buffer_string_is_empty(sce->content_type)) { + /* we are setting application/octet-stream, but also announce that + * this header field might change in the seconds few requests + * + * This should fix the aggressive caching of FF and the script download + * seen by the first installations + */ + response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("application/octet-stream")); + + allow_caching = 0; + } else { + response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(sce->content_type)); + } + } + + if (con->conf.range_requests) { + response_header_overwrite(srv, con, CONST_STR_LEN("Accept-Ranges"), CONST_STR_LEN("bytes")); + } + + if (allow_caching) { + if (con->etag_flags != 0 && !buffer_string_is_empty(sce->etag)) { + if (NULL == array_get_element(con->response.headers, "ETag")) { + /* generate e-tag */ + etag_mutate(con->physical.etag, sce->etag); + + response_header_overwrite(srv, con, CONST_STR_LEN("ETag"), CONST_BUF_LEN(con->physical.etag)); + } + } + + /* prepare header */ + if (NULL == (ds = (data_string *)array_get_element(con->response.headers, "Last-Modified"))) { + mtime = strftime_cache_get(srv, sce->st.st_mtime); + response_header_overwrite(srv, con, CONST_STR_LEN("Last-Modified"), CONST_BUF_LEN(mtime)); + } else { + mtime = ds->value; + } + + if (HANDLER_FINISHED == http_response_handle_cachable(srv, con, mtime)) { + return; + } + } + + if (con->request.http_range && con->conf.range_requests && con->http_status < 300) { + int do_range_request = 1; + /* check if we have a conditional GET */ + + if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "If-Range"))) { + /* if the value is the same as our ETag, we do a Range-request, + * otherwise a full 200 */ + + if (ds->value->ptr[0] == '"') { + /** + * client wants a ETag + */ + if (!con->physical.etag) { + do_range_request = 0; + } else if (!buffer_is_equal(ds->value, con->physical.etag)) { + do_range_request = 0; + } + } else if (!mtime) { + /** + * we don't have a Last-Modified and can match the If-Range: + * + * sending all + */ + do_range_request = 0; + } else if (!buffer_is_equal(ds->value, mtime)) { + do_range_request = 0; + } + } + + if (do_range_request) { + /* content prepared, I'm done */ + con->file_finished = 1; + + if (0 == http_response_parse_range(srv, con, path, sce)) { + con->http_status = 206; + } + return; + } + } + + /* if we are still here, prepare body */ + + /* we add it here for all requests + * the HEAD request will drop it afterwards again + */ + if (0 == sce->st.st_size || 0 == http_chunk_append_file(srv, con, path)) { + con->http_status = 200; + con->file_finished = 1; + } else { + con->http_status = 403; + } +} diff --git a/src/mod_staticfile.c b/src/mod_staticfile.c index a0fa2933..66b75c26 100644 --- a/src/mod_staticfile.c +++ b/src/mod_staticfile.c @@ -34,8 +34,6 @@ typedef struct { typedef struct { PLUGIN_DATA; - buffer *range_buf; - plugin_config **config_storage; plugin_config conf; @@ -47,8 +45,6 @@ INIT_FUNC(mod_staticfile_init) { p = calloc(1, sizeof(*p)); - p->range_buf = buffer_init(); - return p; } @@ -73,7 +69,6 @@ FREE_FUNC(mod_staticfile_free) { } free(p->config_storage); } - buffer_free(p->range_buf); free(p); @@ -156,215 +151,10 @@ static int mod_staticfile_patch_connection(server *srv, connection *con, plugin_ } #undef PATCH -static int http_response_parse_range(server *srv, connection *con, plugin_data *p) { - int multipart = 0; - int error; - off_t start, end; - const char *s, *minus; - char *boundary = "fkj49sn38dcn3"; - data_string *ds; - stat_cache_entry *sce = NULL; - buffer *content_type = NULL; - - if (HANDLER_ERROR == stat_cache_get_entry(srv, con, con->physical.path, &sce)) { - SEGFAULT(); - } - - start = 0; - end = sce->st.st_size - 1; - - con->response.content_length = 0; - - if (NULL != (ds = (data_string *)array_get_element(con->response.headers, "Content-Type"))) { - content_type = ds->value; - } - - for (s = con->request.http_range, error = 0; - !error && *s && NULL != (minus = strchr(s, '-')); ) { - char *err; - off_t la, le; - - if (s == minus) { - /* - */ - - le = strtoll(s, &err, 10); - - if (le == 0) { - /* RFC 2616 - 14.35.1 */ - - con->http_status = 416; - error = 1; - } else if (*err == '\0') { - /* end */ - s = err; - - end = sce->st.st_size - 1; - start = sce->st.st_size + le; - } else if (*err == ',') { - multipart = 1; - s = err + 1; - - end = sce->st.st_size - 1; - start = sce->st.st_size + le; - } else { - error = 1; - } - - } else if (*(minus+1) == '\0' || *(minus+1) == ',') { - /* - */ - - la = strtoll(s, &err, 10); - - if (err == minus) { - /* ok */ - - if (*(err + 1) == '\0') { - s = err + 1; - - end = sce->st.st_size - 1; - start = la; - - } else if (*(err + 1) == ',') { - multipart = 1; - s = err + 2; - - end = sce->st.st_size - 1; - start = la; - } else { - error = 1; - } - } else { - /* error */ - error = 1; - } - } else { - /* - */ - - la = strtoll(s, &err, 10); - - if (err == minus) { - le = strtoll(minus+1, &err, 10); - - /* RFC 2616 - 14.35.1 */ - if (la > le) { - error = 1; - } - - if (*err == '\0') { - /* ok, end*/ - s = err; - - end = le; - start = la; - } else if (*err == ',') { - multipart = 1; - s = err + 1; - - end = le; - start = la; - } else { - /* error */ - - error = 1; - } - } else { - /* error */ - - error = 1; - } - } - - if (!error) { - if (start < 0) start = 0; - - /* RFC 2616 - 14.35.1 */ - if (end > sce->st.st_size - 1) end = sce->st.st_size - 1; - - if (start > sce->st.st_size - 1) { - error = 1; - - con->http_status = 416; - } - } - - if (!error) { - if (multipart) { - /* write boundary-header */ - buffer *b = buffer_init(); - - buffer_copy_string_len(b, CONST_STR_LEN("\r\n--")); - buffer_append_string(b, boundary); - - /* write Content-Range */ - buffer_append_string_len(b, CONST_STR_LEN("\r\nContent-Range: bytes ")); - buffer_append_int(b, start); - buffer_append_string_len(b, CONST_STR_LEN("-")); - buffer_append_int(b, end); - buffer_append_string_len(b, CONST_STR_LEN("/")); - buffer_append_int(b, sce->st.st_size); - - buffer_append_string_len(b, CONST_STR_LEN("\r\nContent-Type: ")); - buffer_append_string_buffer(b, content_type); - - /* write END-OF-HEADER */ - buffer_append_string_len(b, CONST_STR_LEN("\r\n\r\n")); - - con->response.content_length += buffer_string_length(b); - chunkqueue_append_buffer(con->write_queue, b); - buffer_free(b); - } - - chunkqueue_append_file(con->write_queue, con->physical.path, start, end - start + 1); - con->response.content_length += end - start + 1; - } - } - - /* something went wrong */ - if (error) return -1; - - if (multipart) { - /* add boundary end */ - buffer *b = buffer_init(); - - buffer_copy_string_len(b, "\r\n--", 4); - buffer_append_string(b, boundary); - buffer_append_string_len(b, "--\r\n", 4); - - con->response.content_length += buffer_string_length(b); - chunkqueue_append_buffer(con->write_queue, b); - buffer_free(b); - - /* set header-fields */ - - buffer_copy_string_len(p->range_buf, CONST_STR_LEN("multipart/byteranges; boundary=")); - buffer_append_string(p->range_buf, boundary); - - /* overwrite content-type */ - response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p->range_buf)); - } else { - /* add Content-Range-header */ - - buffer_copy_string_len(p->range_buf, CONST_STR_LEN("bytes ")); - buffer_append_int(p->range_buf, start); - buffer_append_string_len(p->range_buf, CONST_STR_LEN("-")); - buffer_append_int(p->range_buf, end); - buffer_append_string_len(p->range_buf, CONST_STR_LEN("/")); - buffer_append_int(p->range_buf, sce->st.st_size); - - response_header_insert(srv, con, CONST_STR_LEN("Content-Range"), CONST_BUF_LEN(p->range_buf)); - } - - /* ok, the file is set-up */ - return 0; -} - URIHANDLER_FUNC(mod_staticfile_subrequest) { plugin_data *p = p_d; size_t k; - stat_cache_entry *sce = NULL; - buffer *mtime = NULL; data_string *ds; - int allow_caching = 1; /* someone else has done a decision for us */ if (con->http_status != 0) return HANDLER_GO_ON; @@ -412,140 +202,8 @@ URIHANDLER_FUNC(mod_staticfile_subrequest) { log_error_write(srv, __FILE__, __LINE__, "s", "-- handling file as static file"); } - if (HANDLER_ERROR == stat_cache_get_entry(srv, con, con->physical.path, &sce)) { - con->http_status = 403; - - log_error_write(srv, __FILE__, __LINE__, "sbsb", - "not a regular file:", con->uri.path, - "->", con->physical.path); - - return HANDLER_FINISHED; - } - - /* we only handline regular files */ -#ifdef HAVE_LSTAT - if ((sce->is_symlink == 1) && !con->conf.follow_symlink) { - con->http_status = 403; - - if (con->conf.log_request_handling) { - log_error_write(srv, __FILE__, __LINE__, "s", "-- access denied due symlink restriction"); - log_error_write(srv, __FILE__, __LINE__, "sb", "Path :", con->physical.path); - } - - buffer_reset(con->physical.path); - return HANDLER_FINISHED; - } -#endif - if (!S_ISREG(sce->st.st_mode)) { - con->http_status = 404; - - if (con->conf.log_file_not_found) { - log_error_write(srv, __FILE__, __LINE__, "sbsb", - "not a regular file:", con->uri.path, - "->", sce->name); - } - - return HANDLER_FINISHED; - } - - /* mod_compress might set several data directly, don't overwrite them */ - - /* set response content-type, if not set already */ - - if (NULL == array_get_element(con->response.headers, "Content-Type")) { - if (buffer_string_is_empty(sce->content_type)) { - /* we are setting application/octet-stream, but also announce that - * this header field might change in the seconds few requests - * - * This should fix the aggressive caching of FF and the script download - * seen by the first installations - */ - response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("application/octet-stream")); - - allow_caching = 0; - } else { - response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(sce->content_type)); - } - } - - if (con->conf.range_requests) { - response_header_overwrite(srv, con, CONST_STR_LEN("Accept-Ranges"), CONST_STR_LEN("bytes")); - } - - if (allow_caching) { - if (p->conf.etags_used && con->etag_flags != 0 && !buffer_string_is_empty(sce->etag)) { - if (NULL == array_get_element(con->response.headers, "ETag")) { - /* generate e-tag */ - etag_mutate(con->physical.etag, sce->etag); - - response_header_overwrite(srv, con, CONST_STR_LEN("ETag"), CONST_BUF_LEN(con->physical.etag)); - } - } - - /* prepare header */ - if (NULL == (ds = (data_string *)array_get_element(con->response.headers, "Last-Modified"))) { - mtime = strftime_cache_get(srv, sce->st.st_mtime); - response_header_overwrite(srv, con, CONST_STR_LEN("Last-Modified"), CONST_BUF_LEN(mtime)); - } else { - mtime = ds->value; - } - - if (HANDLER_FINISHED == http_response_handle_cachable(srv, con, mtime)) { - return HANDLER_FINISHED; - } - } - - if (con->request.http_range && con->conf.range_requests) { - int do_range_request = 1; - /* check if we have a conditional GET */ - - if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "If-Range"))) { - /* if the value is the same as our ETag, we do a Range-request, - * otherwise a full 200 */ - - if (ds->value->ptr[0] == '"') { - /** - * client wants a ETag - */ - if (!con->physical.etag) { - do_range_request = 0; - } else if (!buffer_is_equal(ds->value, con->physical.etag)) { - do_range_request = 0; - } - } else if (!mtime) { - /** - * we don't have a Last-Modified and can match the If-Range: - * - * sending all - */ - do_range_request = 0; - } else if (!buffer_is_equal(ds->value, mtime)) { - do_range_request = 0; - } - } - - if (do_range_request) { - /* content prepared, I'm done */ - con->file_finished = 1; - - if (0 == http_response_parse_range(srv, con, p)) { - con->http_status = 206; - } - return HANDLER_FINISHED; - } - } - - /* if we are still here, prepare body */ - - /* we add it here for all requests - * the HEAD request will drop it afterwards again - */ - if (0 == sce->st.st_size || 0 == http_chunk_append_file(srv, con, con->physical.path)) { - con->http_status = 200; - con->file_finished = 1; - } else { - con->http_status = 403; - } + if (!p->conf.etags_used) con->etag_flags = 0; + http_response_send_file(srv, con, con->physical.path); return HANDLER_FINISHED; } diff --git a/src/response.h b/src/response.h index e493a1c9..f38b8413 100644 --- a/src/response.h +++ b/src/response.h @@ -16,6 +16,7 @@ int response_header_append(server *srv, connection *con, const char *key, size_t handler_t http_response_prepare(server *srv, connection *con); int http_response_redirect_to_directory(server *srv, connection *con); int http_response_handle_cachable(server *srv, connection *con, buffer * mtime); +void http_response_send_file (server *srv, connection *con, buffer *path); buffer * strftime_cache_get(server *srv, time_t last_mod); #endif