[build_cmake] clock_gettime() -lrt w/ glibc < 2.17 (fixes #2737)
[lighttpd.git] / src / mod_uploadprogress.c
blob34e4457cd26d903097a1e21376afdd68167f4f26
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));
103 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, len;
280 data_string *ds;
281 buffer *b;
282 connection *post_con = NULL;
284 UNUSED(srv);
286 if (buffer_string_is_empty(con->uri.path)) return HANDLER_GO_ON;
287 switch(con->request.http_method) {
288 case HTTP_METHOD_GET:
289 case HTTP_METHOD_POST: break;
290 default: return HANDLER_GO_ON;
293 mod_uploadprogress_patch_connection(srv, con, p);
294 if (buffer_string_is_empty(p->conf.progress_url)) return HANDLER_GO_ON;
296 if (con->request.http_method == HTTP_METHOD_GET) {
297 if (!buffer_is_equal(con->uri.path, p->conf.progress_url)) {
298 return HANDLER_GO_ON;
302 /* the request has to contain a 32byte ID */
304 if (NULL == (ds = (data_string *)array_get_element(con->request.headers, "X-Progress-ID"))) {
305 if (!buffer_string_is_empty(con->uri.query)) {
306 /* perhaps the POST request is using the querystring to pass the X-Progress-ID */
307 b = con->uri.query;
308 } else {
309 return HANDLER_GO_ON;
311 } else {
312 b = ds->value;
315 len = buffer_string_length(b);
316 if (len != 32) {
317 log_error_write(srv, __FILE__, __LINE__, "sd",
318 "len of progress-id != 32:", len);
319 return HANDLER_GO_ON;
322 for (i = 0; i < len; i++) {
323 char c = b->ptr[i];
325 if (!light_isxdigit(c)) {
326 log_error_write(srv, __FILE__, __LINE__, "sb",
327 "non-xdigit in progress-id:", b);
328 return HANDLER_GO_ON;
332 /* check if this is a POST request */
333 switch(con->request.http_method) {
334 case HTTP_METHOD_POST:
336 connection_map_insert(p->con_map, con, b);
338 return HANDLER_GO_ON;
339 case HTTP_METHOD_GET:
340 buffer_reset(con->physical.path);
342 con->file_started = 1;
343 con->file_finished = 1;
345 con->http_status = 200;
346 con->mode = DIRECT;
348 /* get the connection */
349 if (NULL == (post_con = connection_map_get_connection(p->con_map, b))) {
350 log_error_write(srv, __FILE__, __LINE__, "sb",
351 "ID no known:", b);
353 chunkqueue_get_append_mem(con->write_queue, CONST_STR_LEN("not in progress"));
355 return HANDLER_FINISHED;
358 response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/xml"));
360 /* just an attempt the force the IE/proxies to NOT cache the request ... doesn't help :( */
361 response_header_overwrite(srv, con, CONST_STR_LEN("Pragma"), CONST_STR_LEN("no-cache"));
362 response_header_overwrite(srv, con, CONST_STR_LEN("Expires"), CONST_STR_LEN("Thu, 19 Nov 1981 08:52:00 GMT"));
363 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"));
365 b = buffer_init();
367 /* prepare XML */
368 buffer_copy_string_len(b, CONST_STR_LEN(
369 "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>"
370 "<upload>"
371 "<size>"));
372 buffer_append_int(b, post_con->request.content_length);
373 buffer_append_string_len(b, CONST_STR_LEN(
374 "</size>"
375 "<received>"));
376 buffer_append_int(b, post_con->request_content_queue->bytes_in);
377 buffer_append_string_len(b, CONST_STR_LEN(
378 "</received>"
379 "</upload>"));
381 #if 0
382 log_error_write(srv, __FILE__, __LINE__, "sb", "...", b);
383 #endif
385 chunkqueue_append_buffer(con->write_queue, b);
386 buffer_free(b);
388 return HANDLER_FINISHED;
389 default:
390 break;
393 return HANDLER_GO_ON;
396 REQUESTDONE_FUNC(mod_uploadprogress_request_done) {
397 plugin_data *p = p_d;
399 UNUSED(srv);
401 if (con->request.http_method != HTTP_METHOD_POST) return HANDLER_GO_ON;
402 if (buffer_string_is_empty(con->uri.path)) return HANDLER_GO_ON;
404 if (connection_map_remove_connection(p->con_map, con)) {
405 /* removed */
408 return HANDLER_GO_ON;
411 /* this function is called at dlopen() time and inits the callbacks */
413 int mod_uploadprogress_plugin_init(plugin *p);
414 int mod_uploadprogress_plugin_init(plugin *p) {
415 p->version = LIGHTTPD_VERSION_ID;
416 p->name = buffer_init_string("uploadprogress");
418 p->init = mod_uploadprogress_init;
419 p->handle_uri_clean = mod_uploadprogress_uri_handler;
420 p->handle_request_done = mod_uploadprogress_request_done;
421 p->handle_connection_close = mod_uploadprogress_request_done;
422 p->set_defaults = mod_uploadprogress_set_defaults;
423 p->cleanup = mod_uploadprogress_free;
425 p->data = NULL;
427 return 0;