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