2 * uhttpd - Tiny single-threaded httpd
4 * Copyright (C) 2010-2012 Jo-Philipp Wich <xm@subsignal.org>
5 * Copyright (C) 2012 Felix Fietkau <nbd@openwrt.org>
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
21 #define _XOPEN_SOURCE 700
23 #include <sys/types.h>
28 #include <libubox/blobmsg.h>
31 #include "mimetypes.h"
33 static LIST_HEAD(index_files
);
36 struct list_head list
;
51 HDR_IF_MODIFIED_SINCE
,
52 HDR_IF_UNMODIFIED_SINCE
,
59 void uh_index_add(const char *filename
)
61 struct index_file
*idx
;
63 idx
= calloc(1, sizeof(*idx
));
65 list_add_tail(&idx
->list
, &index_files
);
68 static char * canonpath(const char *path
, char *path_resolved
)
70 char path_copy
[PATH_MAX
];
71 char *path_cpy
= path_copy
;
72 char *path_res
= path_resolved
;
74 /* relative -> absolute */
76 getcwd(path_copy
, PATH_MAX
);
77 strncat(path_copy
, "/", PATH_MAX
- strlen(path_copy
));
78 strncat(path_copy
, path
, PATH_MAX
- strlen(path_copy
));
80 strncpy(path_copy
, path
, PATH_MAX
);
84 while ((*path_cpy
!= '\0') && (path_cpy
< (path_copy
+ PATH_MAX
- 2))) {
88 /* skip repeating / */
89 if (path_cpy
[1] == '/') {
95 if (path_cpy
[1] == '.') {
97 if ((path_cpy
[2] == '/') || (path_cpy
[2] == '\0')) {
102 /* collapse /x/../ */
103 if ((path_cpy
[2] == '.') &&
104 ((path_cpy
[3] == '/') || (path_cpy
[3] == '\0'))) {
105 while ((path_res
> path_resolved
) && (*--path_res
!= '/'));
113 *path_res
++ = *path_cpy
++;
116 /* remove trailing slash if not root / */
117 if ((path_res
> (path_resolved
+1)) && (path_res
[-1] == '/'))
119 else if (path_res
== path_resolved
)
124 return path_resolved
;
127 /* Returns NULL on error.
128 ** NB: improperly encoded URL should give client 400 [Bad Syntax]; returning
129 ** NULL here causes 404 [Not Found], but that's not too unreasonable. */
130 static struct path_info
*
131 uh_path_lookup(struct client
*cl
, const char *url
)
133 static char path_phys
[PATH_MAX
];
134 static char path_info
[PATH_MAX
];
135 static struct path_info p
;
137 char buffer
[UH_LIMIT_MSGHEAD
];
138 char *docroot
= conf
.docroot
;
139 char *pathptr
= NULL
;
142 int no_sym
= conf
.no_symlinks
;
145 struct index_file
*idx
;
147 /* back out early if url is undefined */
151 memset(path_phys
, 0, sizeof(path_phys
));
152 memset(path_info
, 0, sizeof(path_info
));
153 memset(buffer
, 0, sizeof(buffer
));
154 memset(&p
, 0, sizeof(p
));
157 memcpy(buffer
, docroot
,
158 min(strlen(docroot
), sizeof(buffer
) - 1));
160 /* separate query string from url */
161 if ((pathptr
= strchr(url
, '?')) != NULL
) {
162 p
.query
= pathptr
[1] ? pathptr
+ 1 : NULL
;
164 /* urldecode component w/o query */
166 if (uh_urldecode(&buffer
[strlen(docroot
)],
167 sizeof(buffer
) - strlen(docroot
) - 1,
168 url
, pathptr
- url
) < 0)
169 return NULL
; /* bad URL */
173 /* no query string, decode all of url */
174 else if (uh_urldecode(&buffer
[strlen(docroot
)],
175 sizeof(buffer
) - strlen(docroot
) - 1,
176 url
, strlen(url
) ) < 0)
177 return NULL
; /* bad URL */
179 /* create canon path */
180 for (i
= strlen(buffer
), slash
= (buffer
[max(0, i
-1)] == '/'); i
>= 0; i
--) {
181 if ((buffer
[i
] == 0) || (buffer
[i
] == '/')) {
182 memset(path_info
, 0, sizeof(path_info
));
183 memcpy(path_info
, buffer
, min(i
+ 1, sizeof(path_info
) - 1));
185 if (no_sym
? realpath(path_info
, path_phys
)
186 : canonpath(path_info
, path_phys
)) {
187 memset(path_info
, 0, sizeof(path_info
));
188 memcpy(path_info
, &buffer
[i
],
189 min(strlen(buffer
) - i
, sizeof(path_info
) - 1));
196 /* check whether found path is within docroot */
197 if (strncmp(path_phys
, docroot
, strlen(docroot
)) ||
198 ((path_phys
[strlen(docroot
)] != 0) &&
199 (path_phys
[strlen(docroot
)] != '/')))
202 /* test current path */
203 if (!stat(path_phys
, &p
.stat
)) {
204 /* is a regular file */
205 if (p
.stat
.st_mode
& S_IFREG
) {
208 p
.name
= &path_phys
[strlen(docroot
)];
209 p
.info
= path_info
[0] ? path_info
: NULL
;
213 else if ((p
.stat
.st_mode
& S_IFDIR
) && !strlen(path_info
)) {
214 /* ensure trailing slash */
215 if (path_phys
[strlen(path_phys
)-1] != '/')
216 path_phys
[strlen(path_phys
)] = '/';
218 /* try to locate index file */
219 memset(buffer
, 0, sizeof(buffer
));
220 memcpy(buffer
, path_phys
, sizeof(buffer
));
221 pathptr
= &buffer
[strlen(buffer
)];
223 /* if requested url resolves to a directory and a trailing slash
224 is missing in the request url, redirect the client to the same
225 url with trailing slash appended */
227 uh_http_header(cl
, 302, "Found");
228 ustream_printf(cl
->us
, "Location: %s%s%s\r\n\r\n",
229 &path_phys
[strlen(docroot
)],
231 p
.query
? p
.query
: "");
235 list_for_each_entry(idx
, &index_files
, list
) {
236 strncat(buffer
, idx
->name
, sizeof(buffer
));
238 if (!stat(buffer
, &s
) && (s
.st_mode
& S_IFREG
)) {
239 memcpy(path_phys
, buffer
, sizeof(path_phys
));
240 memcpy(&p
.stat
, &s
, sizeof(p
.stat
));
250 p
.name
= &path_phys
[strlen(docroot
)];
254 return p
.phys
? &p
: NULL
;
258 time_t timegm (struct tm
*tm
);
261 static const char * uh_file_mime_lookup(const char *path
)
263 struct mimetype
*m
= &uh_mime_types
[0];
267 e
= &path
[strlen(path
)-1];
270 if ((*e
== '.' || *e
== '/') && !strcasecmp(&e
[1], m
->extn
))
279 return "application/octet-stream";
282 static const char * uh_file_mktag(struct stat
*s
)
284 static char tag
[128];
286 snprintf(tag
, sizeof(tag
), "\"%x-%x-%x\"",
287 (unsigned int) s
->st_ino
,
288 (unsigned int) s
->st_size
,
289 (unsigned int) s
->st_mtime
);
294 static time_t uh_file_date2unix(const char *date
)
298 memset(&t
, 0, sizeof(t
));
300 if (strptime(date
, "%a, %d %b %Y %H:%M:%S %Z", &t
) != NULL
)
306 static char * uh_file_unix2date(time_t ts
)
308 static char str
[128];
309 struct tm
*t
= gmtime(&ts
);
311 strftime(str
, sizeof(str
), "%a, %d %b %Y %H:%M:%S GMT", t
);
316 static char *uh_file_header(struct client
*cl
, int idx
)
318 if (!cl
->dispatch
.file
.hdr
[idx
])
321 return (char *) blobmsg_data(cl
->dispatch
.file
.hdr
[idx
]);
324 static void uh_file_response_ok_hdrs(struct client
*cl
, struct stat
*s
)
327 ustream_printf(cl
->us
, "ETag: %s\r\n", uh_file_mktag(s
));
328 ustream_printf(cl
->us
, "Last-Modified: %s\r\n",
329 uh_file_unix2date(s
->st_mtime
));
331 ustream_printf(cl
->us
, "Date: %s\r\n", uh_file_unix2date(time(NULL
)));
334 static void uh_file_response_200(struct client
*cl
, struct stat
*s
)
336 uh_http_header(cl
, 200, "OK");
337 return uh_file_response_ok_hdrs(cl
, s
);
340 static void uh_file_response_304(struct client
*cl
, struct stat
*s
)
342 uh_http_header(cl
, 304, "Not Modified");
344 return uh_file_response_ok_hdrs(cl
, s
);
347 static void uh_file_response_412(struct client
*cl
)
349 uh_http_header(cl
, 412, "Precondition Failed");
352 static bool uh_file_if_match(struct client
*cl
, struct stat
*s
)
354 const char *tag
= uh_file_mktag(s
);
355 char *hdr
= uh_file_header(cl
, HDR_IF_MATCH
);
363 for (i
= 0; i
< strlen(hdr
); i
++)
365 if ((hdr
[i
] == ' ') || (hdr
[i
] == ',')) {
368 } else if (!strcmp(p
, "*") || !strcmp(p
, tag
)) {
373 uh_file_response_412(cl
);
377 static int uh_file_if_modified_since(struct client
*cl
, struct stat
*s
)
379 char *hdr
= uh_file_header(cl
, HDR_IF_MODIFIED_SINCE
);
384 if (uh_file_date2unix(hdr
) >= s
->st_mtime
) {
385 uh_file_response_304(cl
, s
);
392 static int uh_file_if_none_match(struct client
*cl
, struct stat
*s
)
394 const char *tag
= uh_file_mktag(s
);
395 char *hdr
= uh_file_header(cl
, HDR_IF_NONE_MATCH
);
403 for (i
= 0; i
< strlen(hdr
); i
++) {
404 if ((hdr
[i
] == ' ') || (hdr
[i
] == ',')) {
407 } else if (!strcmp(p
, "*") || !strcmp(p
, tag
)) {
408 if ((cl
->request
.method
== UH_HTTP_MSG_GET
) ||
409 (cl
->request
.method
== UH_HTTP_MSG_HEAD
))
410 uh_file_response_304(cl
, s
);
412 uh_file_response_412(cl
);
421 static int uh_file_if_range(struct client
*cl
, struct stat
*s
)
423 char *hdr
= uh_file_header(cl
, HDR_IF_RANGE
);
426 uh_file_response_412(cl
);
433 static int uh_file_if_unmodified_since(struct client
*cl
, struct stat
*s
)
435 char *hdr
= uh_file_header(cl
, HDR_IF_UNMODIFIED_SINCE
);
437 if (hdr
&& uh_file_date2unix(hdr
) <= s
->st_mtime
) {
438 uh_file_response_412(cl
);
446 static int uh_file_scandir_filter_dir(const struct dirent
*e
)
448 return strcmp(e
->d_name
, ".") ? 1 : 0;
451 static void uh_file_dirlist(struct client
*cl
, struct path_info
*pi
)
455 char filename
[PATH_MAX
];
457 struct dirent
**files
= NULL
;
460 uh_file_response_200(cl
, NULL
);
461 ustream_printf(cl
->us
, "Content-Type: text/html\r\n\r\n");
464 "<html><head><title>Index of %s</title></head>"
465 "<body><h1>Index of %s</h1><hr /><ol>",
468 if ((count
= scandir(pi
->phys
, &files
, uh_file_scandir_filter_dir
,
471 memset(filename
, 0, sizeof(filename
));
472 memcpy(filename
, pi
->phys
, sizeof(filename
));
473 pathptr
= &filename
[strlen(filename
)];
476 for (i
= 0; i
< count
; i
++) {
477 strncat(filename
, files
[i
]->d_name
,
478 sizeof(filename
) - strlen(files
[i
]->d_name
));
480 if (!stat(filename
, &s
) &&
481 (s
.st_mode
& S_IFDIR
) && (s
.st_mode
& S_IXOTH
))
483 "<li><strong><a href='%s%s'>%s</a>/"
484 "</strong><br /><small>modified: %s"
485 "<br />directory - %.02f kbyte<br />"
486 "<br /></small></li>",
487 pi
->name
, files
[i
]->d_name
,
489 uh_file_unix2date(s
.st_mtime
),
496 for (i
= 0; i
< count
; i
++) {
497 strncat(filename
, files
[i
]->d_name
,
498 sizeof(filename
) - strlen(files
[i
]->d_name
));
500 if (!stat(filename
, &s
) &&
501 !(s
.st_mode
& S_IFDIR
) && (s
.st_mode
& S_IROTH
))
503 "<li><strong><a href='%s%s'>%s</a>"
504 "</strong><br /><small>modified: %s"
505 "<br />%s - %.02f kbyte<br />"
506 "<br /></small></li>",
507 pi
->name
, files
[i
]->d_name
,
509 uh_file_unix2date(s
.st_mtime
),
510 uh_file_mime_lookup(filename
),
517 uh_chunk_printf(cl
, "</ol><hr /></body></html>");
522 for (i
= 0; i
< count
; i
++)
529 static void file_write_cb(struct client
*cl
)
532 int fd
= cl
->dispatch
.file
.fd
;
535 while (cl
->us
->w
.data_bytes
< 256) {
536 r
= read(fd
, buf
, sizeof(buf
));
547 uh_chunk_write(cl
, buf
, r
);
551 static void uh_file_free(struct client
*cl
)
553 close(cl
->dispatch
.file
.fd
);
556 static void uh_file_data(struct client
*cl
, struct path_info
*pi
, int fd
)
558 /* test preconditions */
559 if (!uh_file_if_modified_since(cl
, &pi
->stat
) ||
560 !uh_file_if_match(cl
, &pi
->stat
) ||
561 !uh_file_if_range(cl
, &pi
->stat
) ||
562 !uh_file_if_unmodified_since(cl
, &pi
->stat
) ||
563 !uh_file_if_none_match(cl
, &pi
->stat
)) {
570 uh_file_response_200(cl
, &pi
->stat
);
572 ustream_printf(cl
->us
, "Content-Type: %s\r\n",
573 uh_file_mime_lookup(pi
->name
));
575 ustream_printf(cl
->us
, "Content-Length: %i\r\n\r\n",
580 if (cl
->request
.method
== UH_HTTP_MSG_HEAD
) {
586 cl
->dispatch
.file
.fd
= fd
;
587 cl
->dispatch
.write_cb
= file_write_cb
;
588 cl
->dispatch
.free
= uh_file_free
;
589 cl
->dispatch
.close_fds
= uh_file_free
;
593 static void uh_file_request(struct client
*cl
, struct path_info
*pi
, const char *url
)
595 static const struct blobmsg_policy hdr_policy
[__HDR_MAX
] = {
596 [HDR_IF_MODIFIED_SINCE
] = { "if-modified-since", BLOBMSG_TYPE_STRING
},
597 [HDR_IF_UNMODIFIED_SINCE
] = { "if-unmodified-since", BLOBMSG_TYPE_STRING
},
598 [HDR_IF_MATCH
] = { "if-match", BLOBMSG_TYPE_STRING
},
599 [HDR_IF_NONE_MATCH
] = { "if-none-match", BLOBMSG_TYPE_STRING
},
600 [HDR_IF_RANGE
] = { "if-range", BLOBMSG_TYPE_STRING
},
602 struct blob_attr
*tb
[__HDR_MAX
];
605 blobmsg_parse(hdr_policy
, __HDR_MAX
, tb
, blob_data(cl
->hdr
.head
), blob_len(cl
->hdr
.head
));
607 cl
->dispatch
.file
.hdr
= tb
;
609 if (!(pi
->stat
.st_mode
& S_IROTH
))
612 if (pi
->stat
.st_mode
& S_IFREG
) {
613 fd
= open(pi
->phys
, O_RDONLY
);
617 uh_file_data(cl
, pi
, fd
);
618 } else if ((pi
->stat
.st_mode
& S_IFDIR
)) {
619 if (conf
.no_dirlists
)
622 uh_file_dirlist(cl
, pi
);
630 uh_client_error(cl
, 403, "Forbidden",
631 "You don't have permission to access %s on this server.",
635 static bool __handle_file_request(struct client
*cl
, const char *url
)
637 struct path_info
*pi
;
639 pi
= uh_path_lookup(cl
, url
);
643 if (!pi
->redirected
) {
644 uh_file_request(cl
, pi
, url
);
645 cl
->dispatch
.file
.hdr
= NULL
;
651 void uh_handle_file_request(struct client
*cl
)
653 if (__handle_file_request(cl
, cl
->request
.url
) ||
654 __handle_file_request(cl
, conf
.error_handler
))
657 uh_client_error(cl
, 404, "Not Found", "The requested URL %s was not found on this server.", cl
->request
.url
);