2f829f158ce78d81411cd08cc3dde109d1ba2d10
[project/uhttpd.git] / file.c
1 /*
2 * uhttpd - Tiny single-threaded httpd
3 *
4 * Copyright (C) 2010-2013 Jo-Philipp Wich <xm@subsignal.org>
5 * Copyright (C) 2013 Felix Fietkau <nbd@openwrt.org>
6 *
7 * Permission to use, copy, modify, and/or distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19
20 #define _DEFAULT_SOURCE
21 #define _BSD_SOURCE
22 #define _DARWIN_C_SOURCE
23 #define _XOPEN_SOURCE 700
24
25 #include <sys/types.h>
26 #include <sys/dir.h>
27 #include <time.h>
28 #include <strings.h>
29 #include <dirent.h>
30 #include <inttypes.h>
31
32 #include <libubox/blobmsg.h>
33
34 #include "uhttpd.h"
35 #include "mimetypes.h"
36
37 #define MAX(a, b) (((a) > (b)) ? (a) : (b))
38
39 static LIST_HEAD(index_files);
40 static LIST_HEAD(dispatch_handlers);
41 static LIST_HEAD(pending_requests);
42 static int n_requests;
43
44 struct deferred_request {
45 struct list_head list;
46 struct dispatch_handler *d;
47 struct client *cl;
48 struct path_info pi;
49 bool called, path;
50 };
51
52 struct index_file {
53 struct list_head list;
54 const char *name;
55 };
56
57 enum file_hdr {
58 HDR_AUTHORIZATION,
59 HDR_IF_MODIFIED_SINCE,
60 HDR_IF_UNMODIFIED_SINCE,
61 HDR_IF_MATCH,
62 HDR_IF_NONE_MATCH,
63 HDR_IF_RANGE,
64 __HDR_MAX
65 };
66
67 void uh_index_add(const char *filename)
68 {
69 struct index_file *idx;
70
71 idx = calloc(1, sizeof(*idx));
72 idx->name = filename;
73 list_add_tail(&idx->list, &index_files);
74 }
75
76 static char * canonpath(const char *path, char *path_resolved)
77 {
78 const char *path_cpy = path;
79 char *path_res = path_resolved;
80
81 if (conf.no_symlinks)
82 return realpath(path, path_resolved);
83
84 /* normalize */
85 while ((*path_cpy != '\0') && (path_cpy < (path + PATH_MAX - 2))) {
86 if (*path_cpy != '/')
87 goto next;
88
89 /* skip repeating / */
90 if (path_cpy[1] == '/') {
91 path_cpy++;
92 continue;
93 }
94
95 /* /./ or /../ */
96 if (path_cpy[1] == '.') {
97 /* skip /./ */
98 if ((path_cpy[2] == '/') || (path_cpy[2] == '\0')) {
99 path_cpy += 2;
100 continue;
101 }
102
103 /* collapse /x/../ */
104 if ((path_cpy[2] == '.') &&
105 ((path_cpy[3] == '/') || (path_cpy[3] == '\0'))) {
106 while ((path_res > path_resolved) && (*--path_res != '/'));
107
108 path_cpy += 3;
109 continue;
110 }
111 }
112
113 next:
114 *path_res++ = *path_cpy++;
115 }
116
117 /* remove trailing slash if not root / */
118 if ((path_res > (path_resolved+1)) && (path_res[-1] == '/'))
119 path_res--;
120 else if (path_res == path_resolved)
121 *path_res++ = '/';
122
123 *path_res = '\0';
124
125 return path_resolved;
126 }
127
128 /* Returns NULL on error.
129 ** NB: improperly encoded URL should give client 400 [Bad Syntax]; returning
130 ** NULL here causes 404 [Not Found], but that's not too unreasonable. */
131 struct path_info *
132 uh_path_lookup(struct client *cl, const char *url)
133 {
134 static char path_phys[PATH_MAX];
135 static char path_info[PATH_MAX];
136 static char path_query[PATH_MAX];
137 static struct path_info p;
138
139 const char *docroot = conf.docroot;
140 int docroot_len = strlen(docroot);
141 char *pathptr = NULL;
142 bool slash;
143
144 int i = 0;
145 int len;
146 struct stat s;
147 struct index_file *idx;
148
149 /* back out early if url is undefined */
150 if (url == NULL)
151 return NULL;
152
153 memset(&p, 0, sizeof(p));
154 path_phys[0] = 0;
155 path_info[0] = 0;
156
157 strcpy(uh_buf, docroot);
158
159 /* separate query string from url */
160 if ((pathptr = strchr(url, '?')) != NULL) {
161 if (pathptr[1]) {
162 p.query = path_query;
163 snprintf(path_query, sizeof(path_query), "%s",
164 pathptr + 1);
165 }
166
167 /* urldecode component w/o query */
168 if (pathptr > url) {
169 if (uh_urldecode(&uh_buf[docroot_len],
170 sizeof(uh_buf) - docroot_len - 1,
171 url, pathptr - url ) < 0)
172 return NULL;
173 }
174 }
175
176 /* no query string, decode all of url */
177 else if (uh_urldecode(&uh_buf[docroot_len],
178 sizeof(uh_buf) - docroot_len - 1,
179 url, strlen(url) ) < 0)
180 return NULL;
181
182 /* create canon path */
183 len = strlen(uh_buf);
184 slash = len && uh_buf[len - 1] == '/';
185 len = min(len, sizeof(path_phys) - 1);
186
187 for (i = len; i >= 0; i--) {
188 char ch = uh_buf[i];
189 bool exists;
190
191 if (ch != 0 && ch != '/')
192 continue;
193
194 uh_buf[i] = 0;
195 exists = !!canonpath(uh_buf, path_phys);
196 uh_buf[i] = ch;
197
198 if (!exists)
199 continue;
200
201 /* test current path */
202 if (stat(path_phys, &p.stat))
203 continue;
204
205 snprintf(path_info, sizeof(path_info), "%s", uh_buf + i);
206 break;
207 }
208
209 /* check whether found path is within docroot */
210 if (strncmp(path_phys, docroot, docroot_len) != 0 ||
211 (path_phys[docroot_len] != 0 &&
212 path_phys[docroot_len] != '/'))
213 return NULL;
214
215 /* is a regular file */
216 if (p.stat.st_mode & S_IFREG) {
217 p.root = docroot;
218 p.phys = path_phys;
219 p.name = &path_phys[docroot_len];
220 p.info = path_info[0] ? path_info : NULL;
221 return &p;
222 }
223
224 if (!(p.stat.st_mode & S_IFDIR))
225 return NULL;
226
227 if (path_info[0])
228 return NULL;
229
230 pathptr = path_phys + strlen(path_phys);
231
232 /* ensure trailing slash */
233 if (pathptr[-1] != '/') {
234 pathptr[0] = '/';
235 pathptr[1] = 0;
236 pathptr++;
237 }
238
239 /* if requested url resolves to a directory and a trailing slash
240 is missing in the request url, redirect the client to the same
241 url with trailing slash appended */
242 if (!slash) {
243 uh_http_header(cl, 302, "Found");
244 if (!uh_use_chunked(cl))
245 ustream_printf(cl->us, "Content-Length: 0\r\n");
246 ustream_printf(cl->us, "Location: %s%s%s\r\n\r\n",
247 &path_phys[docroot_len],
248 p.query ? "?" : "",
249 p.query ? p.query : "");
250 uh_request_done(cl);
251 p.redirected = 1;
252 return &p;
253 }
254
255 /* try to locate index file */
256 len = path_phys + sizeof(path_phys) - pathptr - 1;
257 list_for_each_entry(idx, &index_files, list) {
258 if (strlen(idx->name) > len)
259 continue;
260
261 strcpy(pathptr, idx->name);
262 if (!stat(path_phys, &s) && (s.st_mode & S_IFREG)) {
263 memcpy(&p.stat, &s, sizeof(p.stat));
264 break;
265 }
266
267 *pathptr = 0;
268 }
269
270 p.root = docroot;
271 p.phys = path_phys;
272 p.name = &path_phys[docroot_len];
273
274 return p.phys ? &p : NULL;
275 }
276
277 static const char * uh_file_mime_lookup(const char *path)
278 {
279 const struct mimetype *m = &uh_mime_types[0];
280 const char *e;
281
282 while (m->extn) {
283 e = &path[strlen(path)-1];
284
285 while (e >= path) {
286 if ((*e == '.' || *e == '/') && !strcasecmp(&e[1], m->extn))
287 return m->mime;
288
289 e--;
290 }
291
292 m++;
293 }
294
295 return "application/octet-stream";
296 }
297
298 static const char * uh_file_mktag(struct stat *s, char *buf, int len)
299 {
300 snprintf(buf, len, "\"%" PRIx64 "-%" PRIx64 "-%" PRIx64 "\"",
301 s->st_ino, s->st_size, (uint64_t)s->st_mtime);
302
303 return buf;
304 }
305
306 static time_t uh_file_date2unix(const char *date)
307 {
308 struct tm t;
309
310 memset(&t, 0, sizeof(t));
311
312 if (strptime(date, "%a, %d %b %Y %H:%M:%S %Z", &t) != NULL)
313 return timegm(&t);
314
315 return 0;
316 }
317
318 static char * uh_file_unix2date(time_t ts, char *buf, int len)
319 {
320 struct tm *t = gmtime(&ts);
321
322 strftime(buf, len, "%a, %d %b %Y %H:%M:%S GMT", t);
323
324 return buf;
325 }
326
327 static char *uh_file_header(struct client *cl, int idx)
328 {
329 if (!cl->dispatch.file.hdr[idx])
330 return NULL;
331
332 return (char *) blobmsg_data(cl->dispatch.file.hdr[idx]);
333 }
334
335 static void uh_file_response_ok_hdrs(struct client *cl, struct stat *s)
336 {
337 char buf[128];
338
339 if (s) {
340 ustream_printf(cl->us, "ETag: %s\r\n", uh_file_mktag(s, buf, sizeof(buf)));
341 ustream_printf(cl->us, "Last-Modified: %s\r\n",
342 uh_file_unix2date(s->st_mtime, buf, sizeof(buf)));
343 }
344 ustream_printf(cl->us, "Date: %s\r\n",
345 uh_file_unix2date(time(NULL), buf, sizeof(buf)));
346 }
347
348 static void uh_file_response_200(struct client *cl, struct stat *s)
349 {
350 uh_http_header(cl, 200, "OK");
351 return uh_file_response_ok_hdrs(cl, s);
352 }
353
354 static void uh_file_response_304(struct client *cl, struct stat *s)
355 {
356 uh_http_header(cl, 304, "Not Modified");
357
358 return uh_file_response_ok_hdrs(cl, s);
359 }
360
361 static void uh_file_response_405(struct client *cl)
362 {
363 uh_http_header(cl, 405, "Method Not Allowed");
364 }
365
366 static void uh_file_response_412(struct client *cl)
367 {
368 uh_http_header(cl, 412, "Precondition Failed");
369 }
370
371 static bool uh_file_if_match(struct client *cl, struct stat *s)
372 {
373 char buf[128];
374 const char *tag = uh_file_mktag(s, buf, sizeof(buf));
375 char *hdr = uh_file_header(cl, HDR_IF_MATCH);
376 char *p;
377 int i;
378
379 if (!hdr)
380 return true;
381
382 p = &hdr[0];
383 for (i = 0; i < strlen(hdr); i++)
384 {
385 if ((hdr[i] == ' ') || (hdr[i] == ',')) {
386 hdr[i++] = 0;
387 p = &hdr[i];
388 } else if (!strcmp(p, "*") || !strcmp(p, tag)) {
389 return true;
390 }
391 }
392
393 uh_file_response_412(cl);
394 return false;
395 }
396
397 static int uh_file_if_modified_since(struct client *cl, struct stat *s)
398 {
399 char *hdr = uh_file_header(cl, HDR_IF_MODIFIED_SINCE);
400
401 if (!hdr)
402 return true;
403
404 if (uh_file_date2unix(hdr) >= s->st_mtime) {
405 uh_file_response_304(cl, s);
406 return false;
407 }
408
409 return true;
410 }
411
412 static int uh_file_if_none_match(struct client *cl, struct stat *s)
413 {
414 char buf[128];
415 const char *tag = uh_file_mktag(s, buf, sizeof(buf));
416 char *hdr = uh_file_header(cl, HDR_IF_NONE_MATCH);
417 char *p;
418 int i;
419
420 if (!hdr)
421 return true;
422
423 p = &hdr[0];
424 for (i = 0; i < strlen(hdr); i++) {
425 if ((hdr[i] == ' ') || (hdr[i] == ',')) {
426 hdr[i++] = 0;
427 p = &hdr[i];
428 } else if (!strcmp(p, "*") || !strcmp(p, tag)) {
429 if ((cl->request.method == UH_HTTP_MSG_GET) ||
430 (cl->request.method == UH_HTTP_MSG_HEAD))
431 uh_file_response_304(cl, s);
432 else
433 uh_file_response_412(cl);
434
435 return false;
436 }
437 }
438
439 return true;
440 }
441
442 static int uh_file_if_range(struct client *cl, struct stat *s)
443 {
444 char *hdr = uh_file_header(cl, HDR_IF_RANGE);
445
446 if (hdr) {
447 uh_file_response_412(cl);
448 return false;
449 }
450
451 return true;
452 }
453
454 static int uh_file_if_unmodified_since(struct client *cl, struct stat *s)
455 {
456 char *hdr = uh_file_header(cl, HDR_IF_UNMODIFIED_SINCE);
457
458 if (hdr && uh_file_date2unix(hdr) <= s->st_mtime) {
459 uh_file_response_412(cl);
460 return false;
461 }
462
463 return true;
464 }
465
466 static int dirent_cmp(const struct dirent **a, const struct dirent **b)
467 {
468 bool dir_a = !!((*a)->d_type & DT_DIR);
469 bool dir_b = !!((*b)->d_type & DT_DIR);
470
471 /* directories first */
472 if (dir_a != dir_b)
473 return dir_b - dir_a;
474
475 return alphasort(a, b);
476 }
477
478 static void list_entries(struct client *cl, struct dirent **files, int count,
479 const char *path, char *local_path)
480 {
481 const char *suffix = "/";
482 const char *type = "directory";
483 unsigned int mode = S_IXOTH;
484 struct stat s;
485 char *escaped;
486 char *file;
487 char buf[128];
488 int i;
489
490 file = local_path + strlen(local_path);
491 for (i = 0; i < count; i++) {
492 const char *name = files[i]->d_name;
493 bool dir = !!(files[i]->d_type & DT_DIR);
494
495 if (name[0] == '.' && name[1] == 0)
496 goto next;
497
498 sprintf(file, "%s", name);
499 if (stat(local_path, &s))
500 goto next;
501
502 if (!dir) {
503 suffix = "";
504 mode = S_IROTH;
505 type = uh_file_mime_lookup(local_path);
506 }
507
508 if (!(s.st_mode & mode))
509 goto next;
510
511 escaped = uh_htmlescape(name);
512
513 if (!escaped)
514 goto next;
515
516 uh_chunk_printf(cl,
517 "<li><strong><a href='%s%s%s'>%s</a>%s"
518 "</strong><br /><small>modified: %s"
519 "<br />%s - %.02f kbyte<br />"
520 "<br /></small></li>",
521 path, escaped, suffix,
522 escaped, suffix,
523 uh_file_unix2date(s.st_mtime, buf, sizeof(buf)),
524 type, s.st_size / 1024.0);
525
526 free(escaped);
527 *file = 0;
528 next:
529 free(files[i]);
530 }
531 }
532
533 static void uh_file_dirlist(struct client *cl, struct path_info *pi)
534 {
535 struct dirent **files = NULL;
536 char *escaped_path = uh_htmlescape(pi->name);
537 int count = 0;
538
539 if (!escaped_path)
540 {
541 uh_client_error(cl, 500, "Internal Server Error", "Out of memory");
542 return;
543 }
544
545 uh_file_response_200(cl, NULL);
546 ustream_printf(cl->us, "Content-Type: text/html\r\n\r\n");
547
548 uh_chunk_printf(cl,
549 "<html><head><title>Index of %s</title></head>"
550 "<body><h1>Index of %s</h1><hr /><ol>",
551 escaped_path, escaped_path);
552
553 count = scandir(pi->phys, &files, NULL, dirent_cmp);
554 if (count > 0) {
555 strcpy(uh_buf, pi->phys);
556 list_entries(cl, files, count, escaped_path, uh_buf);
557 }
558 free(escaped_path);
559 free(files);
560
561 uh_chunk_printf(cl, "</ol><hr /></body></html>");
562 uh_request_done(cl);
563 }
564
565 static void file_write_cb(struct client *cl)
566 {
567 int fd = cl->dispatch.file.fd;
568 int r;
569
570 while (cl->us->w.data_bytes < 256) {
571 r = read(fd, uh_buf, sizeof(uh_buf));
572 if (r < 0) {
573 if (errno == EINTR)
574 continue;
575 }
576
577 if (!r) {
578 uh_request_done(cl);
579 return;
580 }
581
582 uh_chunk_write(cl, uh_buf, r);
583 }
584 }
585
586 static void uh_file_free(struct client *cl)
587 {
588 close(cl->dispatch.file.fd);
589 }
590
591 static void uh_file_data(struct client *cl, struct path_info *pi, int fd)
592 {
593 /* test preconditions */
594 if (!cl->dispatch.no_cache &&
595 (!uh_file_if_modified_since(cl, &pi->stat) ||
596 !uh_file_if_match(cl, &pi->stat) ||
597 !uh_file_if_range(cl, &pi->stat) ||
598 !uh_file_if_unmodified_since(cl, &pi->stat) ||
599 !uh_file_if_none_match(cl, &pi->stat))) {
600 ustream_printf(cl->us, "\r\n");
601 uh_request_done(cl);
602 close(fd);
603 return;
604 }
605
606 /* write status */
607 uh_file_response_200(cl, &pi->stat);
608
609 ustream_printf(cl->us, "Content-Type: %s\r\n",
610 uh_file_mime_lookup(pi->name));
611
612 ustream_printf(cl->us, "Content-Length: %" PRIu64 "\r\n\r\n",
613 pi->stat.st_size);
614
615
616 /* send body */
617 if (cl->request.method == UH_HTTP_MSG_HEAD) {
618 uh_request_done(cl);
619 close(fd);
620 return;
621 }
622
623 cl->dispatch.file.fd = fd;
624 cl->dispatch.write_cb = file_write_cb;
625 cl->dispatch.free = uh_file_free;
626 cl->dispatch.close_fds = uh_file_free;
627 file_write_cb(cl);
628 }
629
630 static bool __handle_file_request(struct client *cl, char *url);
631
632 static void uh_file_request(struct client *cl, const char *url,
633 struct path_info *pi, struct blob_attr **tb)
634 {
635 int fd;
636 struct http_request *req = &cl->request;
637 char *error_handler, *escaped_url;
638
639 switch (cl->request.method) {
640 case UH_HTTP_MSG_GET:
641 case UH_HTTP_MSG_POST:
642 case UH_HTTP_MSG_HEAD:
643 case UH_HTTP_MSG_OPTIONS:
644 break;
645
646 default:
647 uh_file_response_405(cl);
648 ustream_printf(cl->us, "\r\n");
649 uh_request_done(cl);
650 return;
651 }
652
653 if (!(pi->stat.st_mode & S_IROTH))
654 goto error;
655
656 if (pi->stat.st_mode & S_IFREG) {
657 fd = open(pi->phys, O_RDONLY);
658 if (fd < 0)
659 goto error;
660
661 req->disable_chunked = true;
662 cl->dispatch.file.hdr = tb;
663 uh_file_data(cl, pi, fd);
664 cl->dispatch.file.hdr = NULL;
665 return;
666 }
667
668 if ((pi->stat.st_mode & S_IFDIR)) {
669 if (conf.no_dirlists)
670 goto error;
671
672 uh_file_dirlist(cl, pi);
673 return;
674 }
675
676 error:
677 /* check for a previously set 403 redirect status to prevent infinite
678 recursion when the error page itself lacks sufficient permissions */
679 if (conf.error_handler && req->redirect_status != 403) {
680 req->redirect_status = 403;
681 error_handler = alloca(strlen(conf.error_handler) + 1);
682 strcpy(error_handler, conf.error_handler);
683 if (__handle_file_request(cl, error_handler))
684 return;
685 }
686
687 escaped_url = uh_htmlescape(url);
688
689 uh_client_error(cl, 403, "Forbidden",
690 "You don't have permission to access %s on this server.",
691 escaped_url ? escaped_url : "the url");
692
693 if (escaped_url)
694 free(escaped_url);
695 }
696
697 void uh_dispatch_add(struct dispatch_handler *d)
698 {
699 list_add_tail(&d->list, &dispatch_handlers);
700 }
701
702 static struct dispatch_handler *
703 dispatch_find(const char *url, struct path_info *pi)
704 {
705 struct dispatch_handler *d;
706
707 list_for_each_entry(d, &dispatch_handlers, list) {
708 if (pi) {
709 if (d->check_url)
710 continue;
711
712 if (d->check_path(pi, url))
713 return d;
714 } else {
715 if (d->check_path)
716 continue;
717
718 if (d->check_url(url))
719 return d;
720 }
721 }
722
723 return NULL;
724 }
725
726 static void
727 uh_invoke_script(struct client *cl, struct dispatch_handler *d, struct path_info *pi)
728 {
729 char *url = blobmsg_data(blob_data(cl->hdr.head));
730
731 n_requests++;
732 d->handle_request(cl, url, pi);
733 }
734
735 static void uh_complete_request(struct client *cl)
736 {
737 struct deferred_request *dr;
738
739 n_requests--;
740
741 while (!list_empty(&pending_requests)) {
742 if (n_requests >= conf.max_script_requests)
743 return;
744
745 dr = list_first_entry(&pending_requests, struct deferred_request, list);
746 list_del(&dr->list);
747
748 cl = dr->cl;
749 dr->called = true;
750 cl->dispatch.data_blocked = false;
751 uh_invoke_script(cl, dr->d, dr->path ? &dr->pi : NULL);
752 client_poll_post_data(cl);
753 }
754 }
755
756
757 static void
758 uh_free_pending_request(struct client *cl)
759 {
760 struct deferred_request *dr = cl->dispatch.req_data;
761
762 if (dr->called)
763 uh_complete_request(cl);
764 else
765 list_del(&dr->list);
766 free(dr);
767 }
768
769 static int field_len(const char *ptr)
770 {
771 if (!ptr)
772 return 0;
773
774 return strlen(ptr) + 1;
775 }
776
777 #define path_info_fields \
778 _field(root) \
779 _field(phys) \
780 _field(name) \
781 _field(info) \
782 _field(query)
783
784 static void
785 uh_defer_script(struct client *cl, struct dispatch_handler *d, struct path_info *pi)
786 {
787 struct deferred_request *dr;
788 char *_root, *_phys, *_name, *_info, *_query;
789
790 cl->dispatch.req_free = uh_free_pending_request;
791
792 if (pi) {
793 /* allocate enough memory to duplicate all path_info strings in one block */
794 #undef _field
795 #define _field(_name) &_##_name, field_len(pi->_name),
796 dr = calloc_a(sizeof(*dr), path_info_fields NULL);
797
798 memcpy(&dr->pi, pi, sizeof(*pi));
799 dr->path = true;
800
801 /* copy all path_info strings */
802 #undef _field
803 #define _field(_name) if (pi->_name) dr->pi._name = strcpy(_##_name, pi->_name);
804 path_info_fields
805 } else {
806 dr = calloc(1, sizeof(*dr));
807 }
808
809 cl->dispatch.req_data = dr;
810 cl->dispatch.data_blocked = true;
811 dr->cl = cl;
812 dr->d = d;
813 list_add(&dr->list, &pending_requests);
814 }
815
816 static void
817 uh_invoke_handler(struct client *cl, struct dispatch_handler *d, char *url, struct path_info *pi)
818 {
819 if (!d->script)
820 return d->handle_request(cl, url, pi);
821
822 if (n_requests >= conf.max_script_requests)
823 return uh_defer_script(cl, d, pi);
824
825 cl->dispatch.req_free = uh_complete_request;
826 uh_invoke_script(cl, d, pi);
827 }
828
829 static bool __handle_file_request(struct client *cl, char *url)
830 {
831 static const struct blobmsg_policy hdr_policy[__HDR_MAX] = {
832 [HDR_AUTHORIZATION] = { "authorization", BLOBMSG_TYPE_STRING },
833 [HDR_IF_MODIFIED_SINCE] = { "if-modified-since", BLOBMSG_TYPE_STRING },
834 [HDR_IF_UNMODIFIED_SINCE] = { "if-unmodified-since", BLOBMSG_TYPE_STRING },
835 [HDR_IF_MATCH] = { "if-match", BLOBMSG_TYPE_STRING },
836 [HDR_IF_NONE_MATCH] = { "if-none-match", BLOBMSG_TYPE_STRING },
837 [HDR_IF_RANGE] = { "if-range", BLOBMSG_TYPE_STRING },
838 };
839 struct dispatch_handler *d;
840 struct blob_attr *tb[__HDR_MAX];
841 struct path_info *pi;
842 char *user, *pass, *auth;
843
844 pi = uh_path_lookup(cl, url);
845 if (!pi)
846 return false;
847
848 if (pi->redirected)
849 return true;
850
851 blobmsg_parse(hdr_policy, __HDR_MAX, tb, blob_data(cl->hdr.head), blob_len(cl->hdr.head));
852
853 auth = tb[HDR_AUTHORIZATION] ? blobmsg_data(tb[HDR_AUTHORIZATION]) : NULL;
854
855 if (!uh_auth_check(cl, pi->name, auth, &user, &pass))
856 return true;
857
858 if (user && pass) {
859 blobmsg_add_string(&cl->hdr, "http-auth-user", user);
860 blobmsg_add_string(&cl->hdr, "http-auth-pass", pass);
861 }
862
863 d = dispatch_find(url, pi);
864 if (d)
865 uh_invoke_handler(cl, d, url, pi);
866 else
867 uh_file_request(cl, url, pi, tb);
868
869 return true;
870 }
871
872 static char *uh_handle_alias(char *old_url)
873 {
874 struct alias *alias;
875 static char *new_url;
876 static int url_len;
877
878 if (!list_empty(&conf.cgi_alias)) list_for_each_entry(alias, &conf.cgi_alias, list) {
879 int old_len;
880 int new_len;
881 int path_len = 0;
882
883 if (!uh_path_match(alias->alias, old_url))
884 continue;
885
886 if (alias->path)
887 path_len = strlen(alias->path);
888
889 old_len = strlen(old_url) + 1;
890 new_len = old_len + MAX(conf.cgi_prefix_len, path_len);
891
892 if (new_len > url_len) {
893 new_url = realloc(new_url, new_len);
894 url_len = new_len;
895 }
896
897 *new_url = '\0';
898
899 if (alias->path)
900 strcpy(new_url, alias->path);
901 else if (conf.cgi_prefix)
902 strcpy(new_url, conf.cgi_prefix);
903 strcat(new_url, old_url);
904
905 return new_url;
906 }
907 return old_url;
908 }
909
910 void uh_handle_request(struct client *cl)
911 {
912 struct http_request *req = &cl->request;
913 struct dispatch_handler *d;
914 char *url = blobmsg_data(blob_data(cl->hdr.head));
915 char *error_handler, *escaped_url;
916
917 blob_buf_init(&cl->hdr_response, 0);
918 url = uh_handle_alias(url);
919
920 uh_handler_run(cl, &url, false);
921 if (!url)
922 return;
923
924 req->redirect_status = 200;
925 d = dispatch_find(url, NULL);
926 if (d)
927 return uh_invoke_handler(cl, d, url, NULL);
928
929 if (__handle_file_request(cl, url))
930 return;
931
932 if (uh_handler_run(cl, &url, true)) {
933 if (!url)
934 return;
935
936 uh_handler_run(cl, &url, false);
937 if (__handle_file_request(cl, url))
938 return;
939 }
940
941 req->redirect_status = 404;
942 if (conf.error_handler) {
943 error_handler = alloca(strlen(conf.error_handler) + 1);
944 strcpy(error_handler, conf.error_handler);
945 if (__handle_file_request(cl, error_handler))
946 return;
947 }
948
949 escaped_url = uh_htmlescape(url);
950
951 uh_client_error(cl, 404, "Not Found", "The requested URL %s was not found on this server.",
952 escaped_url ? escaped_url : "");
953
954 if (escaped_url)
955 free(escaped_url);
956 }