uhttpd/file: fix string out of buffer range on uh_defer_script
[project/uhttpd.git] / file.c
diff --git a/file.c b/file.c
index a4d9b1d4b2402e7dbec8dec893db224ef99b2f83..d117387878d99611a6a29ef471ad2b3a379fb801 100644 (file)
--- a/file.c
+++ b/file.c
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#ifndef _DEFAULT_SOURCE
+# define _DEFAULT_SOURCE
+#endif
+
 #define _BSD_SOURCE
 #define _DARWIN_C_SOURCE
 #define _XOPEN_SOURCE 700
@@ -45,6 +49,7 @@ struct deferred_request {
        struct dispatch_handler *d;
        struct client *cl;
        struct path_info pi;
+       char *url;
        bool called, path;
 };
 
@@ -132,6 +137,7 @@ uh_path_lookup(struct client *cl, const char *url)
 {
        static char path_phys[PATH_MAX];
        static char path_info[PATH_MAX];
+       static char path_query[PATH_MAX];
        static struct path_info p;
 
        const char *docroot = conf.docroot;
@@ -156,7 +162,11 @@ uh_path_lookup(struct client *cl, const char *url)
 
        /* separate query string from url */
        if ((pathptr = strchr(url, '?')) != NULL) {
-               p.query = pathptr[1] ? pathptr + 1 : NULL;
+               if (pathptr[1]) {
+                       p.query = path_query;
+                       snprintf(path_query, sizeof(path_query), "%s",
+                                pathptr + 1);
+               }
 
                /* urldecode component w/o query */
                if (pathptr > url) {
@@ -352,6 +362,11 @@ static void uh_file_response_304(struct client *cl, struct stat *s)
        return uh_file_response_ok_hdrs(cl, s);
 }
 
+static void uh_file_response_405(struct client *cl)
+{
+       uh_http_header(cl, 405, "Method Not Allowed");
+}
+
 static void uh_file_response_412(struct client *cl)
 {
        uh_http_header(cl, 412, "Precondition Failed");
@@ -471,6 +486,7 @@ static void list_entries(struct client *cl, struct dirent **files, int count,
        const char *type = "directory";
        unsigned int mode = S_IXOTH;
        struct stat s;
+       char *escaped;
        char *file;
        char buf[128];
        int i;
@@ -496,16 +512,22 @@ static void list_entries(struct client *cl, struct dirent **files, int count,
                if (!(s.st_mode & mode))
                        goto next;
 
+               escaped = uh_htmlescape(name);
+
+               if (!escaped)
+                       goto next;
+
                uh_chunk_printf(cl,
                                "<li><strong><a href='%s%s%s'>%s</a>%s"
                                "</strong><br /><small>modified: %s"
                                "<br />%s - %.02f kbyte<br />"
                                "<br /></small></li>",
-                               path, name, suffix,
-                               name, suffix,
+                               path, escaped, suffix,
+                               escaped, suffix,
                                uh_file_unix2date(s.st_mtime, buf, sizeof(buf)),
                                type, s.st_size / 1024.0);
 
+               free(escaped);
                *file = 0;
 next:
                free(files[i]);
@@ -515,21 +537,30 @@ next:
 static void uh_file_dirlist(struct client *cl, struct path_info *pi)
 {
        struct dirent **files = NULL;
+       char *escaped_path = uh_htmlescape(pi->name);
        int count = 0;
 
+       if (!escaped_path)
+       {
+               uh_client_error(cl, 500, "Internal Server Error", "Out of memory");
+               return;
+       }
+
        uh_file_response_200(cl, NULL);
-       ustream_printf(cl->us, "Content-Type: text/html\r\n\r\n");
+       ustream_printf(cl->us, "Content-Type: text/html; charset=%s\r\n\r\n",
+               conf.dirlist_charset ? conf.dirlist_charset : "UTF-8");
 
        uh_chunk_printf(cl,
                "<html><head><title>Index of %s</title></head>"
                "<body><h1>Index of %s</h1><hr /><ol>",
-               pi->name, pi->name);
+               escaped_path, escaped_path);
 
        count = scandir(pi->phys, &files, NULL, dirent_cmp);
        if (count > 0) {
                strcpy(uh_buf, pi->phys);
-               list_entries(cl, files, count, pi->name, uh_buf);
+               list_entries(cl, files, count, escaped_path, uh_buf);
        }
+       free(escaped_path);
        free(files);
 
        uh_chunk_printf(cl, "</ol><hr /></body></html>");
@@ -601,14 +632,28 @@ static void uh_file_data(struct client *cl, struct path_info *pi, int fd)
        file_write_cb(cl);
 }
 
-static bool __handle_file_request(struct client *cl, char *url);
+static bool __handle_file_request(struct client *cl, char *url, bool is_error_handler);
 
 static void uh_file_request(struct client *cl, const char *url,
                            struct path_info *pi, struct blob_attr **tb)
 {
        int fd;
        struct http_request *req = &cl->request;
-       char *error_handler;
+       char *error_handler, *escaped_url;
+
+       switch (cl->request.method) {
+       case UH_HTTP_MSG_GET:
+       case UH_HTTP_MSG_POST:
+       case UH_HTTP_MSG_HEAD:
+       case UH_HTTP_MSG_OPTIONS:
+               break;
+
+       default:
+               uh_file_response_405(cl);
+               ustream_printf(cl->us, "\r\n");
+               uh_request_done(cl);
+               return;
+       }
 
        if (!(pi->stat.st_mode & S_IROTH))
                goto error;
@@ -640,13 +685,18 @@ error:
                req->redirect_status = 403;
                error_handler = alloca(strlen(conf.error_handler) + 1);
                strcpy(error_handler, conf.error_handler);
-               if (__handle_file_request(cl, error_handler))
+               if (__handle_file_request(cl, error_handler, true))
                        return;
        }
 
+       escaped_url = uh_htmlescape(url);
+
        uh_client_error(cl, 403, "Forbidden",
                        "You don't have permission to access %s on this server.",
-                       url);
+                       escaped_url ? escaped_url : "the url");
+
+       if (escaped_url)
+               free(escaped_url);
 }
 
 void uh_dispatch_add(struct dispatch_handler *d)
@@ -679,10 +729,8 @@ dispatch_find(const char *url, struct path_info *pi)
 }
 
 static void
-uh_invoke_script(struct client *cl, struct dispatch_handler *d, struct path_info *pi)
+uh_invoke_script(struct client *cl, struct dispatch_handler *d, char *url, struct path_info *pi)
 {
-       char *url = blobmsg_data(blob_data(cl->hdr.head));
-
        n_requests++;
        d->handle_request(cl, url, pi);
 }
@@ -703,8 +751,9 @@ static void uh_complete_request(struct client *cl)
                cl = dr->cl;
                dr->called = true;
                cl->dispatch.data_blocked = false;
-               uh_invoke_script(cl, dr->d, dr->path ? &dr->pi : NULL);
+               uh_invoke_script(cl, dr->d, dr->url, dr->path ? &dr->pi : NULL);
                client_poll_post_data(cl);
+               ustream_poll(cl->us);
        }
 }
 
