afb-hreq: creation of hreq classe
[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 'request'
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 *request, const char *prefix, size_t length)
74 {
75         /* check the prefix ? */
76         if (length > request->lentail || (request->tail[length] && request->tail[length] != '/')
77             || memcmp(prefix, request->tail, length))
78                 return 0;
79
80         /* removes successives / */
81         while (length < request->lentail && request->tail[length + 1] == '/')
82                 length++;
83
84         /* update the tail */
85         request->lentail -= length;
86         request->tail += length;
87         return 1;
88 }
89
90 int afb_hreq_valid_tail(struct afb_hreq *request)
91 {
92         return validsubpath(request->tail);
93 }
94
95 void afb_hreq_reply_error(struct afb_hreq *request, 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(request->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 *request, 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(request, 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(request, MHD_HTTP_INTERNAL_SERVER_ERROR);
136                 return 1;
137         }
138
139         /* Don't serve directory */
140         if (S_ISDIR(st.st_mode)) {
141                 rc = afb_hreq_reply_file_if_exist(request, fd, "index.html");
142                 close(fd);
143                 return rc;
144         }
145
146         /* Don't serve special files */
147         if (!S_ISREG(st.st_mode)) {
148                 close(fd);
149                 afb_hreq_reply_error(request, MHD_HTTP_FORBIDDEN);
150                 return 1;
151         }
152
153         /* Check the method */
154         if ((request->method & (afb_method_get | afb_method_head)) == 0) {
155                 close(fd);
156                 afb_hreq_reply_error(request, MHD_HTTP_METHOD_NOT_ALLOWED);
157                 return 1;
158         }
159
160         /* computes the etag */
161         sprintf(etag, "%08X%08X", ((int)(st.st_mtim.tv_sec) ^ (int)(st.st_mtim.tv_nsec)), (int)(st.st_size));
162
163         /* checks the etag */
164         inm = MHD_lookup_connection_value(request->connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_IF_NONE_MATCH);
165         if (inm && 0 == strcmp(inm, etag)) {
166                 /* etag ok, return NOT MODIFIED */
167                 close(fd);
168                 if (verbose)
169                         fprintf(stderr, "Not Modified: [%s]\n", filename);
170                 response = MHD_create_response_from_buffer(0, empty_string, MHD_RESPMEM_PERSISTENT);
171                 status = MHD_HTTP_NOT_MODIFIED;
172         } else {
173                 /* check the size */
174                 if (st.st_size != (off_t) (size_t) st.st_size) {
175                         close(fd);
176                         afb_hreq_reply_error(request, MHD_HTTP_INTERNAL_SERVER_ERROR);
177                         return 1;
178                 }
179
180                 /* create the response */
181                 response = MHD_create_response_from_fd((size_t) st.st_size, fd);
182                 status = MHD_HTTP_OK;
183
184 #if defined(USE_MAGIC_MIME_TYPE)
185                 /* set the type */
186                 if (request->session->magic) {
187                         const char *mimetype = magic_descriptor(request->session->magic, fd);
188                         if (mimetype != NULL)
189                                 MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, mimetype);
190                 }
191 #endif
192         }
193
194         /* fills the value and send */
195         MHD_add_response_header(response, MHD_HTTP_HEADER_CACHE_CONTROL, request->session->cacheTimeout);
196         MHD_add_response_header(response, MHD_HTTP_HEADER_ETAG, etag);
197         MHD_queue_response(request->connection, status, response);
198         MHD_destroy_response(response);
199         return 1;
200 }
201
202 int afb_hreq_reply_file(struct afb_hreq *request, int dirfd, const char *filename)
203 {
204         int rc = afb_hreq_reply_file_if_exist(request, dirfd, filename);
205         if (rc == 0)
206                 afb_hreq_reply_error(request, MHD_HTTP_NOT_FOUND);
207         return 1;
208 }
209
210 int afb_hreq_redirect_to(struct afb_hreq *request, const char *url)
211 {
212         struct MHD_Response *response;
213
214         response = MHD_create_response_from_buffer(0, empty_string, MHD_RESPMEM_PERSISTENT);
215         MHD_add_response_header(response, MHD_HTTP_HEADER_LOCATION, url);
216         MHD_queue_response(request->connection, MHD_HTTP_MOVED_PERMANENTLY, response);
217         MHD_destroy_response(response);
218         if (verbose)
219                 fprintf(stderr, "redirect from [%s] to [%s]\n", request->url, url);
220         return 1;
221 }
222