[core] fdevent_cycle_logger()
[lighttpd.git] / src / mod_uploadprogress.c
blobc12a478a23012eabc62e5c014f24c1209481973c
1 #include "first.h"
3 #include "base.h"
4 #include "log.h"
5 #include "buffer.h"
7 #include "plugin.h"
9 #include "response.h"
11 #include <stdlib.h>
12 #include <string.h>
14 /**
15 * this is a uploadprogress for a lighttpd plugin
19 typedef struct {
20 buffer *con_id;
21 connection *con;
22 } connection_map_entry;
24 typedef struct {
25 connection_map_entry **ptr;
27 size_t used;
28 size_t size;
29 } connection_map;
31 /* plugin config for all request/connections */
33 typedef struct {
34 buffer *progress_url;
35 } plugin_config;
37 typedef struct {
38 PLUGIN_DATA;
40 connection_map *con_map;
42 plugin_config **config_storage;
44 plugin_config conf;
45 } plugin_data;
47 /**
49 * connection maps
53 /* init the plugin data */
54 static connection_map *connection_map_init() {
55 connection_map *cm;
57 cm = calloc(1, sizeof(*cm));
59 return cm;
62 static void connection_map_free(connection_map *cm) {
63 size_t i;
64 for (i = 0; i < cm->size; i++) {
65 connection_map_entry *cme = cm->ptr[i];
67 if (!cme) break;
69 if (cme->con_id) {
70 buffer_free(cme->con_id);
72 free(cme);
75 free(cm);
78 static int connection_map_insert(connection_map *cm, connection *con, const char *con_id, size_t idlen) {
79 connection_map_entry *cme;
80 size_t i;
82 if (cm->size == 0) {
83 cm->size = 16;
84 cm->ptr = malloc(cm->size * sizeof(*(cm->ptr)));
85 for (i = 0; i < cm->size; i++) {
86 cm->ptr[i] = NULL;
88 } else if (cm->used == cm->size) {
89 cm->size += 16;
90 cm->ptr = realloc(cm->ptr, cm->size * sizeof(*(cm->ptr)));
91 for (i = cm->used; i < cm->size; i++) {
92 cm->ptr[i] = NULL;
96 if (cm->ptr[cm->used]) {
97 /* is already alloced, just reuse it */
98 cme = cm->ptr[cm->used];
99 } else {
100 cme = malloc(sizeof(*cme));
101 cme->con_id = buffer_init();
103 buffer_copy_string_len(cme->con_id, con_id, idlen);
104 cme->con = con;
106 cm->ptr[cm->used++] = cme;
108 return 0;
111 static connection *connection_map_get_connection(connection_map *cm, const char *con_id, size_t idlen) {
112 size_t i;
114 for (i = 0; i < cm->used; i++) {
115 connection_map_entry *cme = cm->ptr[i];
117 if (buffer_is_equal_string(cme->con_id, con_id, idlen)) {
118 /* found connection */
120 return cme->con;
123 return NULL;
126 static int connection_map_remove_connection(connection_map *cm, connection *con) {
127 size_t i;
129 for (i = 0; i < cm->used; i++) {
130 connection_map_entry *cme = cm->ptr[i];
132 if (cme->con == con) {
133 /* found connection */
135 buffer_reset(cme->con_id);
136 cme->con = NULL;
138 cm->used--;
140 /* swap positions with the last entry */
141 if (cm->used) {
142 cm->ptr[i] = cm->ptr[cm->used];
143 cm->ptr[cm->used] = cme;
146 return 1;
150 return 0;
153 /* init the plugin data */
154 INIT_FUNC(mod_uploadprogress_init) {
155 plugin_data *p;
157 p = calloc(1, sizeof(*p));
159 p->con_map = connection_map_init();
161 return p;
164 /* detroy the plugin data */
165 FREE_FUNC(mod_uploadprogress_free) {
166 plugin_data *p = p_d;
168 UNUSED(srv);
170 if (!p) return HANDLER_GO_ON;
172 if (p->config_storage) {
173 size_t i;
174 for (i = 0; i < srv->config_context->used; i++) {
175 plugin_config *s = p->config_storage[i];
177 if (NULL == s) continue;
179 buffer_free(s->progress_url);
181 free(s);
183 free(p->config_storage);
186 connection_map_free(p->con_map);
188 free(p);
190 return HANDLER_GO_ON;
193 /* handle plugin config and check values */
195 SETDEFAULTS_FUNC(mod_uploadprogress_set_defaults) {
196 plugin_data *p = p_d;
197 size_t i = 0;
199 config_values_t cv[] = {
200 { "upload-progress.progress-url", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
201 { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
204 if (!p) return HANDLER_ERROR;
206 p->config_storage = calloc(1, srv->config_context->used * sizeof(plugin_config *));
208 for (i = 0; i < srv->config_context->used; i++) {
209 data_config const* config = (data_config const*)srv->config_context->data[i];
210 plugin_config *s;
212 s = calloc(1, sizeof(plugin_config));
213 s->progress_url = buffer_init();
215 cv[0].destination = s->progress_url;
217 p->config_storage[i] = s;
219 if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) {
220 return HANDLER_ERROR;
224 return HANDLER_GO_ON;
227 #define PATCH(x) \
228 p->conf.x = s->x;
229 static int mod_uploadprogress_patch_connection(server *srv, connection *con, plugin_data *p) {
230 size_t i, j;
231 plugin_config *s = p->config_storage[0];
233 PATCH(progress_url);
235 /* skip the first, the global context */
236 for (i = 1; i < srv->config_context->used; i++) {
237 data_config *dc = (data_config *)srv->config_context->data[i];
238 s = p->config_storage[i];
240 /* condition didn't match */
241 if (!config_check_cond(srv, con, dc)) continue;
243 /* merge config */
244 for (j = 0; j < dc->value->used; j++) {
245 data_unset *du = dc->value->data[j];
247 if (buffer_is_equal_string(du->key, CONST_STR_LEN("upload-progress.progress-url"))) {
248 PATCH(progress_url);
253 return 0;
255 #undef PATCH
259 * the idea:
261 * for the first request we check if it is a post-request
263 * if no, move out, don't care about them
265 * if yes, take the connection structure and register it locally
266 * in the progress-struct together with an session-id (md5 ... )
268 * if the connections closes, cleanup the entry in the progress-struct
270 * a second request can now get the info about the size of the upload,
271 * the received bytes
275 URIHANDLER_FUNC(mod_uploadprogress_uri_handler) {
276 plugin_data *p = p_d;
277 size_t len;
278 char *id;
279 data_string *ds;
280 buffer *b;
281 connection *post_con = NULL;
282 int pathinfo = 0;
284 if (buffer_string_is_empty(con->uri.path)) return HANDLER_GO_ON;
285 switch(con->request.http_method) {
286 case HTTP_METHOD_GET:
287 case HTTP_METHOD_POST: break;
288 default: return HANDLER_GO_ON;
291 mod_uploadprogress_patch_connection(srv, con, p);
292 if (buffer_string_is_empty(p->conf.progress_url)) return HANDLER_GO_ON;
294 if (con->request.http_method == HTTP_METHOD_GET) {
295 if (!buffer_is_equal(con->uri.path, p->conf.progress_url)) {
296 return HANDLER_GO_ON;
300 if (NULL != (ds = (data_string *)array_get_element(con->request.headers, "X-Progress-ID"))) {
301 id = ds->value->ptr;
302 } else if (!buffer_string_is_empty(con->uri.query)
303 && (id = strstr(con->uri.query->ptr, "X-Progress-ID="))) {
304 /* perhaps the POST request is using the query-string to pass the X-Progress-ID */
305 id += sizeof("X-Progress-ID=")-1;
306 } else {
307 /*(path-info is not known at this point in request)*/
308 id = con->uri.path->ptr;
309 len = buffer_string_length(con->uri.path);
310 if (len >= 33 && id[len-33] == '/') {
311 id += len - 32;
312 pathinfo = 1;
313 } else {
314 return HANDLER_GO_ON;
318 /* the request has to contain a 32byte ID */
319 for (len = 0; light_isxdigit(id[len]); ++len) ;
320 if (len != 32) {
321 if (!pathinfo) { /*(reduce false positive noise in error log)*/
322 log_error_write(srv, __FILE__, __LINE__, "ss",
323 "invalid progress-id; non-xdigit or len != 32:", id);
325 return HANDLER_GO_ON;
328 /* check if this is a POST request */
329 switch(con->request.http_method) {
330 case HTTP_METHOD_POST:
332 connection_map_insert(p->con_map, con, id, len);
334 return HANDLER_GO_ON;
335 case HTTP_METHOD_GET:
336 buffer_reset(con->physical.path);
338 con->file_started = 1;
339 con->file_finished = 1;
341 con->http_status = 200;
342 con->mode = DIRECT;
344 /* get the connection */
345 if (NULL == (post_con = connection_map_get_connection(p->con_map, id, len))) {
346 log_error_write(srv, __FILE__, __LINE__, "ss",
347 "ID not known:", id);
349 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("not in progress"));
351 return HANDLER_FINISHED;
354 response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/xml"));
356 /* just an attempt the force the IE/proxies to NOT cache the request ... doesn't help :( */
357 response_header_overwrite(srv, con, CONST_STR_LEN("Pragma"), CONST_STR_LEN("no-cache"));
358 response_header_overwrite(srv, con, CONST_STR_LEN("Expires"), CONST_STR_LEN("Thu, 19 Nov 1981 08:52:00 GMT"));
359 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"));
361 b = buffer_init();
363 /* prepare XML */
364 buffer_copy_string_len(b, CONST_STR_LEN(
365 "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>"
366 "<upload>"
367 "<size>"));
368 buffer_append_int(b, post_con->request.content_length);
369 buffer_append_string_len(b, CONST_STR_LEN(
370 "</size>"
371 "<received>"));
372 buffer_append_int(b, post_con->request_content_queue->bytes_in);
373 buffer_append_string_len(b, CONST_STR_LEN(
374 "</received>"
375 "</upload>"));
377 #if 0
378 log_error_write(srv, __FILE__, __LINE__, "sb", "...", b);
379 #endif
381 chunkqueue_append_buffer(con->write_queue, b);
382 buffer_free(b);
384 return HANDLER_FINISHED;
385 default:
386 break;
389 return HANDLER_GO_ON;
392 REQUESTDONE_FUNC(mod_uploadprogress_request_done) {
393 plugin_data *p = p_d;
395 UNUSED(srv);
397 if (con->request.http_method != HTTP_METHOD_POST) return HANDLER_GO_ON;
398 if (buffer_string_is_empty(con->uri.path)) return HANDLER_GO_ON;
400 if (connection_map_remove_connection(p->con_map, con)) {
401 /* removed */
404 return HANDLER_GO_ON;
407 /* this function is called at dlopen() time and inits the callbacks */
409 int mod_uploadprogress_plugin_init(plugin *p);
410 int mod_uploadprogress_plugin_init(plugin *p) {
411 p->version = LIGHTTPD_VERSION_ID;
412 p->name = buffer_init_string("uploadprogress");
414 p->init = mod_uploadprogress_init;
415 p->handle_uri_clean = mod_uploadprogress_uri_handler;
416 p->connection_reset = mod_uploadprogress_request_done;
417 p->set_defaults = mod_uploadprogress_set_defaults;
418 p->cleanup = mod_uploadprogress_free;
420 p->data = NULL;
422 return 0;