[core] set REDIRECT_STATUS to error_handler_saved_status (fixes #1828)
[lighttpd.git] / src / mod_uploadprogress.c
blob2772b80e3ef9b860ec81f7ec68b656b25d51922d
1 #include "first.h"
3 #include "base.h"
4 #include "log.h"
5 #include "buffer.h"
7 #include "plugin.h"
9 #include "response.h"
10 #include "stat_cache.h"
12 #include <ctype.h>
13 #include <stdlib.h>
14 #include <string.h>
16 /**
17 * this is a uploadprogress for a lighttpd plugin
21 typedef struct {
22 buffer *con_id;
23 connection *con;
24 } connection_map_entry;
26 typedef struct {
27 connection_map_entry **ptr;
29 size_t used;
30 size_t size;
31 } connection_map;
33 /* plugin config for all request/connections */
35 typedef struct {
36 buffer *progress_url;
37 } plugin_config;
39 typedef struct {
40 PLUGIN_DATA;
42 connection_map *con_map;
44 plugin_config **config_storage;
46 plugin_config conf;
47 } plugin_data;
49 /**
51 * connection maps
55 /* init the plugin data */
56 static connection_map *connection_map_init() {
57 connection_map *cm;
59 cm = calloc(1, sizeof(*cm));
61 return cm;
64 static void connection_map_free(connection_map *cm) {
65 size_t i;
66 for (i = 0; i < cm->size; i++) {
67 connection_map_entry *cme = cm->ptr[i];
69 if (!cme) break;
71 if (cme->con_id) {
72 buffer_free(cme->con_id);
74 free(cme);
77 free(cm);
80 static int connection_map_insert(connection_map *cm, connection *con, buffer *con_id) {
81 connection_map_entry *cme;
82 size_t i;
84 if (cm->size == 0) {
85 cm->size = 16;
86 cm->ptr = malloc(cm->size * sizeof(*(cm->ptr)));
87 for (i = 0; i < cm->size; i++) {
88 cm->ptr[i] = NULL;
90 } else if (cm->used == cm->size) {
91 cm->size += 16;
92 cm->ptr = realloc(cm->ptr, cm->size * sizeof(*(cm->ptr)));
93 for (i = cm->used; i < cm->size; i++) {
94 cm->ptr[i] = NULL;
98 if (cm->ptr[cm->used]) {
99 /* is already alloced, just reuse it */
100 cme = cm->ptr[cm->used];
101 } else {
102 cme = malloc(sizeof(*cme));
104 cme->con_id = buffer_init();
105 buffer_copy_buffer(cme->con_id, con_id);
106 cme->con = con;
108 cm->ptr[cm->used++] = cme;
110 return 0;
113 static connection *connection_map_get_connection(connection_map *cm, buffer *con_id) {
114 size_t i;
116 for (i = 0; i < cm->used; i++) {
117 connection_map_entry *cme = cm->ptr[i];
119 if (buffer_is_equal(cme->con_id, con_id)) {
120 /* found connection */
122 return cme->con;
125 return NULL;
128 static int connection_map_remove_connection(connection_map *cm, connection *con) {
129 size_t i;
131 for (i = 0; i < cm->used; i++) {
132 connection_map_entry *cme = cm->ptr[i];
134 if (cme->con == con) {
135 /* found connection */
137 buffer_reset(cme->con_id);
138 cme->con = NULL;
140 cm->used--;
142 /* swap positions with the last entry */
143 if (cm->used) {
144 cm->ptr[i] = cm->ptr[cm->used];
145 cm->ptr[cm->used] = cme;
148 return 1;
152 return 0;
155 /* init the plugin data */
156 INIT_FUNC(mod_uploadprogress_init) {
157 plugin_data *p;
159 p = calloc(1, sizeof(*p));
161 p->con_map = connection_map_init();
163 return p;
166 /* detroy the plugin data */
167 FREE_FUNC(mod_uploadprogress_free) {
168 plugin_data *p = p_d;
170 UNUSED(srv);
172 if (!p) return HANDLER_GO_ON;
174 if (p->config_storage) {
175 size_t i;
176 for (i = 0; i < srv->config_context->used; i++) {
177 plugin_config *s = p->config_storage[i];
179 if (NULL == s) continue;
181 buffer_free(s->progress_url);
183 free(s);
185 free(p->config_storage);
188 connection_map_free(p->con_map);
190 free(p);
192 return HANDLER_GO_ON;
195 /* handle plugin config and check values */
197 SETDEFAULTS_FUNC(mod_uploadprogress_set_defaults) {
198 plugin_data *p = p_d;
199 size_t i = 0;
201 config_values_t cv[] = {
202 { "upload-progress.progress-url", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
203 { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
206 if (!p) return HANDLER_ERROR;
208 p->config_storage = calloc(1, srv->config_context->used * sizeof(plugin_config *));
210 for (i = 0; i < srv->config_context->used; i++) {
211 data_config const* config = (data_config const*)srv->config_context->data[i];
212 plugin_config *s;
214 s = calloc(1, sizeof(plugin_config));
215 s->progress_url = buffer_init();
217 cv[0].destination = s->progress_url;
219 p->config_storage[i] = s;
221 if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) {
222 return HANDLER_ERROR;
226 return HANDLER_GO_ON;
229 #define PATCH(x) \
230 p->conf.x = s->x;
231 static int mod_uploadprogress_patch_connection(server *srv, connection *con, plugin_data *p) {
232 size_t i, j;
233 plugin_config *s = p->config_storage[0];
235 PATCH(progress_url);
237 /* skip the first, the global context */
238 for (i = 1; i < srv->config_context->used; i++) {
239 data_config *dc = (data_config *)srv->config_context->data[i];
240 s = p->config_storage[i];
242 /* condition didn't match */
243 if (!config_check_cond(srv, con, dc)) continue;
245 /* merge config */
246 for (j = 0; j < dc->value->used; j++) {
247 data_unset *du = dc->value->data[j];
249 if (buffer_is_equal_string(du->key, CONST_STR_LEN("upload-progress.progress-url"))) {
250 PATCH(progress_url);
255 return 0;
257 #undef PATCH
261 * the idea:
263 * for the first request we check if it is a post-request
265 * if no, move out, don't care about them
267 * if yes, take the connection structure and register it locally
268 * in the progress-struct together with an session-id (md5 ... )
270 * if the connections closes, cleanup the entry in the progress-struct
272 * a second request can now get the info about the size of the upload,
273 * the received bytes
277 URIHANDLER_FUNC(mod_uploadprogress_uri_handler) {
278 plugin_data *p = p_d;
279 size_t i;
280 data_string *ds;
281 buffer *b;
282 connection *post_con = NULL;
284 UNUSED(srv);
286 if (con->uri.path->used == 0) return HANDLER_GO_ON;
288 mod_uploadprogress_patch_connection(srv, con, p);
290 /* check if this is a POST request */
291 switch(con->request.http_method) {
292 case HTTP_METHOD_POST:
293 /* the request has to contain a 32byte ID */
295 if (NULL == (ds = (data_string *)array_get_element(con->request.headers, "X-Progress-ID"))) {
296 if (!buffer_string_is_empty(con->uri.query)) {
297 /* perhaps the POST request is using the querystring to pass the X-Progress-ID */
298 b = con->uri.query;
299 } else {
300 return HANDLER_GO_ON;
302 } else {
303 b = ds->value;
306 if (b->used != 32 + 1) {
307 log_error_write(srv, __FILE__, __LINE__, "sd",
308 "len of progress-id != 32:", b->used - 1);
309 return HANDLER_GO_ON;
312 for (i = 0; i < b->used - 1; i++) {
313 char c = b->ptr[i];
315 if (!light_isxdigit(c)) {
316 log_error_write(srv, __FILE__, __LINE__, "sb",
317 "non-xdigit in progress-id:", b);
318 return HANDLER_GO_ON;
322 connection_map_insert(p->con_map, con, b);
324 return HANDLER_GO_ON;
325 case HTTP_METHOD_GET:
326 if (!buffer_is_equal(con->uri.path, p->conf.progress_url)) {
327 return HANDLER_GO_ON;
330 if (NULL == (ds = (data_string *)array_get_element(con->request.headers, "X-Progress-ID"))) {
331 if (!buffer_string_is_empty(con->uri.query)) {
332 /* perhaps the GET request is using the querystring to pass the X-Progress-ID */
333 b = con->uri.query;
334 } else {
335 return HANDLER_GO_ON;
337 } else {
338 b = ds->value;
341 if (b->used != 32 + 1) {
342 log_error_write(srv, __FILE__, __LINE__, "sd",
343 "len of progress-id != 32:", b->used - 1);
344 return HANDLER_GO_ON;
347 for (i = 0; i < b->used - 1; i++) {
348 char c = b->ptr[i];
350 if (!light_isxdigit(c)) {
351 log_error_write(srv, __FILE__, __LINE__, "sb",
352 "non-xdigit in progress-id:", b);
353 return HANDLER_GO_ON;
357 buffer_reset(con->physical.path);
359 con->file_started = 1;
360 con->file_finished = 1;
362 con->http_status = 200;
363 con->mode = DIRECT;
365 /* get the connection */
366 if (NULL == (post_con = connection_map_get_connection(p->con_map, b))) {
367 log_error_write(srv, __FILE__, __LINE__, "sb",
368 "ID no known:", b);
370 chunkqueue_get_append_mem(con->write_queue, CONST_STR_LEN("starting"));
372 return HANDLER_FINISHED;
375 response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/xml"));
377 /* just an attempt the force the IE/proxies to NOT cache the request ... doesn't help :( */
378 response_header_overwrite(srv, con, CONST_STR_LEN("Pragma"), CONST_STR_LEN("no-cache"));
379 response_header_overwrite(srv, con, CONST_STR_LEN("Expires"), CONST_STR_LEN("Thu, 19 Nov 1981 08:52:00 GMT"));
380 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"));
382 b = buffer_init();
384 /* prepare XML */
385 buffer_copy_string_len(b, CONST_STR_LEN(
386 "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>"
387 "<upload>"
388 "<size>"));
389 buffer_append_int(b, post_con->request.content_length);
390 buffer_append_string_len(b, CONST_STR_LEN(
391 "</size>"
392 "<received>"));
393 buffer_append_int(b, post_con->request_content_queue->bytes_in);
394 buffer_append_string_len(b, CONST_STR_LEN(
395 "</received>"
396 "</upload>"));
398 #if 0
399 log_error_write(srv, __FILE__, __LINE__, "sb", "...", b);
400 #endif
402 chunkqueue_append_buffer(con->write_queue, b);
403 buffer_free(b);
405 return HANDLER_FINISHED;
406 default:
407 break;
410 return HANDLER_GO_ON;
413 REQUESTDONE_FUNC(mod_uploadprogress_request_done) {
414 plugin_data *p = p_d;
416 UNUSED(srv);
418 if (con->uri.path->used == 0) return HANDLER_GO_ON;
420 if (connection_map_remove_connection(p->con_map, con)) {
421 /* removed */
424 return HANDLER_GO_ON;
427 /* this function is called at dlopen() time and inits the callbacks */
429 int mod_uploadprogress_plugin_init(plugin *p);
430 int mod_uploadprogress_plugin_init(plugin *p) {
431 p->version = LIGHTTPD_VERSION_ID;
432 p->name = buffer_init_string("uploadprogress");
434 p->init = mod_uploadprogress_init;
435 p->handle_uri_clean = mod_uploadprogress_uri_handler;
436 p->handle_request_done = mod_uploadprogress_request_done;
437 p->set_defaults = mod_uploadprogress_set_defaults;
438 p->cleanup = mod_uploadprogress_free;
440 p->data = NULL;
442 return 0;