api-v3: First draft
[src/app-framework-binder.git] / src / afb-hreq.c
1 /*
2  * Copyright (C) 2016, 2017, 2018 "IoT.bzh"
3  * Author: José Bollo <jose.bollo@iot.bzh>
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *   http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17
18 #define _GNU_SOURCE
19
20 #include <stdlib.h>
21 #include <stdio.h>
22 #include <string.h>
23 #include <assert.h>
24 #include <errno.h>
25 #include <fcntl.h>
26 #include <sys/stat.h>
27
28 #include <microhttpd.h>
29 #include <json-c/json.h>
30
31 #if defined(USE_MAGIC_MIME_TYPE)
32 #include <magic.h>
33 #endif
34
35 #include "afb-method.h"
36 #include "afb-msg-json.h"
37 #include "afb-context.h"
38 #include "afb-hreq.h"
39 #include "afb-session.h"
40 #include "afb-cred.h"
41 #include "verbose.h"
42 #include "locale-root.h"
43
44 #define SIZE_RESPONSE_BUFFER   8192
45
46 static int global_reqids = 0;
47
48 static char empty_string[] = "";
49
50 static const char long_key_for_uuid[] = "x-afb-uuid";
51 static const char short_key_for_uuid[] = "uuid";
52
53 static const char long_key_for_token[] = "x-afb-token";
54 static const char short_key_for_token[] = "token";
55
56 static const char long_key_for_reqid[] = "x-afb-reqid";
57 static const char short_key_for_reqid[] = "reqid";
58
59 static char *cookie_name = NULL;
60 static char *cookie_setter = NULL;
61 static char *tmp_pattern = NULL;
62
63 /*
64  * Structure for storing key/values read from POST requests
65  */
66 struct hreq_data {
67         struct hreq_data *next; /* chain to next data */
68         char *key;              /* key name */
69         size_t length;          /* length of the value (used for appending) */
70         char *value;            /* the value (or original filename) */
71         char *path;             /* path of the file saved */
72 };
73
74 static struct json_object *req_json(struct afb_xreq *xreq);
75 static struct afb_arg req_get(struct afb_xreq *xreq, const char *name);
76 static void req_reply(struct afb_xreq *xreq, struct json_object *object, const char *error, const char *info);
77 static void req_destroy(struct afb_xreq *xreq);
78
79 const struct afb_xreq_query_itf afb_hreq_xreq_query_itf = {
80         .json = req_json,
81         .get = req_get,
82         .reply = req_reply,
83         .unref = req_destroy
84 };
85
86 static struct hreq_data *get_data(struct afb_hreq *hreq, const char *key, int create)
87 {
88         struct hreq_data *data = hreq->data;
89         while (data != NULL) {
90                 if (!strcasecmp(data->key, key))
91                         return data;
92                 data = data->next;
93         }
94         if (create) {
95                 data = calloc(1, sizeof *data);
96                 if (data != NULL) {
97                         data->key = strdup(key);
98                         if (data->key == NULL) {
99                                 free(data);
100                                 data = NULL;
101                         } else {
102                                 data->next = hreq->data;
103                                 hreq->data = data;
104                         }
105                 }
106         }
107         return data;
108 }
109
110 /* a valid subpath is a relative path not looking deeper than root using .. */
111 static int validsubpath(const char *subpath)
112 {
113         int l = 0, i = 0;
114
115         while (subpath[i]) {
116                 switch (subpath[i++]) {
117                 case '.':
118                         if (!subpath[i])
119                                 break;
120                         if (subpath[i] == '/') {
121                                 i++;
122                                 break;
123                         }
124                         if (subpath[i++] == '.') {
125                                 if (!subpath[i]) {
126                                         if (--l < 0)
127                                                 return 0;
128                                         break;
129                                 }
130                                 if (subpath[i++] == '/') {
131                                         if (--l < 0)
132                                                 return 0;
133                                         break;
134                                 }
135                         }
136                 default:
137                         while (subpath[i] && subpath[i] != '/')
138                                 i++;
139                         l++;
140                 case '/':
141                         break;
142                 }
143         }
144         return 1;
145 }
146
147 static void afb_hreq_reply_v(struct afb_hreq *hreq, unsigned status, struct MHD_Response *response, va_list args)
148 {
149         char *cookie;
150         const char *k, *v;
151
152         if (hreq->replied != 0)
153                 return;
154
155         k = va_arg(args, const char *);
156         while (k != NULL) {
157                 v = va_arg(args, const char *);
158                 MHD_add_response_header(response, k, v);
159                 k = va_arg(args, const char *);
160         }
161         v = afb_context_sent_uuid(&hreq->xreq.context);
162         if (v != NULL && asprintf(&cookie, cookie_setter, v) > 0) {
163                 MHD_add_response_header(response, MHD_HTTP_HEADER_SET_COOKIE, cookie);
164                 free(cookie);
165         }
166         MHD_queue_response(hreq->connection, status, response);
167         MHD_destroy_response(response);
168
169         hreq->replied = 1;
170         if (hreq->suspended != 0) {
171                 extern void run_micro_httpd(struct afb_hsrv *hsrv);
172                 MHD_resume_connection (hreq->connection);
173                 hreq->suspended = 0;
174                 run_micro_httpd(hreq->hsrv);
175         }
176 }
177
178 void afb_hreq_reply(struct afb_hreq *hreq, unsigned status, struct MHD_Response *response, ...)
179 {
180         va_list args;
181         va_start(args, response);
182         afb_hreq_reply_v(hreq, status, response, args);
183         va_end(args);
184 }
185
186 void afb_hreq_reply_empty(struct afb_hreq *hreq, unsigned status, ...)
187 {
188         va_list args;
189         va_start(args, status);
190         afb_hreq_reply_v(hreq, status, MHD_create_response_from_buffer(0, NULL, MHD_RESPMEM_PERSISTENT), args);
191         va_end(args);
192 }
193
194 void afb_hreq_reply_static(struct afb_hreq *hreq, unsigned status, size_t size, const char *buffer, ...)
195 {
196         va_list args;
197         va_start(args, buffer);
198         afb_hreq_reply_v(hreq, status, MHD_create_response_from_buffer((unsigned)size, (char*)buffer, MHD_RESPMEM_PERSISTENT), args);
199         va_end(args);
200 }
201
202 void afb_hreq_reply_copy(struct afb_hreq *hreq, unsigned status, size_t size, const char *buffer, ...)
203 {
204         va_list args;
205         va_start(args, buffer);
206         afb_hreq_reply_v(hreq, status, MHD_create_response_from_buffer((unsigned)size, (char*)buffer, MHD_RESPMEM_MUST_COPY), args);
207         va_end(args);
208 }
209
210 void afb_hreq_reply_free(struct afb_hreq *hreq, unsigned status, size_t size, char *buffer, ...)
211 {
212         va_list args;
213         va_start(args, buffer);
214         afb_hreq_reply_v(hreq, status, MHD_create_response_from_buffer((unsigned)size, buffer, MHD_RESPMEM_MUST_FREE), args);
215         va_end(args);
216 }
217
218 #if defined(USE_MAGIC_MIME_TYPE)
219
220 #if !defined(MAGIC_DB)
221 #define MAGIC_DB "/usr/share/misc/magic.mgc"
222 #endif
223
224 static magic_t lazy_libmagic()
225 {
226         static int done = 0;
227         static magic_t result = NULL;
228
229         if (!done) {
230                 done = 1;
231                 /* MAGIC_MIME tells magic to return a mime of the file,
232                          but you can specify different things */
233                 INFO("Loading mimetype default magic database");
234                 result = magic_open(MAGIC_MIME_TYPE);
235                 if (result == NULL) {
236                         ERROR("unable to initialize magic library");
237                 }
238                 /* Warning: should not use NULL for DB
239                                 [libmagic bug wont pass efence check] */
240                 else if (magic_load(result, MAGIC_DB) != 0) {
241                         ERROR("cannot load magic database: %s", magic_error(result));
242                         magic_close(result);
243                         result = NULL;
244                 }
245         }
246
247         return result;
248 }
249
250 static const char *magic_mimetype_fd(int fd)
251 {
252         magic_t lib = lazy_libmagic();
253         return lib ? magic_descriptor(lib, fd) : NULL;
254 }
255
256 #endif
257
258 static const char *mimetype_fd_name(int fd, const char *filename)
259 {
260         const char *result = NULL;
261
262 #if defined(INFER_EXTENSION)
263         /*
264          * Set some well-known extensions
265          * Note that it is mandatory for example for css files in order to provide
266          * right mimetype that must be text/css (otherwise chrome browser will not
267          * load correctly css file) while libmagic returns text/plain.
268          */
269         const char *extension = strrchr(filename, '.');
270         if (extension) {
271                 static const char *const known[][2] = {
272                         /* keep it sorted for dichotomic search */
273                         { ".css",       "text/css" },
274                         { ".gif",       "image/gif" },
275                         { ".html",      "text/html" },
276                         { ".htm",       "text/html" },
277                         { ".ico",       "image/x-icon"},
278                         { ".jpeg",      "image/jpeg" },
279                         { ".jpg",       "image/jpeg" },
280                         { ".js",        "text/javascript" },
281                         { ".json",      "application/json" },
282                         { ".mp3",       "audio/mpeg" },
283                         { ".png",       "image/png" },
284                         { ".svg",       "image/svg+xml" },
285                         { ".ttf",       "application/x-font-ttf"},
286                         { ".txt",       "text/plain" },
287                         { ".wav",       "audio/x-wav" },
288                         { ".xht",       "application/xhtml+xml" },
289                         { ".xhtml",     "application/xhtml+xml" },
290                         { ".xml",       "application/xml" }
291                 };
292                 int i, c, l = 0, u = sizeof known / sizeof *known;
293                 while (l < u) {
294                         i = (l + u) >> 1;
295                         c = strcasecmp(extension, known[i][0]);
296                         if (!c) {
297                                 result = known[i][1];
298                                 break;
299                         }
300                         if (c < 0)
301                                 u = i;
302                         else
303                                 l = i + 1;
304                 }
305         }
306 #endif
307 #if defined(USE_MAGIC_MIME_TYPE)
308         if (result == NULL)
309                 result = magic_mimetype_fd(fd);
310 #endif
311         return result;
312 }
313
314 static void req_destroy(struct afb_xreq *xreq)
315 {
316         struct afb_hreq *hreq = CONTAINER_OF_XREQ(struct afb_hreq, xreq);
317         struct hreq_data *data;
318
319         if (hreq->postform != NULL)
320                 MHD_destroy_post_processor(hreq->postform);
321         for (data = hreq->data; data; data = hreq->data) {
322                 hreq->data = data->next;
323                 if (data->path) {
324                         unlink(data->path);
325                         free(data->path);
326                 }
327                 free(data->key);
328                 free(data->value);
329                 free(data);
330         }
331         afb_context_disconnect(&hreq->xreq.context);
332         json_object_put(hreq->json);
333         free((char*)hreq->xreq.request.called_api);
334         free((char*)hreq->xreq.request.called_verb);
335         afb_cred_unref(hreq->xreq.cred);
336         free(hreq);
337 }
338
339 void afb_hreq_addref(struct afb_hreq *hreq)
340 {
341         afb_xreq_unhooked_addref(&hreq->xreq);
342 }
343
344 void afb_hreq_unref(struct afb_hreq *hreq)
345 {
346         if (hreq->replied)
347                 hreq->xreq.replied = 1;
348         afb_xreq_unhooked_unref(&hreq->xreq);
349 }
350
351 /*
352  * Removes the 'prefix' of 'length' from the tail of 'hreq'
353  * if and only if the prefix exists and is terminated by a leading
354  * slash
355  */
356 int afb_hreq_unprefix(struct afb_hreq *hreq, const char *prefix, size_t length)
357 {
358         /* check the prefix ? */
359         if (length > hreq->lentail || (hreq->tail[length] && hreq->tail[length] != '/')
360             || strncasecmp(prefix, hreq->tail, length))
361                 return 0;
362
363         /* removes successives / */
364         while (length < hreq->lentail && hreq->tail[length + 1] == '/')
365                 length++;
366
367         /* update the tail */
368         hreq->lentail -= length;
369         hreq->tail += length;
370         return 1;
371 }
372
373 int afb_hreq_valid_tail(struct afb_hreq *hreq)
374 {
375         return validsubpath(hreq->tail);
376 }
377
378 void afb_hreq_reply_error(struct afb_hreq *hreq, unsigned int status)
379 {
380         afb_hreq_reply_empty(hreq, status, NULL);
381 }
382
383 int afb_hreq_redirect_to_ending_slash_if_needed(struct afb_hreq *hreq)
384 {
385         char *tourl;
386
387         if (hreq->url[hreq->lenurl - 1] == '/')
388                 return 0;
389
390         /* the redirect is needed for reliability of relative path */
391         tourl = alloca(hreq->lenurl + 2);
392         memcpy(tourl, hreq->url, hreq->lenurl);
393         tourl[hreq->lenurl] = '/';
394         tourl[hreq->lenurl + 1] = 0;
395         afb_hreq_redirect_to(hreq, tourl, 1);
396         return 1;
397 }
398
399 int afb_hreq_reply_file_if_exist(struct afb_hreq *hreq, int dirfd, const char *filename)
400 {
401         int rc;
402         int fd;
403         unsigned int status;
404         struct stat st;
405         char etag[1 + 2 * 8];
406         const char *inm;
407         struct MHD_Response *response;
408         const char *mimetype;
409
410         /* Opens the file or directory */
411         if (filename[0]) {
412                 fd = openat(dirfd, filename, O_RDONLY);
413                 if (fd < 0) {
414                         if (errno == ENOENT)
415                                 return 0;
416                         afb_hreq_reply_error(hreq, MHD_HTTP_FORBIDDEN);
417                         return 1;
418                 }
419         } else {
420                 fd = dup(dirfd);
421                 if (fd < 0) {
422                         afb_hreq_reply_error(hreq, MHD_HTTP_INTERNAL_SERVER_ERROR);
423                         return 1;
424                 }
425         }
426
427         /* Retrieves file's status */
428         if (fstat(fd, &st) != 0) {
429                 close(fd);
430                 afb_hreq_reply_error(hreq, MHD_HTTP_INTERNAL_SERVER_ERROR);
431                 return 1;
432         }
433
434         /* serve directory */
435         if (S_ISDIR(st.st_mode)) {
436                 rc = afb_hreq_redirect_to_ending_slash_if_needed(hreq);
437                 if (rc == 0) {
438                         static const char *indexes[] = { "index.html", NULL };
439                         int i = 0;
440                         while (indexes[i] != NULL) {
441                                 if (faccessat(fd, indexes[i], R_OK, 0) == 0) {
442                                         rc = afb_hreq_reply_file_if_exist(hreq, fd, indexes[i]);
443                                         break;
444                                 }
445                                 i++;
446                         }
447                 }
448                 close(fd);
449                 return rc;
450         }
451
452         /* Don't serve special files */
453         if (!S_ISREG(st.st_mode)) {
454                 close(fd);
455                 afb_hreq_reply_error(hreq, MHD_HTTP_FORBIDDEN);
456                 return 1;
457         }
458
459         /* Check the method */
460         if ((hreq->method & (afb_method_get | afb_method_head)) == 0) {
461                 close(fd);
462                 afb_hreq_reply_error(hreq, MHD_HTTP_METHOD_NOT_ALLOWED);
463                 return 1;
464         }
465
466         /* computes the etag */
467         sprintf(etag, "%08X%08X", ((int)(st.st_mtim.tv_sec) ^ (int)(st.st_mtim.tv_nsec)), (int)(st.st_size));
468
469         /* checks the etag */
470         inm = MHD_lookup_connection_value(hreq->connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_IF_NONE_MATCH);
471         if (inm && 0 == strcmp(inm, etag)) {
472                 /* etag ok, return NOT MODIFIED */
473                 close(fd);
474                 DEBUG("Not Modified: [%s]", filename);
475                 response = MHD_create_response_from_buffer(0, empty_string, MHD_RESPMEM_PERSISTENT);
476                 status = MHD_HTTP_NOT_MODIFIED;
477         } else {
478                 /* check the size */
479                 if (st.st_size != (off_t) (size_t) st.st_size) {
480                         close(fd);
481                         afb_hreq_reply_error(hreq, MHD_HTTP_INTERNAL_SERVER_ERROR);
482                         return 1;
483                 }
484
485                 /* create the response */
486                 response = MHD_create_response_from_fd((size_t) st.st_size, fd);
487                 status = MHD_HTTP_OK;
488
489                 /* set the type */
490                 mimetype = mimetype_fd_name(fd, filename);
491                 if (mimetype != NULL)
492                         MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, mimetype);
493         }
494
495         /* fills the value and send */
496         afb_hreq_reply(hreq, status, response,
497                         MHD_HTTP_HEADER_CACHE_CONTROL, hreq->cacheTimeout,
498                         MHD_HTTP_HEADER_ETAG, etag,
499                         NULL);
500         return 1;
501 }
502
503 int afb_hreq_reply_file(struct afb_hreq *hreq, int dirfd, const char *filename)
504 {
505         int rc = afb_hreq_reply_file_if_exist(hreq, dirfd, filename);
506         if (rc == 0)
507                 afb_hreq_reply_error(hreq, MHD_HTTP_NOT_FOUND);
508         return 1;
509 }
510
511 int afb_hreq_reply_locale_file_if_exist(struct afb_hreq *hreq, struct locale_search *search, const char *filename)
512 {
513         int rc;
514         int fd;
515         unsigned int status;
516         struct stat st;
517         char etag[1 + 2 * 8];
518         const char *inm;
519         struct MHD_Response *response;
520         const char *mimetype;
521
522         /* Opens the file or directory */
523         fd = locale_search_open(search, filename[0] ? filename : ".", O_RDONLY);
524         if (fd < 0) {
525                 if (errno == ENOENT)
526                         return 0;
527                 afb_hreq_reply_error(hreq, MHD_HTTP_FORBIDDEN);
528                 return 1;
529         }
530
531         /* Retrieves file's status */
532         if (fstat(fd, &st) != 0) {
533                 close(fd);
534                 afb_hreq_reply_error(hreq, MHD_HTTP_INTERNAL_SERVER_ERROR);
535                 return 1;
536         }
537
538         /* serve directory */
539         if (S_ISDIR(st.st_mode)) {
540                 rc = afb_hreq_redirect_to_ending_slash_if_needed(hreq);
541                 if (rc == 0) {
542                         static const char *indexes[] = { "index.html", NULL };
543                         int i = 0;
544                         size_t length = strlen(filename);
545                         char *extname = alloca(length + 30); /* 30 is enough to old data of indexes */
546                         memcpy(extname, filename, length);
547                         if (length && extname[length - 1] != '/')
548                                 extname[length++] = '/';
549                         while (rc == 0 && indexes[i] != NULL) {
550                                 strcpy(extname + length, indexes[i++]);
551                                 rc = afb_hreq_reply_locale_file_if_exist(hreq, search, extname);
552                         }
553                 }
554                 close(fd);
555                 return rc;
556         }
557
558         /* Don't serve special files */
559         if (!S_ISREG(st.st_mode)) {
560                 close(fd);
561                 afb_hreq_reply_error(hreq, MHD_HTTP_FORBIDDEN);
562                 return 1;
563         }
564
565         /* Check the method */
566         if ((hreq->method & (afb_method_get | afb_method_head)) == 0) {
567                 close(fd);
568                 afb_hreq_reply_error(hreq, MHD_HTTP_METHOD_NOT_ALLOWED);
569                 return 1;
570         }
571
572         /* computes the etag */
573         sprintf(etag, "%08X%08X", ((int)(st.st_mtim.tv_sec) ^ (int)(st.st_mtim.tv_nsec)), (int)(st.st_size));
574
575         /* checks the etag */
576         inm = MHD_lookup_connection_value(hreq->connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_IF_NONE_MATCH);
577         if (inm && 0 == strcmp(inm, etag)) {
578                 /* etag ok, return NOT MODIFIED */
579                 close(fd);
580                 DEBUG("Not Modified: [%s]", filename);
581                 response = MHD_create_response_from_buffer(0, empty_string, MHD_RESPMEM_PERSISTENT);
582                 status = MHD_HTTP_NOT_MODIFIED;
583         } else {
584                 /* check the size */
585                 if (st.st_size != (off_t) (size_t) st.st_size) {
586                         close(fd);
587                         afb_hreq_reply_error(hreq, MHD_HTTP_INTERNAL_SERVER_ERROR);
588                         return 1;
589                 }
590
591                 /* create the response */
592                 response = MHD_create_response_from_fd((size_t) st.st_size, fd);
593                 status = MHD_HTTP_OK;
594
595                 /* set the type */
596                 mimetype = mimetype_fd_name(fd, filename);
597                 if (mimetype != NULL)
598                         MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, mimetype);
599         }
600
601         /* fills the value and send */
602         afb_hreq_reply(hreq, status, response,
603                         MHD_HTTP_HEADER_CACHE_CONTROL, hreq->cacheTimeout,
604                         MHD_HTTP_HEADER_ETAG, etag,
605                         NULL);
606         return 1;
607 }
608
609 int afb_hreq_reply_locale_file(struct afb_hreq *hreq, struct locale_search *search, const char *filename)
610 {
611         int rc = afb_hreq_reply_locale_file_if_exist(hreq, search, filename);
612         if (rc == 0)
613                 afb_hreq_reply_error(hreq, MHD_HTTP_NOT_FOUND);
614         return 1;
615 }
616
617 struct _mkq_ {
618         int count;
619         size_t length;
620         size_t alloc;
621         char *text;
622 };
623
624 static void _mkq_add_(struct _mkq_ *mkq, char value)
625 {
626         char *text = mkq->text;
627         if (text != NULL) {
628                 if (mkq->length == mkq->alloc) {
629                         mkq->alloc += 100;
630                         text = realloc(text, mkq->alloc);
631                         if (text == NULL) {
632                                 free(mkq->text);
633                                 mkq->text = NULL;
634                                 return;
635                         }
636                         mkq->text = text;
637                 }
638                 text[mkq->length++] = value;
639         }
640 }
641
642 static void _mkq_add_hex_(struct _mkq_ *mkq, char value)
643 {
644         _mkq_add_(mkq, (char)(value < 10 ? value + '0' : value + 'A' - 10));
645 }
646
647 static void _mkq_add_esc_(struct _mkq_ *mkq, char value)
648 {
649         _mkq_add_(mkq, '%');
650         _mkq_add_hex_(mkq, (char)((value >> 4) & 15));
651         _mkq_add_hex_(mkq, (char)(value & 15));
652 }
653
654 static void _mkq_add_char_(struct _mkq_ *mkq, char value)
655 {
656         if (value <= ' ' || value >= 127)
657                 _mkq_add_esc_(mkq, value);
658         else
659                 switch(value) {
660                 case '=':
661                 case '&':
662                 case '%':
663                         _mkq_add_esc_(mkq, value);
664                         break;
665                 default:
666                         _mkq_add_(mkq, value);
667                 }
668 }
669
670 static void _mkq_append_(struct _mkq_ *mkq, const char *value)
671 {
672         while(*value)
673                 _mkq_add_char_(mkq, *value++);
674 }
675
676 static int _mkquery_(struct _mkq_ *mkq, enum MHD_ValueKind kind, const char *key, const char *value)
677 {
678         _mkq_add_(mkq, mkq->count++ ? '&' : '?');
679         _mkq_append_(mkq, key);
680         if (value != NULL) {
681                 _mkq_add_(mkq, '=');
682                 _mkq_append_(mkq, value);
683         }
684         return 1;
685 }
686
687 static char *url_with_query(struct afb_hreq *hreq, const char *url)
688 {
689         struct _mkq_ mkq;
690
691         mkq.count = 0;
692         mkq.length = strlen(url);
693         mkq.alloc = mkq.length + 1000;
694         mkq.text = malloc(mkq.alloc);
695         if (mkq.text != NULL) {
696                 strcpy(mkq.text, url);
697                 MHD_get_connection_values(hreq->connection, MHD_GET_ARGUMENT_KIND, (void*)_mkquery_, &mkq);
698                 _mkq_add_(&mkq, 0);
699         }
700         return mkq.text;
701 }
702
703 void afb_hreq_redirect_to(struct afb_hreq *hreq, const char *url, int add_query_part)
704 {
705         const char *to;
706         char *wqp;
707
708         wqp = add_query_part ? url_with_query(hreq, url) : NULL;
709         to = wqp ? : url;
710         afb_hreq_reply_static(hreq, MHD_HTTP_MOVED_PERMANENTLY, 0, NULL,
711                         MHD_HTTP_HEADER_LOCATION, to, NULL);
712         DEBUG("redirect from [%s] to [%s]", hreq->url, url);
713         free(wqp);
714 }
715
716 const char *afb_hreq_get_cookie(struct afb_hreq *hreq, const char *name)
717 {
718         return MHD_lookup_connection_value(hreq->connection, MHD_COOKIE_KIND, name);
719 }
720
721 const char *afb_hreq_get_argument(struct afb_hreq *hreq, const char *name)
722 {
723         struct hreq_data *data = get_data(hreq, name, 0);
724         return data ? data->value : MHD_lookup_connection_value(hreq->connection, MHD_GET_ARGUMENT_KIND, name);
725 }
726
727 const char *afb_hreq_get_header(struct afb_hreq *hreq, const char *name)
728 {
729         return MHD_lookup_connection_value(hreq->connection, MHD_HEADER_KIND, name);
730 }
731
732 int afb_hreq_post_add(struct afb_hreq *hreq, const char *key, const char *data, size_t size)
733 {
734         void *p;
735         struct hreq_data *hdat = get_data(hreq, key, 1);
736         if (hdat->path != NULL) {
737                 return 0;
738         }
739         p = realloc(hdat->value, hdat->length + size + 1);
740         if (p == NULL) {
741                 return 0;
742         }
743         hdat->value = p;
744         memcpy(&hdat->value[hdat->length], data, size);
745         hdat->length += size;
746         hdat->value[hdat->length] = 0;
747         return 1;
748 }
749
750 int afb_hreq_init_download_path(const char *directory)
751 {
752         struct stat st;
753         size_t n;
754         char *p;
755
756         if (access(directory, R_OK|W_OK)) {
757                 /* no read/write access */
758                 return -1;
759         }
760         if (stat(directory, &st)) {
761                 /* can't get info */
762                 return -1;
763         }
764         if (!S_ISDIR(st.st_mode)) {
765                 /* not a directory */
766                 errno = ENOTDIR;
767                 return -1;
768         }
769         n = strlen(directory);
770         while(n > 1 && directory[n-1] == '/') n--;
771         p = malloc(n + 8);
772         if (p == NULL) {
773                 /* can't allocate memory */
774                 errno = ENOMEM;
775                 return -1;
776         }
777         memcpy(p, directory, n);
778         p[n++] = '/';
779         p[n++] = 'X';
780         p[n++] = 'X';
781         p[n++] = 'X';
782         p[n++] = 'X';
783         p[n++] = 'X';
784         p[n++] = 'X';
785         p[n] = 0;
786         free(tmp_pattern);
787         tmp_pattern = p;
788         return 0;
789 }
790
791 static int opentempfile(char **path)
792 {
793         int fd;
794         char *fname;
795
796         fname = strdup(tmp_pattern ? : "XXXXXX"); /* TODO improve the path */
797         if (fname == NULL)
798                 return -1;
799
800         fd = mkostemp(fname, O_CLOEXEC|O_WRONLY);
801         if (fd < 0)
802                 free(fname);
803         else
804                 *path = fname;
805         return fd;
806 }
807
808 int afb_hreq_post_add_file(struct afb_hreq *hreq, const char *key, const char *file, const char *data, size_t size)
809 {
810         int fd;
811         ssize_t sz;
812         struct hreq_data *hdat = get_data(hreq, key, 1);
813
814         if (hdat->value == NULL) {
815                 hdat->value = strdup(file);
816                 if (hdat->value == NULL)
817                         return 0;
818                 fd = opentempfile(&hdat->path);
819         } else if (strcmp(hdat->value, file) || hdat->path == NULL) {
820                 return 0;
821         } else {
822                 fd = open(hdat->path, O_WRONLY|O_APPEND);
823         }
824         if (fd < 0)
825                 return 0;
826         while (size) {
827                 sz = write(fd, data, size);
828                 if (sz >= 0) {
829                         hdat->length += (size_t)sz;
830                         size -= (size_t)sz;
831                         data += sz;
832                 } else if (errno != EINTR)
833                         break;
834         }
835         close(fd);
836         return !size;
837 }
838
839 static struct afb_arg req_get(struct afb_xreq *xreq, const char *name)
840 {
841         const char *value;
842         struct afb_hreq *hreq = CONTAINER_OF_XREQ(struct afb_hreq, xreq);
843         struct hreq_data *hdat = get_data(hreq, name, 0);
844         if (hdat)
845                 return (struct afb_arg){
846                         .name = hdat->key,
847                         .value = hdat->value,
848                         .path = hdat->path
849                 };
850
851         value = MHD_lookup_connection_value(hreq->connection, MHD_GET_ARGUMENT_KIND, name);
852         return (struct afb_arg){
853                 .name = value == NULL ? NULL : name,
854                 .value = value,
855                 .path = NULL
856         };
857 }
858
859 static int _iterargs_(struct json_object *obj, enum MHD_ValueKind kind, const char *key, const char *value)
860 {
861         json_object_object_add(obj, key, value ? json_object_new_string(value) : NULL);
862         return 1;
863 }
864
865 static struct json_object *req_json(struct afb_xreq *xreq)
866 {
867         struct hreq_data *hdat;
868         struct json_object *obj, *val;
869         struct afb_hreq *hreq = CONTAINER_OF_XREQ(struct afb_hreq, xreq);
870
871         obj = hreq->json;
872         if (obj == NULL) {
873                 hreq->json = obj = json_object_new_object();
874                 if (obj == NULL) {
875                 } else {
876                         MHD_get_connection_values (hreq->connection, MHD_GET_ARGUMENT_KIND, (void*)_iterargs_, obj);
877                         for (hdat = hreq->data ; hdat ; hdat = hdat->next) {
878                                 if (hdat->path == NULL)
879                                         val = hdat->value ? json_object_new_string(hdat->value) : NULL;
880                                 else {
881                                         val = json_object_new_object();
882                                         if (val == NULL) {
883                                         } else {
884                                                 json_object_object_add(val, "file", json_object_new_string(hdat->value));
885                                                 json_object_object_add(val, "path", json_object_new_string(hdat->path));
886                                         }
887                                 }
888                                 json_object_object_add(obj, hdat->key, val);
889                         }
890                 }
891         }
892         return obj;
893 }
894
895 static ssize_t send_json_cb(json_object *obj, uint64_t pos, char *buf, size_t max)
896 {
897         ssize_t len = stpncpy(buf, json_object_to_json_string_ext(obj, JSON_C_TO_STRING_PLAIN)+pos, max) - buf;
898         return len ? : (ssize_t)MHD_CONTENT_READER_END_OF_STREAM;
899 }
900
901 static void req_reply(struct afb_xreq *xreq, struct json_object *object, const char *error, const char *info)
902 {
903         struct afb_hreq *hreq = CONTAINER_OF_XREQ(struct afb_hreq, xreq);
904         struct json_object *sub, *reply;
905         const char *reqid;
906         struct MHD_Response *response;
907
908         /* create the reply */
909         reply = afb_msg_json_reply(object, error, info, &xreq->context);
910
911         /* append the req id on need */
912         reqid = afb_hreq_get_argument(hreq, long_key_for_reqid);
913         if (reqid == NULL)
914                 reqid = afb_hreq_get_argument(hreq, short_key_for_reqid);
915         if (reqid != NULL && json_object_object_get_ex(reply, "request", &sub))
916                 json_object_object_add(sub, "reqid", json_object_new_string(reqid));
917
918         response = MHD_create_response_from_callback((uint64_t)strlen(json_object_to_json_string_ext(reply, JSON_C_TO_STRING_PLAIN)), SIZE_RESPONSE_BUFFER, (void*)send_json_cb, reply, (void*)json_object_put);
919         afb_hreq_reply(hreq, MHD_HTTP_OK, response, NULL);
920 }
921
922 void afb_hreq_call(struct afb_hreq *hreq, struct afb_apiset *apiset, const char *api, size_t lenapi, const char *verb, size_t lenverb)
923 {
924         hreq->xreq.request.called_api = strndup(api, lenapi);
925         hreq->xreq.request.called_verb = strndup(verb, lenverb);
926         if (hreq->xreq.request.called_api == NULL || hreq->xreq.request.called_verb == NULL) {
927                 ERROR("Out of memory");
928                 afb_hreq_reply_error(hreq, MHD_HTTP_INTERNAL_SERVER_ERROR);
929         } else if (afb_hreq_init_context(hreq) < 0) {
930                 afb_hreq_reply_error(hreq, MHD_HTTP_INTERNAL_SERVER_ERROR);
931         } else {
932                 afb_xreq_unhooked_addref(&hreq->xreq);
933                 afb_xreq_process(&hreq->xreq, apiset);
934         }
935 }
936
937 int afb_hreq_init_context(struct afb_hreq *hreq)
938 {
939         const char *uuid;
940         const char *token;
941
942         if (hreq->xreq.context.session != NULL)
943                 return 0;
944
945         uuid = afb_hreq_get_header(hreq, long_key_for_uuid);
946         if (uuid == NULL)
947                 uuid = afb_hreq_get_argument(hreq, long_key_for_uuid);
948         if (uuid == NULL)
949                 uuid = afb_hreq_get_cookie(hreq, cookie_name);
950         if (uuid == NULL)
951                 uuid = afb_hreq_get_argument(hreq, short_key_for_uuid);
952
953         token = afb_hreq_get_header(hreq, long_key_for_token);
954         if (token == NULL)
955                 token = afb_hreq_get_argument(hreq, long_key_for_token);
956         if (token == NULL)
957                 token = afb_hreq_get_argument(hreq, short_key_for_token);
958
959         return afb_context_connect(&hreq->xreq.context, uuid, token);
960 }
961
962 int afb_hreq_init_cookie(int port, const char *path, int maxage)
963 {
964         int rc;
965
966         free(cookie_name);
967         free(cookie_setter);
968         cookie_name = NULL;
969         cookie_setter = NULL;
970
971         path = path ? : "/";
972         rc = asprintf(&cookie_name, "%s-%d", long_key_for_uuid, port);
973         if (rc < 0)
974                 return 0;
975         rc = asprintf(&cookie_setter, "%s=%%s; Path=%s; Max-Age=%d; HttpOnly",
976                         cookie_name, path, maxage);
977         if (rc < 0)
978                 return 0;
979         return 1;
980 }
981
982 struct afb_xreq *afb_hreq_to_xreq(struct afb_hreq *hreq)
983 {
984         return &hreq->xreq;
985 }
986
987 struct afb_hreq *afb_hreq_create()
988 {
989         struct afb_hreq *hreq = calloc(1, sizeof *hreq);
990         if (hreq) {
991                 /* init the request */
992                 afb_xreq_init(&hreq->xreq, &afb_hreq_xreq_query_itf);
993                 hreq->reqid = ++global_reqids;
994         }
995         return hreq;
996 }
997