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