work in progress, session handling
[src/app-framework-binder.git] / src / afb-hreq.c
1 /*
2  * Copyright 2016 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
30 #include "local-def.h"
31 #include "afb-method.h"
32 #include "afb-req-itf.h"
33 #include "afb-hreq.h"
34 #include "session.h"
35
36 #define SIZE_RESPONSE_BUFFER   8000
37
38 static char empty_string[] = "";
39
40 static const char uuid_header[] = "x-afb-uuid";
41 static const char uuid_arg[] = "uuid";
42 static const char uuid_cookie[] = "uuid";
43
44 static const char token_header[] = "x-afb-token";
45 static const char token_arg[] = "token";
46 static const char token_cookie[] = "token";
47
48
49 struct hreq_data {
50         struct hreq_data *next;
51         char *key;
52         int file;
53         size_t length;
54         char *value;
55 };
56
57 static struct afb_arg req_get(struct afb_hreq *hreq, const char *name);
58 static void req_iterate(struct afb_hreq *hreq, int (*iterator)(void *closure, struct afb_arg arg), void *closure);
59 static void req_fail(struct afb_hreq *hreq, const char *status, const char *info);
60 static void req_success(struct afb_hreq *hreq, json_object *obj, const char *info);
61 static int req_session_create(struct afb_hreq *hreq);
62 static int req_session_check(struct afb_hreq *hreq, int refresh);
63 static void req_session_close(struct afb_hreq *hreq);
64
65 static const struct afb_req_itf afb_hreq_itf = {
66         .get = (void*)req_get,
67         .iterate = (void*)req_iterate,
68         .fail = (void*)req_fail,
69         .success = (void*)req_success,
70         .session_create = (void*)req_session_create,
71         .session_check = (void*)req_session_check,
72         .session_close = (void*)req_session_close
73 };
74
75 static struct hreq_data *get_data(struct afb_hreq *hreq, const char *key, int create)
76 {
77         struct hreq_data *data = hreq->data;
78         if (key == NULL)
79                 key = empty_string;
80         while (data != NULL) {
81                 if (!strcasecmp(data->key, key))
82                         return data;
83                 data = data->next;
84         }
85         if (create) {
86                 data = calloc(1, sizeof *data);
87                 if (data != NULL) {
88                         data->key = strdup(key);
89                         if (data->key == NULL) {
90                                 free(data);
91                                 data = NULL;
92                         } else {
93                                 data->next = hreq->data;
94                                 hreq->data = data;
95                         }
96                 }
97         }
98         return data;
99 }
100
101 /* a valid subpath is a relative path not looking deeper than root using .. */
102 static int validsubpath(const char *subpath)
103 {
104         int l = 0, i = 0;
105
106         while (subpath[i]) {
107                 switch (subpath[i++]) {
108                 case '.':
109                         if (!subpath[i])
110                                 break;
111                         if (subpath[i] == '/') {
112                                 i++;
113                                 break;
114                         }
115                         if (subpath[i++] == '.') {
116                                 if (!subpath[i]) {
117                                         if (--l < 0)
118                                                 return 0;
119                                         break;
120                                 }
121                                 if (subpath[i++] == '/') {
122                                         if (--l < 0)
123                                                 return 0;
124                                         break;
125                                 }
126                         }
127                 default:
128                         while (subpath[i] && subpath[i] != '/')
129                                 i++;
130                         l++;
131                 case '/':
132                         break;
133                 }
134         }
135         return 1;
136 }
137
138 void afb_hreq_free(struct afb_hreq *hreq)
139 {
140         struct hreq_data *data;
141         if (hreq != NULL) {
142                 if (hreq->postform != NULL)
143                         MHD_destroy_post_processor(hreq->postform);
144                 for (data = hreq->data; data; data = hreq->data) {
145                         hreq->data = data->next;
146                         free(data->key);
147                         free(data->value);
148                         free(data);
149                 }
150                 ctxClientPut(hreq->context);
151                 free(hreq);
152         }
153 }
154
155 /*
156  * Removes the 'prefix' of 'length' from the tail of 'hreq'
157  * if and only if the prefix exists and is terminated by a leading
158  * slash
159  */
160 int afb_hreq_unprefix(struct afb_hreq *hreq, const char *prefix, size_t length)
161 {
162         /* check the prefix ? */
163         if (length > hreq->lentail || (hreq->tail[length] && hreq->tail[length] != '/')
164             || strncasecmp(prefix, hreq->tail, length))
165                 return 0;
166
167         /* removes successives / */
168         while (length < hreq->lentail && hreq->tail[length + 1] == '/')
169                 length++;
170
171         /* update the tail */
172         hreq->lentail -= length;
173         hreq->tail += length;
174         return 1;
175 }
176
177 int afb_hreq_valid_tail(struct afb_hreq *hreq)
178 {
179         return validsubpath(hreq->tail);
180 }
181
182 void afb_hreq_reply_error(struct afb_hreq *hreq, unsigned int status)
183 {
184         char *buffer;
185         int length;
186         struct MHD_Response *response;
187
188         length = asprintf(&buffer, "<html><body>error %u</body></html>", status);
189         if (length > 0)
190                 response = MHD_create_response_from_buffer((unsigned)length, buffer, MHD_RESPMEM_MUST_FREE);
191         else {
192                 buffer = "<html><body>error</body></html>";
193                 response = MHD_create_response_from_buffer(strlen(buffer), buffer, MHD_RESPMEM_PERSISTENT);
194         }
195         if (!MHD_queue_response(hreq->connection, status, response))
196                 fprintf(stderr, "Failed to reply error code %u", status);
197         MHD_destroy_response(response);
198 }
199
200 int afb_hreq_reply_file_if_exist(struct afb_hreq *hreq, int dirfd, const char *filename)
201 {
202         int rc;
203         int fd;
204         unsigned int status;
205         struct stat st;
206         char etag[1 + 2 * sizeof(int)];
207         const char *inm;
208         struct MHD_Response *response;
209
210         /* Opens the file or directory */
211         if (filename[0]) {
212                 fd = openat(dirfd, filename, O_RDONLY);
213                 if (fd < 0) {
214                         if (errno == ENOENT)
215                                 return 0;
216                         afb_hreq_reply_error(hreq, MHD_HTTP_FORBIDDEN);
217                         return 1;
218                 }
219         } else {
220                 fd = dup(dirfd);
221                 if (fd < 0) {
222                         afb_hreq_reply_error(hreq, MHD_HTTP_INTERNAL_SERVER_ERROR);
223                         return 1;
224                 }
225         }
226
227         /* Retrieves file's status */
228         if (fstat(fd, &st) != 0) {
229                 close(fd);
230                 afb_hreq_reply_error(hreq, MHD_HTTP_INTERNAL_SERVER_ERROR);
231                 return 1;
232         }
233
234         /* serve directory */
235         if (S_ISDIR(st.st_mode)) {
236                 if (hreq->url[hreq->lenurl - 1] != '/') {
237                         /* the redirect is needed for reliability of relative path */
238                         char *tourl = alloca(hreq->lenurl + 2);
239                         memcpy(tourl, hreq->url, hreq->lenurl);
240                         tourl[hreq->lenurl] = '/';
241                         tourl[hreq->lenurl + 1] = 0;
242                         rc = afb_hreq_redirect_to(hreq, tourl);
243                 } else {
244                         rc = afb_hreq_reply_file_if_exist(hreq, fd, "index.html");
245                 }
246                 close(fd);
247                 return rc;
248         }
249
250         /* Don't serve special files */
251         if (!S_ISREG(st.st_mode)) {
252                 close(fd);
253                 afb_hreq_reply_error(hreq, MHD_HTTP_FORBIDDEN);
254                 return 1;
255         }
256
257         /* Check the method */
258         if ((hreq->method & (afb_method_get | afb_method_head)) == 0) {
259                 close(fd);
260                 afb_hreq_reply_error(hreq, MHD_HTTP_METHOD_NOT_ALLOWED);
261                 return 1;
262         }
263
264         /* computes the etag */
265         sprintf(etag, "%08X%08X", ((int)(st.st_mtim.tv_sec) ^ (int)(st.st_mtim.tv_nsec)), (int)(st.st_size));
266
267         /* checks the etag */
268         inm = MHD_lookup_connection_value(hreq->connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_IF_NONE_MATCH);
269         if (inm && 0 == strcmp(inm, etag)) {
270                 /* etag ok, return NOT MODIFIED */
271                 close(fd);
272                 if (verbose)
273                         fprintf(stderr, "Not Modified: [%s]\n", filename);
274                 response = MHD_create_response_from_buffer(0, empty_string, MHD_RESPMEM_PERSISTENT);
275                 status = MHD_HTTP_NOT_MODIFIED;
276         } else {
277                 /* check the size */
278                 if (st.st_size != (off_t) (size_t) st.st_size) {
279                         close(fd);
280                         afb_hreq_reply_error(hreq, MHD_HTTP_INTERNAL_SERVER_ERROR);
281                         return 1;
282                 }
283
284                 /* create the response */
285                 response = MHD_create_response_from_fd((size_t) st.st_size, fd);
286                 status = MHD_HTTP_OK;
287
288 #if defined(USE_MAGIC_MIME_TYPE)
289                 /* set the type */
290                 if (hreq->session->magic) {
291                         const char *mimetype = magic_descriptor(hreq->session->magic, fd);
292                         if (mimetype != NULL)
293                                 MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, mimetype);
294                 }
295 #endif
296         }
297
298         /* fills the value and send */
299         MHD_add_response_header(response, MHD_HTTP_HEADER_CACHE_CONTROL, hreq->session->cacheTimeout);
300         MHD_add_response_header(response, MHD_HTTP_HEADER_ETAG, etag);
301         MHD_queue_response(hreq->connection, status, response);
302         MHD_destroy_response(response);
303         return 1;
304 }
305
306 int afb_hreq_reply_file(struct afb_hreq *hreq, int dirfd, const char *filename)
307 {
308         int rc = afb_hreq_reply_file_if_exist(hreq, dirfd, filename);
309         if (rc == 0)
310                 afb_hreq_reply_error(hreq, MHD_HTTP_NOT_FOUND);
311         return 1;
312 }
313
314 int afb_hreq_redirect_to(struct afb_hreq *hreq, const char *url)
315 {
316         struct MHD_Response *response;
317
318         response = MHD_create_response_from_buffer(0, empty_string, MHD_RESPMEM_PERSISTENT);
319         MHD_add_response_header(response, MHD_HTTP_HEADER_LOCATION, url);
320         MHD_queue_response(hreq->connection, MHD_HTTP_MOVED_PERMANENTLY, response);
321         MHD_destroy_response(response);
322         if (verbose)
323                 fprintf(stderr, "redirect from [%s] to [%s]\n", hreq->url, url);
324         return 1;
325 }
326
327 const char *afb_hreq_get_cookie(struct afb_hreq *hreq, const char *name)
328 {
329         return MHD_lookup_connection_value(hreq->connection, MHD_COOKIE_KIND, name);
330 }
331
332 const char *afb_hreq_get_argument(struct afb_hreq *hreq, const char *name)
333 {
334         struct hreq_data *data = get_data(hreq, name, 0);
335         return data ? data->value : MHD_lookup_connection_value(hreq->connection, MHD_GET_ARGUMENT_KIND, name);
336 }
337
338 const char *afb_hreq_get_header(struct afb_hreq *hreq, const char *name)
339 {
340         return MHD_lookup_connection_value(hreq->connection, MHD_HEADER_KIND, name);
341 }
342
343 void afb_hreq_post_end(struct afb_hreq *hreq)
344 {
345         struct hreq_data *data = hreq->data;
346         while(data) {
347                 if (data->file > 0) {
348                         close(data->file);
349                         data->file = -1;
350                 }
351                 data = data->next;
352         }
353 }
354
355 int afb_hreq_post_add(struct afb_hreq *hreq, const char *key, const char *data, size_t size)
356 {
357         void *p;
358         struct hreq_data *hdat = get_data(hreq, key, 1);
359         if (hdat->file) {
360                 return 0;
361         }
362         p = realloc(hdat->value, hdat->length + size + 1);
363         if (p == NULL) {
364                 return 0;
365         }
366         hdat->value = p;
367         memcpy(&hdat->value[hdat->length], data, size);
368         hdat->length += size;
369         hdat->value[hdat->length] = 0;
370         return 1;
371 }
372
373 int afb_hreq_post_add_file(struct afb_hreq *hreq, const char *key, const char *file, const char *data, size_t size)
374 {
375         struct hreq_data *hdat = get_data(hreq, key, 1);
376
377         /* continuation with reopening */
378         if (hdat->file < 0) {
379                 hdat->file = open(hdat->value, O_WRONLY|O_APPEND);
380                 if (hdat->file == 0) {
381                         hdat->file = dup(0);
382                         close(0);
383                 }
384                 if (hdat->file <= 0)
385                         return 0;
386         }
387         if (hdat->file > 0) {
388                 write(hdat->file, data, size);
389                 return 1;
390         }
391
392         /* creation */
393         /* TODO */
394         return 0;
395         
396 }
397
398 int afb_hreq_is_argument_a_file(struct afb_hreq *hreq, const char *key)
399 {
400         struct hreq_data *hdat = get_data(hreq, key, 0);
401         return hdat != NULL && hdat->file != 0;
402 }
403
404
405 struct afb_req afb_hreq_to_req(struct afb_hreq *hreq)
406 {
407         return (struct afb_req){ .itf = &afb_hreq_itf, .data = hreq };
408 }
409
410 static struct afb_arg req_get(struct afb_hreq *hreq, const char *name)
411 {
412         struct hreq_data *hdat = get_data(hreq, name, 0);
413         if (hdat)
414                 return (struct afb_arg){
415                         .name = hdat->key,
416                         .value = hdat->value,
417                         .size = hdat->length,
418                         .is_file = (hdat->file != 0)
419                 };
420                 
421         return (struct afb_arg){
422                 .name = name,
423                 .value = MHD_lookup_connection_value(hreq->connection, MHD_GET_ARGUMENT_KIND, name),
424                 .size = 0,
425                 .is_file = 0
426         };
427 }
428
429 struct iterdata
430 {
431         struct afb_hreq *hreq;
432         int (*iterator)(void *closure, struct afb_arg arg);
433         void *closure;
434 };
435
436 static int _iterargs_(struct iterdata *id, enum MHD_ValueKind kind, const char *key, const char *value)
437 {
438         if (get_data(id->hreq, key, 0))
439                 return 1;
440         return id->iterator(id->closure, (struct afb_arg){
441                 .name = key,
442                 .value = value,
443                 .size = 0,
444                 .is_file = 0
445         });
446 }
447
448 static void req_iterate(struct afb_hreq *hreq, int (*iterator)(void *closure, struct afb_arg arg), void *closure)
449 {
450         struct iterdata id = { .hreq = hreq, .iterator = iterator, .closure = closure };
451         struct hreq_data *hdat = hreq->data;
452         while (hdat) {
453                 if (!iterator(closure, (struct afb_arg){
454                         .name = hdat->key,
455                         .value = hdat->value,
456                         .size = hdat->length,
457                         .is_file = (hdat->file != 0)}))
458                         return;
459                 hdat = hdat->next;
460         }
461         MHD_get_connection_values (hreq->connection, MHD_GET_ARGUMENT_KIND, (void*)_iterargs_, &id);
462 }
463
464 static ssize_t send_json_cb(json_object *obj, uint64_t pos, char *buf, size_t max)
465 {
466         ssize_t len = stpncpy(buf, json_object_to_json_string(obj)+pos, max) - buf;
467         return len ? : -1;
468 }
469
470 static void req_reply(struct afb_hreq *hreq, unsigned retcode, const char *status, const char *info, json_object *resp)
471 {
472         json_object *root, *request;
473         struct MHD_Response *response;
474
475         root = json_object_new_object();
476         json_object_object_add(root, "jtype", json_object_new_string("afb-reply"));
477         request = json_object_new_object();
478         json_object_object_add(root, "request", request);
479         json_object_object_add(request, "status", json_object_new_string(status));
480         if (info)
481                 json_object_object_add(request, "info", json_object_new_string(info));
482         if (resp)
483                 json_object_object_add(root, "response", resp);
484         if (hreq->context) {
485                 json_object_object_add(request, uuid_arg, json_object_new_string(hreq->context->uuid));
486                 json_object_object_add(request, token_arg, json_object_new_string(hreq->context->token));
487         }
488
489         response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, SIZE_RESPONSE_BUFFER, (void*)send_json_cb, root, (void*)json_object_put);
490         MHD_queue_response(hreq->connection, retcode, response);
491         MHD_destroy_response(response);
492 }
493
494 static void req_fail(struct afb_hreq *hreq, const char *status, const char *info)
495 {
496         req_reply(hreq, MHD_HTTP_OK, status, info, NULL);
497 }
498
499 static void req_success(struct afb_hreq *hreq, json_object *obj, const char *info)
500 {
501         req_reply(hreq, MHD_HTTP_OK, "success", info, obj);
502 }
503
504 struct AFB_clientCtx *afb_hreq_context(struct afb_hreq *hreq)
505 {
506         const char *uuid;
507
508         if (hreq->context == NULL) {
509                 uuid = afb_hreq_get_header(hreq, uuid_header);
510                 if (uuid == NULL)
511                         uuid = afb_hreq_get_argument(hreq, uuid_arg);
512                 if (uuid == NULL)
513                         uuid = afb_hreq_get_cookie(hreq, uuid_cookie);
514                 hreq->context = ctxClientGet(uuid);
515         }
516         return hreq->context;
517 }
518
519 static int req_session_create(struct afb_hreq *hreq)
520 {
521         struct AFB_clientCtx *context = afb_hreq_context(hreq);
522         if (context == NULL)
523                 return 0;
524         if (context->created)
525                 return 0;
526         return req_session_check(hreq, 1);
527 }
528
529 static int req_session_check(struct afb_hreq *hreq, int refresh)
530 {
531         const char *token;
532
533         struct AFB_clientCtx *context = afb_hreq_context(hreq);
534
535         if (context == NULL)
536                 return 0;
537
538         token = afb_hreq_get_header(hreq, token_header);
539         if (token == NULL)
540                 token = afb_hreq_get_argument(hreq, token_arg);
541         if (token == NULL)
542                 token = afb_hreq_get_cookie(hreq, token_cookie);
543         if (token == NULL)
544                 return 0;
545
546         if (!ctxTokenCheck (context, token))
547                 return 0;
548
549         if (refresh) {
550                 ctxTokenNew (context);
551         }
552
553         return 1;
554 }
555
556 static void req_session_close(struct afb_hreq *hreq)
557 {
558         struct AFB_clientCtx *context = afb_hreq_context(hreq);
559         if (context != NULL)
560                 ctxClientClose(context);
561 }
562
563
564