2 * Copyright 2016 IoT.bzh
3 * Author: José Bollo <jose.bollo@iot.bzh>
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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 #include <microhttpd.h>
24 #include "../include/local-def.h"
25 #include "afb-method.h"
26 #include "afb-req-itf.h"
29 static char empty_string[] = "";
32 struct hreq_data *next;
39 static const struct afb_req_itf afb_hreq_itf = {
40 .argument = (void*)afb_hreq_get_argument,
41 .is_argument_file = (void*)afb_hreq_is_argument_a_file,
42 .iterate_arguments = (void*)afb_hreq_iterate_arguments
45 static struct hreq_data *get_data(struct afb_hreq *hreq, const char *key, int create)
47 struct hreq_data *data = hreq->data;
50 while (data != NULL) {
51 if (!strcasecmp(data->key, key))
56 data = calloc(1, sizeof *data);
58 data->key = strdup(key);
59 if (data->key == NULL) {
63 data->next = hreq->data;
71 /* a valid subpath is a relative path not looking deeper than root using .. */
72 static int validsubpath(const char *subpath)
77 switch (subpath[i++]) {
81 if (subpath[i] == '/') {
85 if (subpath[i++] == '.') {
91 if (subpath[i++] == '/') {
98 while (subpath[i] && subpath[i] != '/')
109 * Removes the 'prefix' of 'length' frome the tail of 'hreq'
110 * if and only if the prefix exists and is terminated by a leading
113 int afb_hreq_unprefix(struct afb_hreq *hreq, const char *prefix, size_t length)
115 /* check the prefix ? */
116 if (length > hreq->lentail || (hreq->tail[length] && hreq->tail[length] != '/')
117 || strncasecmp(prefix, hreq->tail, length))
120 /* removes successives / */
121 while (length < hreq->lentail && hreq->tail[length + 1] == '/')
124 /* update the tail */
125 hreq->lentail -= length;
126 hreq->tail += length;
130 int afb_hreq_valid_tail(struct afb_hreq *hreq)
132 return validsubpath(hreq->tail);
135 void afb_hreq_reply_error(struct afb_hreq *hreq, unsigned int status)
139 struct MHD_Response *response;
141 length = asprintf(&buffer, "<html><body>error %u</body></html>", status);
143 response = MHD_create_response_from_buffer((unsigned)length, buffer, MHD_RESPMEM_MUST_FREE);
145 buffer = "<html><body>error</body></html>";
146 response = MHD_create_response_from_buffer(strlen(buffer), buffer, MHD_RESPMEM_PERSISTENT);
148 if (!MHD_queue_response(hreq->connection, status, response))
149 fprintf(stderr, "Failed to reply error code %u", status);
150 MHD_destroy_response(response);
153 int afb_hreq_reply_file_if_exist(struct afb_hreq *hreq, int dirfd, const char *filename)
159 char etag[1 + 2 * sizeof(int)];
161 struct MHD_Response *response;
163 /* Opens the file or directory */
164 fd = openat(dirfd, filename, O_RDONLY);
168 afb_hreq_reply_error(hreq, MHD_HTTP_FORBIDDEN);
172 /* Retrieves file's status */
173 if (fstat(fd, &st) != 0) {
175 afb_hreq_reply_error(hreq, MHD_HTTP_INTERNAL_SERVER_ERROR);
179 /* serve directory */
180 if (S_ISDIR(st.st_mode)) {
181 if (hreq->url[hreq->lenurl - 1] != '/') {
182 /* the redirect is needed for reliability of relative path */
183 char *tourl = alloca(hreq->lenurl + 2);
184 memcpy(tourl, hreq->url, hreq->lenurl);
185 tourl[hreq->lenurl] = '/';
186 tourl[hreq->lenurl + 1] = 0;
187 rc = afb_hreq_redirect_to(hreq, tourl);
189 rc = afb_hreq_reply_file_if_exist(hreq, fd, "index.html");
195 /* Don't serve special files */
196 if (!S_ISREG(st.st_mode)) {
198 afb_hreq_reply_error(hreq, MHD_HTTP_FORBIDDEN);
202 /* Check the method */
203 if ((hreq->method & (afb_method_get | afb_method_head)) == 0) {
205 afb_hreq_reply_error(hreq, MHD_HTTP_METHOD_NOT_ALLOWED);
209 /* computes the etag */
210 sprintf(etag, "%08X%08X", ((int)(st.st_mtim.tv_sec) ^ (int)(st.st_mtim.tv_nsec)), (int)(st.st_size));
212 /* checks the etag */
213 inm = MHD_lookup_connection_value(hreq->connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_IF_NONE_MATCH);
214 if (inm && 0 == strcmp(inm, etag)) {
215 /* etag ok, return NOT MODIFIED */
218 fprintf(stderr, "Not Modified: [%s]\n", filename);
219 response = MHD_create_response_from_buffer(0, empty_string, MHD_RESPMEM_PERSISTENT);
220 status = MHD_HTTP_NOT_MODIFIED;
223 if (st.st_size != (off_t) (size_t) st.st_size) {
225 afb_hreq_reply_error(hreq, MHD_HTTP_INTERNAL_SERVER_ERROR);
229 /* create the response */
230 response = MHD_create_response_from_fd((size_t) st.st_size, fd);
231 status = MHD_HTTP_OK;
233 #if defined(USE_MAGIC_MIME_TYPE)
235 if (hreq->session->magic) {
236 const char *mimetype = magic_descriptor(hreq->session->magic, fd);
237 if (mimetype != NULL)
238 MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, mimetype);
243 /* fills the value and send */
244 MHD_add_response_header(response, MHD_HTTP_HEADER_CACHE_CONTROL, hreq->session->cacheTimeout);
245 MHD_add_response_header(response, MHD_HTTP_HEADER_ETAG, etag);
246 MHD_queue_response(hreq->connection, status, response);
247 MHD_destroy_response(response);
251 int afb_hreq_reply_file(struct afb_hreq *hreq, int dirfd, const char *filename)
253 int rc = afb_hreq_reply_file_if_exist(hreq, dirfd, filename);
255 afb_hreq_reply_error(hreq, MHD_HTTP_NOT_FOUND);
259 int afb_hreq_redirect_to(struct afb_hreq *hreq, const char *url)
261 struct MHD_Response *response;
263 response = MHD_create_response_from_buffer(0, empty_string, MHD_RESPMEM_PERSISTENT);
264 MHD_add_response_header(response, MHD_HTTP_HEADER_LOCATION, url);
265 MHD_queue_response(hreq->connection, MHD_HTTP_MOVED_PERMANENTLY, response);
266 MHD_destroy_response(response);
268 fprintf(stderr, "redirect from [%s] to [%s]\n", hreq->url, url);
272 const char *afb_hreq_get_cookie(struct afb_hreq *hreq, const char *name)
274 return MHD_lookup_connection_value(hreq->connection, MHD_COOKIE_KIND, name);
277 const char *afb_hreq_get_argument(struct afb_hreq *hreq, const char *name)
279 struct hreq_data *data = get_data(hreq, name, 0);
280 return data ? data->value : MHD_lookup_connection_value(hreq->connection, MHD_GET_ARGUMENT_KIND, name);
283 const char *afb_hreq_get_header(struct afb_hreq *hreq, const char *name)
285 return MHD_lookup_connection_value(hreq->connection, MHD_HEADER_KIND, name);
288 void afb_hreq_post_end(struct afb_hreq *hreq)
290 struct hreq_data *data = hreq->data;
292 if (data->file > 0) {
300 int afb_hreq_post_add(struct afb_hreq *hreq, const char *key, const char *data, size_t size)
303 struct hreq_data *hdat = get_data(hreq, key, 1);
307 p = realloc(hdat->value, hdat->length + size + 1);
312 memcpy(&hdat->value[hdat->length], data, size);
313 hdat->length += size;
314 hdat->value[hdat->length] = 0;
318 int afb_hreq_post_add_file(struct afb_hreq *hreq, const char *key, const char *file, const char *data, size_t size)
320 struct hreq_data *hdat = get_data(hreq, key, 1);
322 /* continuation with reopening */
323 if (hdat->file < 0) {
324 hdat->file = open(hdat->value, O_WRONLY|O_APPEND);
325 if (hdat->file == 0) {
332 if (hdat->file > 0) {
333 write(hdat->file, data, size);
343 int afb_hreq_is_argument_a_file(struct afb_hreq *hreq, const char *key)
345 struct hreq_data *hdat = get_data(hreq, key, 0);
346 return hdat != NULL && hdat->file != 0;
350 struct afb_req afb_hreq_to_req(struct afb_hreq *hreq)
352 return (struct afb_req){ .itf = &afb_hreq_itf, .data = hreq };
357 struct afb_hreq *hreq;
358 int (*iterator)(void *closure, const char *key, const char *value, int isfile);
362 static int itargs(struct iterator_data *id, enum MHD_ValueKind kind, const char *key, const char *value)
364 if (get_data(id->hreq, key, 0))
366 return id->iterator(id->closure, key, value, 0);
369 void afb_hreq_iterate_arguments(struct afb_hreq *hreq, int (*iterator)(void *closure, const char *key, const char *value, int isfile), void *closure)
371 struct iterator_data id = { .hreq = hreq, .iterator = iterator, .closure = closure };
372 struct hreq_data *data = hreq->data;
374 if (!iterator(closure, data->key, data->value, !!data->file))
378 MHD_get_connection_values (hreq->connection, MHD_GET_ARGUMENT_KIND, (void*)itargs, &id);
381 void afb_hreq_drop_data(struct afb_hreq *hreq)
383 struct hreq_data *data = hreq->data;
385 hreq->data = data->next;