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