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