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