[mod_cgi] fix pipe_cloexec() when no O_CLOEXEC
[lighttpd.git] / src / mod_uploadprogress.c
blob3da05ba218987ee5b21bf816bf310fd2864ba397
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 <stdlib.h>
13 #include <string.h>
15 /**
16 * this is a uploadprogress for a lighttpd plugin
20 typedef struct {
21 buffer *con_id;
22 connection *con;
23 } connection_map_entry;
25 typedef struct {
26 connection_map_entry **ptr;
28 size_t used;
29 size_t size;
30 } connection_map;
32 /* plugin config for all request/connections */
34 typedef struct {
35 buffer *progress_url;
36 } plugin_config;
38 typedef struct {
39 PLUGIN_DATA;
41 connection_map *con_map;
43 plugin_config **config_storage;
45 plugin_config conf;
46 } plugin_data;
48 /**
50 * connection maps
54 /* init the plugin data */
55 static connection_map *connection_map_init() {
56 connection_map *cm;
58 cm = calloc(1, sizeof(*cm));
60 return cm;
63 static void connection_map_free(connection_map *cm) {
64 size_t i;
65 for (i = 0; i < cm->size; i++) {
66 connection_map_entry *cme = cm->ptr[i];
68 if (!cme) break;
70 if (cme->con_id) {
71 buffer_free(cme->con_id);
73 free(cme);
76 free(cm);
79 static int connection_map_insert(connection_map *cm, connection *con, buffer *con_id) {
80 connection_map_entry *cme;
81 size_t i;
83 if (cm->size == 0) {
84 cm->size = 16;
85 cm->ptr = malloc(cm->size * sizeof(*(cm->ptr)));
86 for (i = 0; i < cm->size; i++) {
87 cm->ptr[i] = NULL;
89 } else if (cm->used == cm->size) {
90 cm->size += 16;
91 cm->ptr = realloc(cm->ptr, cm->size * sizeof(*(cm->ptr)));
92 for (i = cm->used; i < cm->size; i++) {
93 cm->ptr[i] = NULL;
97 if (cm->ptr[cm->used]) {
98 /* is already alloced, just reuse it */
99 cme = cm->ptr[cm->used];
100 } else {
101 cme = malloc(sizeof(*cme));
102 cme->con_id = buffer_init();
104 buffer_copy_buffer(cme->con_id, con_id);
105 cme->con = con;
107 cm->ptr[cm->used++] = cme;
109 return 0;
112 static connection *connection_map_get_connection(connection_map *cm, buffer *con_id) {
113 size_t i;
115 for (i = 0; i < cm->used; i++) {
116 connection_map_entry *cme = cm->ptr[i];
118 if (buffer_is_equal(cme->con_id, con_id)) {
119 /* found connection */
121 return cme->con;
124 return NULL;
127 static int connection_map_remove_connection(connection_map *cm, connection *con) {
128 size_t i;
130 for (i = 0; i < cm->used; i++) {
131 connection_map_entry *cme = cm->ptr[i];
133 if (cme->con == con) {
134 /* found connection */
136 buffer_reset(cme->con_id);
137 cme->con = NULL;
139 cm->used--;
141 /* swap positions with the last entry */
142 if (cm->used) {
143 cm->ptr[i] = cm->ptr[cm->used];
144 cm->ptr[cm->used] = cme;
147 return 1;
151 return 0;
154 /* init the plugin data */
155 INIT_FUNC(mod_uploadprogress_init) {
156 plugin_data *p;
158 p = calloc(1, sizeof(*p));
160 p->con_map = connection_map_init();
162 return p;
165 /* detroy the plugin data */
166 FREE_FUNC(mod_uploadprogress_free) {
167 plugin_data *p = p_d;
169 UNUSED(srv);
171 if (!p) return HANDLER_GO_ON;
173 if (p->config_storage) {
174 size_t i;
175 for (i = 0; i < srv->config_context->used; i++) {
176 plugin_config *s = p->config_storage[i];
178 if (NULL == s) continue;
180 buffer_free(s->progress_url);
182 free(s);
184 free(p->config_storage);
187 connection_map_free(p->con_map);
189 free(p);
191 return HANDLER_GO_ON;
194 /* handle plugin config and check values */
196 SETDEFAULTS_FUNC(mod_uploadprogress_set_defaults) {
197 plugin_data *p = p_d;
198 size_t i = 0;
200 config_values_t cv[] = {
201 { "upload-progress.progress-url", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
202 { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
205 if (!p) return HANDLER_ERROR;
207 p->config_storage = calloc(1, srv->config_context->used * sizeof(plugin_config *));
209 for (i = 0; i < srv->config_context->used; i++) {
210 data_config const* config = (data_config const*)srv->config_context->data[i];
211 plugin_config *s;
213 s = calloc(1, sizeof(plugin_config));
214 s->progress_url = buffer_init();
216 cv[0].destination = s->progress_url;
218 p->config_storage[i] = s;
220 if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) {
221 return HANDLER_ERROR;
225 return HANDLER_GO_ON;
228 #define PATCH(x) \
229 p->conf.x = s->x;
230 static int mod_uploadprogress_patch_connection(server *srv, connection *con, plugin_data *p) {
231 size_t i, j;
232 plugin_config *s = p->config_storage[0];
234 PATCH(progress_url);
236 /* skip the first, the global context */
237 for (i = 1; i < srv->config_context->used; i++) {
238 data_config *dc = (data_config *)srv->config_context->data[i];
239 s = p->config_storage[i];
241 /* condition didn't match */
242 if (!config_check_cond(srv, con, dc)) continue;
244 /* merge config */
245 for (j = 0; j < dc->value->used; j++) {
246 data_unset *du = dc->value->data[j];
248 if (buffer_is_equal_string(du->key, CONST_STR_LEN("upload-progress.progress-url"))) {
249 PATCH(progress_url);
254 return 0;
256 #undef PATCH
260 * the idea:
262 * for the first request we check if it is a post-request
264 * if no, move out, don't care about them
266 * if yes, take the connection structure and register it locally
267 * in the progress-struct together with an session-id (md5 ... )
269 * if the connections closes, cleanup the entry in the progress-struct
271 * a second request can now get the info about the size of the upload,
272 * the received bytes
276 URIHANDLER_FUNC(mod_uploadprogress_uri_handler) {
277 plugin_data *p = p_d;
278 size_t i, len;
279 data_string *ds;
280 buffer *b;
281 connection *post_con = NULL;
283 UNUSED(srv);
285 if (buffer_string_is_empty(con->uri.path)) return HANDLER_GO_ON;
286 switch(con->request.http_method) {
287 case HTTP_METHOD_GET:
288 case HTTP_METHOD_POST: break;
289 default: return HANDLER_GO_ON;
292 mod_uploadprogress_patch_connection(srv, con, p);
293 if (buffer_string_is_empty(p->conf.progress_url)) return HANDLER_GO_ON;
295 if (con->request.http_method == HTTP_METHOD_GET) {
296 if (!buffer_is_equal(con->uri.path, p->conf.progress_url)) {
297 return HANDLER_GO_ON;
301 /* the request has to contain a 32byte ID */
303 if (NULL == (ds = (data_string *)array_get_element(con->request.headers, "X-Progress-ID"))) {
304 if (!buffer_string_is_empty(con->uri.query)) {
305 /* perhaps the POST request is using the querystring to pass the X-Progress-ID */
306 b = con->uri.query;
307 } else {
308 return HANDLER_GO_ON;
310 } else {
311 b = ds->value;
314 len = buffer_string_length(b);
315 if (len != 32) {
316 log_error_write(srv, __FILE__, __LINE__, "sd",
317 "len of progress-id != 32:", len);
318 return HANDLER_GO_ON;
321 for (i = 0; i < len; i++) {
322 char c = b->ptr[i];
324 if (!light_isxdigit(c)) {
325 log_error_write(srv, __FILE__, __LINE__, "sb",
326 "non-xdigit in progress-id:", b);
327 return HANDLER_GO_ON;
331 /* check if this is a POST request */
332 switch(con->request.http_method) {
333 case HTTP_METHOD_POST:
335 connection_map_insert(p->con_map, con, b);
337 return HANDLER_GO_ON;
338 case HTTP_METHOD_GET:
339 buffer_reset(con->physical.path);
341 con->file_started = 1;
342 con->file_finished = 1;
344 con->http_status = 200;
345 con->mode = DIRECT;
347 /* get the connection */
348 if (NULL == (post_con = connection_map_get_connection(p->con_map, b))) {
349 log_error_write(srv, __FILE__, __LINE__, "sb",
350 "ID no known:", b);
352 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("not in progress"));
354 return HANDLER_FINISHED;
357 response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/xml"));
359 /* just an attempt the force the IE/proxies to NOT cache the request ... doesn't help :( */
360 response_header_overwrite(srv, con, CONST_STR_LEN("Pragma"), CONST_STR_LEN("no-cache"));
361 response_header_overwrite(srv, con, CONST_STR_LEN("Expires"), CONST_STR_LEN("Thu, 19 Nov 1981 08:52:00 GMT"));
362 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"));
364 b = buffer_init();
366 /* prepare XML */
367 buffer_copy_string_len(b, CONST_STR_LEN(
368 "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>"
369 "<upload>"
370 "<size>"));
371 buffer_append_int(b, post_con->request.content_length);
372 buffer_append_string_len(b, CONST_STR_LEN(
373 "</size>"
374 "<received>"));
375 buffer_append_int(b, post_con->request_content_queue->bytes_in);
376 buffer_append_string_len(b, CONST_STR_LEN(
377 "</received>"
378 "</upload>"));
380 #if 0
381 log_error_write(srv, __FILE__, __LINE__, "sb", "...", b);
382 #endif
384 chunkqueue_append_buffer(con->write_queue, b);
385 buffer_free(b);
387 return HANDLER_FINISHED;
388 default:
389 break;
392 return HANDLER_GO_ON;
395 REQUESTDONE_FUNC(mod_uploadprogress_request_done) {
396 plugin_data *p = p_d;
398 UNUSED(srv);
400 if (con->request.http_method != HTTP_METHOD_POST) return HANDLER_GO_ON;
401 if (buffer_string_is_empty(con->uri.path)) return HANDLER_GO_ON;
403 if (connection_map_remove_connection(p->con_map, con)) {
404 /* removed */
407 return HANDLER_GO_ON;
410 /* this function is called at dlopen() time and inits the callbacks */
412 int mod_uploadprogress_plugin_init(plugin *p);
413 int mod_uploadprogress_plugin_init(plugin *p) {
414 p->version = LIGHTTPD_VERSION_ID;
415 p->name = buffer_init_string("uploadprogress");
417 p->init = mod_uploadprogress_init;
418 p->handle_uri_clean = mod_uploadprogress_uri_handler;
419 p->handle_request_done = mod_uploadprogress_request_done;
420 p->handle_connection_close = mod_uploadprogress_request_done;
421 p->set_defaults = mod_uploadprogress_set_defaults;
422 p->cleanup = mod_uploadprogress_free;
424 p->data = NULL;
426 return 0;