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