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