diff options
Diffstat (limited to 'src/mod_uploadprogress.c')
-rw-r--r-- | src/mod_uploadprogress.c | 638 |
1 files changed, 0 insertions, 638 deletions
diff --git a/src/mod_uploadprogress.c b/src/mod_uploadprogress.c deleted file mode 100644 index 56423680..00000000 --- a/src/mod_uploadprogress.c +++ /dev/null @@ -1,638 +0,0 @@ -#include <ctype.h> -#include <stdlib.h> -#include <string.h> - -#include "base.h" -#include "log.h" -#include "buffer.h" - -#include "plugin.h" - -#include "response.h" -#include "stat_cache.h" - -#define CONFIG_UPLOAD_PROGRESS_URL "upload-progress.progress-url" -#define CONFIG_UPLOAD_PROGRESS_TIMEOUT "upload-progress.remove-timeout" -#define CONFIG_UPLOAD_PROGRESS_DEBUG "upload-progress.debug" - -/** - * uploadprogress for lighttpd - * - * Initial: Jan Kneschke <jan@kneschke.de> - * Timeout+Status addon: Bjoern Kalkbrenner <terminar@cyberphoria.org> [20070112] - * - * the timeout is used to keep in the status information intact even if the parent - * connection is gone already - */ - -typedef struct { - buffer *tracking_id; - connection *con; - - time_t timeout; - int status; - off_t size; -} connection_map_entry; - -typedef struct { - connection_map_entry **ptr; - - size_t used; - size_t size; -} connection_map; - -/* plugin config for all request/connections */ - -typedef struct { - buffer *progress_url; - unsigned short debug; - unsigned short remove_timeout; -} plugin_config; - -typedef struct { - PLUGIN_DATA; - - connection_map *con_map; - - buffer *tmp_buf; /** used as temporary buffer for extracting the tracking id */ - - plugin_config **config_storage; - - plugin_config conf; -} plugin_data; - -/** - * - * connection maps - * - */ - -/* init the plugin data */ -static connection_map *connection_map_init() { - connection_map *cm; - - cm = calloc(1, sizeof(*cm)); - - return cm; -} - -static void connection_map_free(connection_map *cm) { - size_t i; - for (i = 0; i < cm->size; i++) { - connection_map_entry *cme = cm->ptr[i]; - - if (!cme) break; - - if (cme->tracking_id) { - buffer_free(cme->tracking_id); - } - free(cme); - } - - free(cm); -} - -static connection_map_entry *connection_map_insert(connection_map *cm, buffer *tracking_id, connection *con) { - connection_map_entry *cme; - size_t i; - - if (cm->size == 0) { - cm->size = 16; - cm->ptr = malloc(cm->size * sizeof(*(cm->ptr))); - for (i = 0; i < cm->size; i++) { - cm->ptr[i] = NULL; - } - } else if (cm->used == cm->size) { - cm->size += 16; - cm->ptr = realloc(cm->ptr, cm->size * sizeof(*(cm->ptr))); - for (i = cm->used; i < cm->size; i++) { - cm->ptr[i] = NULL; - } - } - - if (cm->ptr[cm->used]) { - /* is already alloced, just reuse it */ - cme = cm->ptr[cm->used]; - } else { - cme = malloc(sizeof(*cme)); - cme->tracking_id = buffer_init(); - } - cme->timeout = 0; - cme->status = 0; - buffer_copy_string_buffer(cme->tracking_id, tracking_id); - cme->con = con; - - cm->ptr[cm->used++] = cme; - - return cme; -} - -static connection_map_entry *connection_map_get_connection_entry(connection_map *cm, buffer *tracking_id) { - size_t i; - - for (i = 0; i < cm->used; i++) { - connection_map_entry *cme = cm->ptr[i]; - - if (buffer_is_equal(cme->tracking_id, tracking_id)) { - /* found connection */ - return cme; - } - } - return NULL; -} - -static void connection_map_remove_connection(connection_map *cm, size_t i) { - connection_map_entry *cme = cm->ptr[i]; - - buffer_reset(cme->tracking_id); - cme->timeout=0; - cme->status=0; - - cm->used--; - - /* swap positions with the last entry */ - if (cm->used) { - cm->ptr[i] = cm->ptr[cm->used]; - cm->ptr[cm->used] = cme; - } -} - -/** - * remove dead tracking IDs - * - * uploadprogress.remove-timeout sets a grace-period in which the - * connection status is still known even of the connection is already - * being removed - * - */ -static void connection_map_clear_timeout_connections(connection_map *cm) { - size_t i; - time_t now_t = time(NULL); - - for (i = 0; i < cm->used; i++) { - connection_map_entry *cme = cm->ptr[i]; - - if (cme->timeout != 0 && cme->timeout < now_t) { - /* found connection */ - connection_map_remove_connection(cm, i); - } - } -} - -/** - * extract the tracking-id from the parameters - * - * for POST requests it is part of the request headers - * for GET requests ... too - */ -static buffer *get_tracking_id(plugin_data *p, connection *con) { - data_string *ds; - buffer *b = NULL; - char *qstr=NULL; - size_t i; - - /* the request has to contain a 32byte ID */ - if (NULL == (ds = (data_string *)array_get_element(con->request.headers, CONST_STR_LEN("X-Progress-ID")))) { - char *amp = NULL; - - /* perhaps the POST request is using the querystring to pass the X-Progress-ID */ - if (buffer_is_empty(con->uri.query)) { - /* - * con->uri.query will not be parsed out if a 413 error happens - */ - if (NULL != (qstr = strchr(con->request.uri->ptr, '?'))) { - /** extract query string from request.uri */ - buffer_copy_string(con->uri.query, qstr + 1); - } else { - return NULL; - } - } - - /** split the query-string and extract the X-Progress-ID */ - do { - char *eq = NULL; - char *start = amp ? amp + 1 : con->uri.query->ptr; - - amp = strchr(start, '&'); - - /* check the string between start and amp for = */ - - if (amp) { - buffer_copy_string_len(p->tmp_buf, start, amp - start); - } else { - buffer_copy_string(p->tmp_buf, start); - } - - eq = strchr(p->tmp_buf->ptr, '='); - - if (eq) { - *eq = '\0'; - - if (0 == strcmp(p->tmp_buf->ptr, "X-Progress-ID")) { - size_t key_len = sizeof("X-Progress-ID") - 1; - size_t var_len = p->tmp_buf->used - 1; - /* found */ - - buffer_copy_string_len(p->tmp_buf, start + key_len + 1, var_len - key_len - 1); - - b = p->tmp_buf; - - break; - } - } - } while (amp); - - if (!b) return NULL; - } else { - /* request header was found, use it */ - b = ds->value; - } - - if (b->used != 32 + 1) { - if (p->conf.debug) ERROR("the Progress-ID has to be 32 characters long, got %zd characters", b->used - 1); - return NULL; - } - - for (i = 0; i < b->used - 1; i++) { - char c = b->ptr[i]; - - if (!light_isxdigit(c)) { - if (p->conf.debug) ERROR("only hex-digits are allowed (0-9 + a-f): (ascii: %d)", c); - return NULL; - } - } - - return b; -} - -/* init the plugin data */ -INIT_FUNC(mod_uploadprogress_init) { - plugin_data *p; - - UNUSED(srv); - - p = calloc(1, sizeof(*p)); - - p->con_map = connection_map_init(); - p->tmp_buf = buffer_init(); - - return p; -} - -/* detroy the plugin data */ -FREE_FUNC(mod_uploadprogress_free) { - plugin_data *p = p_d; - - if (!p) return HANDLER_GO_ON; - - if (p->config_storage) { - size_t i; - for (i = 0; i < srv->config_context->used; i++) { - plugin_config *s = p->config_storage[i]; - - buffer_free(s->progress_url); - s->remove_timeout=0; - - free(s); - } - free(p->config_storage); - } - - connection_map_free(p->con_map); - buffer_free(p->tmp_buf); - - free(p); - - return HANDLER_GO_ON; -} - -/* handle plugin config and check values */ - -SETDEFAULTS_FUNC(mod_uploadprogress_set_defaults) { - plugin_data *p = p_d; - size_t i = 0; - - config_values_t cv[] = { - { CONFIG_UPLOAD_PROGRESS_URL, NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */ - { CONFIG_UPLOAD_PROGRESS_TIMEOUT, NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 1 */ - { CONFIG_UPLOAD_PROGRESS_DEBUG, NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 2 */ - { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } - }; - - if (!p) return HANDLER_ERROR; - - p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *)); - - for (i = 0; i < srv->config_context->used; i++) { - plugin_config *s; - - s = calloc(1, sizeof(plugin_config)); - s->progress_url = buffer_init(); - s->remove_timeout = 60; - s->debug = 0; - - cv[0].destination = s->progress_url; - cv[1].destination = &(s->remove_timeout); - cv[2].destination = &(s->debug); - - p->config_storage[i] = s; - - if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) { - return HANDLER_ERROR; - } - } - - return HANDLER_GO_ON; -} - -static int mod_uploadprogress_patch_connection(server *srv, connection *con, plugin_data *p) { - size_t i, j; - plugin_config *s = p->config_storage[0]; - - PATCH_OPTION(progress_url); - PATCH_OPTION(remove_timeout); - PATCH_OPTION(debug); - - /* skip the first, the global context */ - for (i = 1; i < srv->config_context->used; i++) { - data_config *dc = (data_config *)srv->config_context->data[i]; - s = p->config_storage[i]; - - /* condition didn't match */ - if (!config_check_cond(srv, con, dc)) continue; - - /* merge config */ - for (j = 0; j < dc->value->used; j++) { - data_unset *du = dc->value->data[j]; - - if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_UPLOAD_PROGRESS_URL))) { - PATCH_OPTION(progress_url); - } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_UPLOAD_PROGRESS_TIMEOUT))) { - PATCH_OPTION(remove_timeout); - } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_UPLOAD_PROGRESS_DEBUG))) { - PATCH_OPTION(debug); - } - } - } - - return 0; -} - -/** - * - * the idea: - * - * for the first request we check if it is a post-request - * - * if no, move out, don't care about them - * - * if yes, take the connection structure and register it locally - * in the progress-struct together with an session-id (md5 ... ) - * - * if the connections closes, cleanup the entry in the progress-struct - * - * a second request can now get the info about the size of the upload, - * the received bytes - * - */ - -URIHANDLER_FUNC(mod_uploadprogress_uri_handler) { - plugin_data *p = p_d; - buffer *tracking_id; - buffer *b; - connection_map_entry *post_con_entry = NULL; - connection_map_entry *map_con_entry = NULL; - - if (buffer_is_empty(con->uri.path)) return HANDLER_GO_ON; - - mod_uploadprogress_patch_connection(srv, con, p); - - /* no progress URL set, ignore request */ - if (buffer_is_empty(p->conf.progress_url)) return HANDLER_GO_ON; - - switch(con->request.http_method) { - case HTTP_METHOD_POST: - /** - * a POST request is the UPLOAD itself - * - * get the unique tracker id - */ - if (NULL == (tracking_id = get_tracking_id(p, con))) { - return HANDLER_GO_ON; - } - - if (NULL == (map_con_entry = connection_map_get_connection_entry(p->con_map, tracking_id))) { - connection_map_insert(p->con_map, tracking_id, con); - - if (p->conf.debug) TRACE("POST: connection is new, registered: %s", SAFE_BUF_STR(tracking_id)); - } else { - map_con_entry->timeout = 0; - map_con_entry->status = 0; - - if (p->conf.debug) TRACE("POST: connection is known, id: %s", SAFE_BUF_STR(tracking_id)); - } - - return HANDLER_GO_ON; - case HTTP_METHOD_GET: - /** - * the status request for the current connection - */ - if (p->conf.debug) TRACE("(uploadprogress) urls %s == %s", SAFE_BUF_STR(con->uri.path), SAFE_BUF_STR(p->conf.progress_url)); - - if (!buffer_is_equal(con->uri.path, p->conf.progress_url)) { - return HANDLER_GO_ON; - } - - /* get the tracker id */ - if (NULL == (tracking_id = get_tracking_id(p, con))) { - return HANDLER_GO_ON; - } - - buffer_reset(con->physical.path); - - con->file_started = 1; - con->http_status = 200; - con->send->is_closed = 1; - - /* send JSON content */ - - response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/javascript")); - - /* just an attempt the force the IE/proxies to NOT cache the request */ - response_header_overwrite(srv, con, CONST_STR_LEN("Pragma"), CONST_STR_LEN("no-cache")); - response_header_overwrite(srv, con, CONST_STR_LEN("Expires"), CONST_STR_LEN("Thu, 19 Nov 1981 08:52:00 GMT")); - response_header_overwrite(srv, con, CONST_STR_LEN("Cache-Control"), - CONST_STR_LEN("no-store, no-cache, must-revalidate, post-check=0, pre-check=0")); - - b = chunkqueue_get_append_buffer(con->send); - - /* get the connection */ - if (NULL == (post_con_entry = connection_map_get_connection_entry(p->con_map, tracking_id))) { - /** - * looks like we don't know the tracking id yet, GET and POST out of sync ? */ - buffer_append_string_len(b, CONST_STR_LEN("new Object({ 'state' : 'starting' })\r\n")); - con->send->bytes_in += b->used-1; - - if (p->conf.debug) TRACE("connection unknown: %s, sending: %s", SAFE_BUF_STR(tracking_id), SAFE_BUF_STR(b)); - - return HANDLER_FINISHED; - } - - buffer_copy_string_len(b, CONST_STR_LEN("new Object({ 'state' : ")); - - if (post_con_entry->status == 413) { - /* the upload was too large */ - buffer_append_string_len(b, CONST_STR_LEN("'error', 'status' : 413")); - } else if (post_con_entry->con == NULL) { - /* the connection is already gone */ - buffer_append_string_len(b, CONST_STR_LEN("'done', 'size' : ")); - buffer_append_off_t(b, post_con_entry->size); - } else { - /* the upload is already done, but the connection might be still open */ - buffer_append_string(b, post_con_entry->con->recv->is_closed ? "'done'" : "'uploading'"); - buffer_append_string_len(b, CONST_STR_LEN(", 'received' : ")); - buffer_append_off_t(b, post_con_entry->con->recv->bytes_in); - buffer_append_string_len(b, CONST_STR_LEN(", 'size' : ")); - buffer_append_off_t(b, post_con_entry->con->request.content_length == -1 ? 0 : post_con_entry->con->request.content_length); - } - buffer_append_string_len(b, CONST_STR_LEN("})\r\n")); - con->send->bytes_in += b->used-1; - - if (p->conf.debug) TRACE("connection is known: %s, sending: %s", SAFE_BUF_STR(tracking_id), SAFE_BUF_STR(b)); - - return HANDLER_FINISHED; - default: - break; - } - - return HANDLER_GO_ON; -} - -/** - * check if request parser sent 413 for our POST request - */ -URIHANDLER_FUNC(mod_uploadprogress_response_header) { - plugin_data *p = p_d; - - buffer *tracking_id; - connection_map_entry *map_con_entry = NULL; - - UNUSED(srv); - - /* - * we only want to process an 413 (Bad length) error for the upload (POST request) - */ - if (con->request.http_method != HTTP_METHOD_POST || con->http_status != 413) { - return HANDLER_GO_ON; - } - - if (p->conf.debug) { - TRACE("response_header: con=%p, http_method=%d, http_status=%d", (void*) con, - con->request.http_method, con->http_status); - } - - /* get the tracker id */ - if (NULL == (tracking_id = get_tracking_id(p, con))) { - return HANDLER_GO_ON; - } - - if (NULL == (map_con_entry = connection_map_get_connection_entry(p->con_map, tracking_id))) { - /** - * in case the request parser meant the request was too large the URI handler won't - * get called. Insert the connection mapping here - */ - if (NULL == (map_con_entry = connection_map_insert(p->con_map, tracking_id, con))) { - return HANDLER_GO_ON; - } - } - - /* ok, found our entries, setting 413 here for status */ - map_con_entry->status = 413; - - return HANDLER_GO_ON; -} - -/** - * remove the parent connection from the connection mapping - * when it got closed - * - * keep the mapping active for a while to send a valid final status - */ -REQUESTDONE_FUNC(mod_uploadprogress_request_done) { - plugin_data *p = p_d; - buffer *tracking_id; - connection_map_entry *cm = NULL; - - UNUSED(srv); - - if (buffer_is_empty(con->uri.path)) return HANDLER_GO_ON; - - /* - * only need to handle the upload request. - */ - if (con->request.http_method != HTTP_METHOD_POST) { - return HANDLER_GO_ON; - } - - if (NULL == (tracking_id = get_tracking_id(p, con))) { - return HANDLER_GO_ON; - } - - if (p->conf.debug) { - TRACE("upload is done, moving tracking-id to backlog: tracking-id=%s, http_status=%d", - SAFE_BUF_STR(tracking_id), - con->http_status); - } - - /* - * set timeout on the upload's connection_map_entry. - */ - if (NULL == (cm = connection_map_get_connection_entry(p->con_map, tracking_id))) { - if (p->conf.debug) { - TRACE("tracking ID %s not found, can't set timeout", SAFE_BUF_STR(tracking_id)); - } - return HANDLER_GO_ON; - } - - /* save request size to be able to report it even when cm->con == NULL */ - cm->size = con->request.content_length; - - cm->timeout = time(NULL) + p->conf.remove_timeout; - cm->con = NULL; /* con becomes invalid very soon */ - - return HANDLER_GO_ON; -} - -/** - * remove dead connections once in while - */ -TRIGGER_FUNC(mod_uploadprogress_trigger) { - plugin_data *p = p_d; - - if ((srv->cur_ts % 10) != 0) return HANDLER_GO_ON; - - connection_map_clear_timeout_connections(p->con_map); - - return HANDLER_GO_ON; -} - - -/* this function is called at dlopen() time and inits the callbacks */ - -LI_EXPORT int mod_uploadprogress_plugin_init(plugin *p); -LI_EXPORT int mod_uploadprogress_plugin_init(plugin *p) { - p->version = LIGHTTPD_VERSION_ID; - p->name = buffer_init_string("uploadprogress"); - - p->init = mod_uploadprogress_init; - p->handle_uri_clean = mod_uploadprogress_uri_handler; - p->handle_response_done = mod_uploadprogress_request_done; - p->set_defaults = mod_uploadprogress_set_defaults; - p->cleanup = mod_uploadprogress_free; - p->handle_trigger = mod_uploadprogress_trigger; - p->handle_response_header = mod_uploadprogress_response_header; - - p->data = NULL; - - return 0; -} |