@@ -737,10 +786,10 @@ static int field_len(const char *ptr)
        _field(query)
 
 static void
-uh_defer_script(struct client *cl, struct dispatch_handler *d, struct path_info *pi)
+uh_defer_script(struct client *cl, struct dispatch_handler *d, char *url, struct path_info *pi)
 {
        struct deferred_request *dr;
-       char *_root, *_phys, *_name, *_info, *_query;
+       char *_url, *_root, *_phys, *_name, *_info, *_query;
 
        cl->dispatch.req_free = uh_free_pending_request;
 
@@ -748,7 +797,7 @@ uh_defer_script(struct client *cl, struct dispatch_handler *d, struct path_info
                /* allocate enough memory to duplicate all path_info strings in one block */
 #undef _field
 #define _field(_name) &_##_name, field_len(pi->_name),
-               dr = calloc_a(sizeof(*dr), path_info_fields NULL);
+               dr = calloc_a(sizeof(*dr), &_url, strlen(url) + 1, path_info_fields NULL);
 
                memcpy(&dr->pi, pi, sizeof(*pi));
                dr->path = true;
@@ -758,11 +807,12 @@ uh_defer_script(struct client *cl, struct dispatch_handler *d, struct path_info
 #define _field(_name) if (pi->_name) dr->pi._name = strcpy(_##_name, pi->_name);
                path_info_fields
        } else {
-               dr = calloc(1, sizeof(*dr));
+               dr = calloc_a(sizeof(*dr), &_url, strlen(url) + 1, NULL);
        }
 
        cl->dispatch.req_data = dr;
        cl->dispatch.data_blocked = true;
+       dr->url = strcpy(_url, url);
        dr->cl = cl;
        dr->d = d;
        list_add(&dr->list, &pending_requests);
@@ -775,13 +825,13 @@ uh_invoke_handler(struct client *cl, struct dispatch_handler *d, char *url, stru
                return d->handle_request(cl, url, pi);
 
        if (n_requests >= conf.max_script_requests)
-               return uh_defer_script(cl, d, pi);
+               return uh_defer_script(cl, d, url, pi);
 
        cl->dispatch.req_free = uh_complete_request;
-       uh_invoke_script(cl, d, pi);
+       uh_invoke_script(cl, d, url, pi);
 }
 
-static bool __handle_file_request(struct client *cl, char *url)
+static bool __handle_file_request(struct client *cl, char *url, bool is_error_handler)
 {
        static const struct blobmsg_policy hdr_policy[__HDR_MAX] = {
                [HDR_AUTHORIZATION] = { "authorization", BLOBMSG_TYPE_STRING },
@@ -794,7 +844,17 @@ static bool __handle_file_request(struct client *cl, char *url)
        struct dispatch_handler *d;
        struct blob_attr *tb[__HDR_MAX];
        struct path_info *pi;
-       char *user, *pass;
+       char *user, *pass, *auth;
+
+       if (is_error_handler) {
+               d = dispatch_find(url, NULL);
+
+               if (d) {
+                       uh_invoke_handler(cl, d, url, NULL);
+
+                       return true;
+               }
+       }
 
        pi = uh_path_lookup(cl, url);
        if (!pi)
@@ -804,14 +864,15 @@ static bool __handle_file_request(struct client *cl, char *url)
                return true;
 
        blobmsg_parse(hdr_policy, __HDR_MAX, tb, blob_data(cl->hdr.head), blob_len(cl->hdr.head));
-       if (tb[HDR_AUTHORIZATION]) {
-               if (!uh_auth_check(cl, pi->name, blobmsg_data(tb[HDR_AUTHORIZATION]), &user, &pass))
-                       return true;
 
-               if (user && pass) {
-                       blobmsg_add_string(&cl->hdr, "http-auth-user", user);
-                       blobmsg_add_string(&cl->hdr, "http-auth-pass", pass);
-               }
+       auth = tb[HDR_AUTHORIZATION] ? blobmsg_data(tb[HDR_AUTHORIZATION]) : NULL;
+
+       if (!uh_auth_check(cl, pi->name, auth, &user, &pass))
+               return true;
+
+       if (user && pass) {
+               blobmsg_add_string(&cl->hdr, "http-auth-user", user);
+               blobmsg_add_string(&cl->hdr, "http-auth-pass", pass);
        }
 
        d = dispatch_find(url, pi);
@@ -866,7 +927,7 @@ void uh_handle_request(struct client *cl)
        struct http_request *req = &cl->request;
        struct dispatch_handler *d;
        char *url = blobmsg_data(blob_data(cl->hdr.head));
-       char *error_handler;
+       char *error_handler, *escaped_url;
 
        blob_buf_init(&cl->hdr_response, 0);
        url = uh_handle_alias(url);
@@ -880,7 +941,7 @@ void uh_handle_request(struct client *cl)
        if (d)
                return uh_invoke_handler(cl, d, url, NULL);
 
-       if (__handle_file_request(cl, url))
+       if (__handle_file_request(cl, url, false))
                return;
 
        if (uh_handler_run(cl, &url, true)) {
@@ -888,7 +949,7 @@ void uh_handle_request(struct client *cl)
                        return;
 
                uh_handler_run(cl, &url, false);
-               if (__handle_file_request(cl, url))
+               if (__handle_file_request(cl, url, false))
                        return;
        }
 
@@ -896,9 +957,15 @@ void uh_handle_request(struct client *cl)
        if (conf.error_handler) {
                error_handler = alloca(strlen(conf.error_handler) + 1);
                strcpy(error_handler, conf.error_handler);
-               if (__handle_file_request(cl, error_handler))
+               if (__handle_file_request(cl, error_handler, true))
                        return;
        }
 
-       uh_client_error(cl, 404, "Not Found", "The requested URL %s was not found on this server.", url);
+       escaped_url = uh_htmlescape(url);
+
+       uh_client_error(cl, 404, "Not Found", "The requested URL %s was not found on this server.",
+                       escaped_url ? escaped_url : "");
+
+       if (escaped_url)
+               free(escaped_url);
 }