diff options
author | Reyk Floeter <reyk@esdenera.com> | 2015-03-07 21:15:37 +0100 |
---|---|---|
committer | Reyk Floeter <reyk@esdenera.com> | 2015-03-07 21:15:37 +0100 |
commit | 7fdb196d97b9af7ed5e238ce8dca1ed5ad21ebbe (patch) | |
tree | 580763c74a99af82056996f27fa2277554b13d71 /httpd/server_file.c | |
parent | fa2f6dd5e2c33c291f059a90713e585abfc310ef (diff) | |
download | httpd-7fdb196d97b9af7ed5e238ce8dca1ed5ad21ebbe.tar.gz httpd-7fdb196d97b9af7ed5e238ce8dca1ed5ad21ebbe.zip |
Move files the git repository
Diffstat (limited to 'httpd/server_file.c')
-rw-r--r-- | httpd/server_file.c | 469 |
1 files changed, 469 insertions, 0 deletions
diff --git a/httpd/server_file.c b/httpd/server_file.c new file mode 100644 index 0000000..f697504 --- /dev/null +++ b/httpd/server_file.c @@ -0,0 +1,469 @@ +/* $OpenBSD: server_file.c,v 1.51 2015/02/12 10:05:29 reyk Exp $ */ + +/* + * Copyright (c) 2006 - 2015 Reyk Floeter <reyk@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/stat.h> + +#include <limits.h> +#include <errno.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <stdio.h> +#include <dirent.h> +#include <time.h> +#include <event.h> + +#include "httpd.h" +#include "http.h" + +#define MINIMUM(a, b) (((a) < (b)) ? (a) : (b)) +#define MAXIMUM(a, b) (((a) > (b)) ? (a) : (b)) + +int server_file_access(struct httpd *, struct client *, char *, size_t); +int server_file_request(struct httpd *, struct client *, char *, + struct stat *); +int server_file_index(struct httpd *, struct client *, struct stat *); +int server_file_method(struct client *); + +int +server_file_access(struct httpd *env, struct client *clt, + char *path, size_t len) +{ + struct http_descriptor *desc = clt->clt_descreq; + struct server_config *srv_conf = clt->clt_srv_conf; + struct stat st; + char *newpath; + int ret; + + errno = 0; + + if (access(path, R_OK) == -1) { + goto fail; + } else if (stat(path, &st) == -1) { + goto fail; + } else if (S_ISDIR(st.st_mode)) { + /* Deny access if directory indexing is disabled */ + if (srv_conf->flags & SRVFLAG_NO_INDEX) { + errno = EACCES; + goto fail; + } + + if (desc->http_path_alias != NULL) { + /* Recursion - the index "file" is a directory? */ + errno = EINVAL; + goto fail; + } + + /* Redirect to path with trailing "/" */ + if (path[strlen(path) - 1] != '/') { + if (asprintf(&newpath, "http%s://%s%s/", + srv_conf->flags & SRVFLAG_TLS ? "s" : "", + desc->http_host, desc->http_path) == -1) + return (500); + /* Path alias will be used for the redirection */ + desc->http_path_alias = newpath; + + /* Indicate that the file has been moved */ + return (301); + } + + /* Append the default index file to the location */ + if (asprintf(&newpath, "%s%s", desc->http_path, + srv_conf->index) == -1) + return (500); + desc->http_path_alias = newpath; + if (server_getlocation(clt, newpath) != srv_conf) { + /* The location has changed */ + return (server_file(env, clt)); + } + + /* Otherwise append the default index file to the path */ + if (strlcat(path, srv_conf->index, len) >= len) { + errno = EACCES; + goto fail; + } + + ret = server_file_access(env, clt, path, len); + if (ret == 404) { + /* + * Index file not found; fail if auto-indexing is + * not enabled, otherwise return success but + * indicate directory with S_ISDIR of the previous + * stat. + */ + if ((srv_conf->flags & SRVFLAG_AUTO_INDEX) == 0) { + errno = EACCES; + goto fail; + } + + return (server_file_index(env, clt, &st)); + } + return (ret); + } else if (!S_ISREG(st.st_mode)) { + /* Don't follow symlinks and ignore special files */ + errno = EACCES; + goto fail; + } + + return (server_file_request(env, clt, path, &st)); + + fail: + switch (errno) { + case ENOENT: + case ENOTDIR: + return (404); + case EACCES: + return (403); + default: + return (500); + } + + /* NOTREACHED */ +} + +int +server_file(struct httpd *env, struct client *clt) +{ + struct http_descriptor *desc = clt->clt_descreq; + struct server_config *srv_conf = clt->clt_srv_conf; + char path[PATH_MAX]; + const char *stripped, *errstr = NULL; + int ret = 500; + + if (srv_conf->flags & SRVFLAG_FCGI) + return (server_fcgi(env, clt)); + + /* Request path is already canonicalized */ + stripped = server_root_strip( + desc->http_path_alias != NULL ? + desc->http_path_alias : desc->http_path, + srv_conf->strip); + if ((size_t)snprintf(path, sizeof(path), "%s%s", + srv_conf->root, stripped) >= sizeof(path)) { + errstr = desc->http_path; + goto abort; + } + + /* Returns HTTP status code on error */ + if ((ret = server_file_access(env, clt, path, sizeof(path))) > 0) { + errstr = desc->http_path_alias != NULL ? + desc->http_path_alias : desc->http_path; + goto abort; + } + + return (ret); + + abort: + if (errstr == NULL) + errstr = strerror(errno); + server_abort_http(clt, ret, errstr); + return (-1); +} + +int +server_file_method(struct client *clt) +{ + struct http_descriptor *desc = clt->clt_descreq; + + switch (desc->http_method) { + case HTTP_METHOD_GET: + case HTTP_METHOD_HEAD: + return (0); + default: + /* Other methods are not allowed */ + errno = EACCES; + return (405); + } + /* NOTREACHED */ +} + +int +server_file_request(struct httpd *env, struct client *clt, char *path, + struct stat *st) +{ + struct server_config *srv_conf = clt->clt_srv_conf; + struct media_type *media; + const char *errstr = NULL; + int fd = -1, ret, code = 500; + + if ((ret = server_file_method(clt)) != 0) { + code = ret; + goto abort; + } + + /* Now open the file, should be readable or we have another problem */ + if ((fd = open(path, O_RDONLY)) == -1) + goto abort; + + media = media_find(env->sc_mediatypes, path); + ret = server_response_http(clt, 200, media, st->st_size, + MINIMUM(time(NULL), st->st_mtim.tv_sec)); + switch (ret) { + case -1: + goto fail; + case 0: + /* Connection is already finished */ + close(fd); + goto done; + default: + break; + } + + clt->clt_fd = fd; + if (clt->clt_srvbev != NULL) + bufferevent_free(clt->clt_srvbev); + + clt->clt_srvbev = bufferevent_new(clt->clt_fd, server_read, + server_write, server_file_error, clt); + if (clt->clt_srvbev == NULL) { + errstr = "failed to allocate file buffer event"; + goto fail; + } + + /* Adjust read watermark to the socket output buffer size */ + bufferevent_setwatermark(clt->clt_srvbev, EV_READ, 0, + clt->clt_sndbufsiz); + + bufferevent_settimeout(clt->clt_srvbev, + srv_conf->timeout.tv_sec, srv_conf->timeout.tv_sec); + bufferevent_enable(clt->clt_srvbev, EV_READ); + bufferevent_disable(clt->clt_bev, EV_READ); + + done: + server_reset_http(clt); + return (0); + fail: + bufferevent_disable(clt->clt_bev, EV_READ|EV_WRITE); + bufferevent_free(clt->clt_bev); + clt->clt_bev = NULL; + abort: + if (errstr == NULL) + errstr = strerror(errno); + server_abort_http(clt, code, errstr); + return (-1); +} + +int +server_file_index(struct httpd *env, struct client *clt, struct stat *st) +{ + char path[PATH_MAX]; + char tmstr[21]; + struct http_descriptor *desc = clt->clt_descreq; + struct server_config *srv_conf = clt->clt_srv_conf; + struct dirent **namelist, *dp; + int namesize, i, ret, fd = -1, namewidth, skip; + int code = 500; + struct evbuffer *evb = NULL; + struct media_type *media; + const char *stripped, *style; + char *escapeduri, *escapedhtml, *escapedpath; + struct tm tm; + time_t t, dir_mtime; + + if ((ret = server_file_method(clt)) != 0) { + code = ret; + goto abort; + } + + /* Request path is already canonicalized */ + stripped = server_root_strip(desc->http_path, srv_conf->strip); + if ((size_t)snprintf(path, sizeof(path), "%s%s", + srv_conf->root, stripped) >= sizeof(path)) + goto abort; + + /* Now open the file, should be readable or we have another problem */ + if ((fd = open(path, O_RDONLY)) == -1) + goto abort; + + /* Save last modification time */ + dir_mtime = MINIMUM(time(NULL), st->st_mtim.tv_sec); + + if ((evb = evbuffer_new()) == NULL) + goto abort; + + if ((namesize = scandir(path, &namelist, NULL, alphasort)) == -1) + goto abort; + + /* Indicate failure but continue going through the list */ + skip = 0; + + if ((escapedpath = escape_html(desc->http_path)) == NULL) + goto fail; + + /* A CSS stylesheet allows minimal customization by the user */ + style = "body { background-color: white; color: black; font-family: " + "sans-serif; }\nhr { border: 0; border-bottom: 1px dashed; }\n"; + /* Generate simple HTML index document */ + if (evbuffer_add_printf(evb, + "<!DOCTYPE html>\n" + "<html>\n" + "<head>\n" + "<title>Index of %s</title>\n" + "<style type=\"text/css\"><!--\n%s\n--></style>\n" + "</head>\n" + "<body>\n" + "<h1>Index of %s</h1>\n" + "<hr>\n<pre>\n", + escapedpath, style, escapedpath) == -1) + skip = 1; + + free(escapedpath); + + for (i = 0; i < namesize; i++) { + dp = namelist[i]; + + if (skip || + fstatat(fd, dp->d_name, st, 0) == -1) { + free(dp); + continue; + } + + t = st->st_mtime; + localtime_r(&t, &tm); + strftime(tmstr, sizeof(tmstr), "%d-%h-%Y %R", &tm); + namewidth = 51 - strlen(dp->d_name); + + if ((escapeduri = url_encode(dp->d_name)) == NULL) + goto fail; + if ((escapedhtml = escape_html(dp->d_name)) == NULL) + goto fail; + + if (dp->d_name[0] == '.' && + !(dp->d_name[1] == '.' && dp->d_name[2] == '\0')) { + /* ignore hidden files starting with a dot */ + } else if (S_ISDIR(st->st_mode)) { + namewidth -= 1; /* trailing slash */ + if (evbuffer_add_printf(evb, + "<a href=\"%s\">%s/</a>%*s%s%20s\n", + escapeduri, escapedhtml, + MAXIMUM(namewidth, 0), " ", tmstr, "-") == -1) + skip = 1; + } else if (S_ISREG(st->st_mode)) { + if (evbuffer_add_printf(evb, + "<a href=\"%s\">%s</a>%*s%s%20llu\n", + escapeduri, escapedhtml, + MAXIMUM(namewidth, 0), " ", + tmstr, st->st_size) == -1) + skip = 1; + } + free(escapeduri); + free(escapedhtml); + free(dp); + } + free(namelist); + + if (skip || + evbuffer_add_printf(evb, + "</pre>\n<hr>\n</body>\n</html>\n") == -1) + goto abort; + + close(fd); + fd = -1; + + media = media_find(env->sc_mediatypes, "index.html"); + ret = server_response_http(clt, 200, media, EVBUFFER_LENGTH(evb), + dir_mtime); + switch (ret) { + case -1: + goto fail; + case 0: + /* Connection is already finished */ + evbuffer_free(evb); + goto done; + default: + break; + } + + if (server_bufferevent_write_buffer(clt, evb) == -1) + goto fail; + evbuffer_free(evb); + evb = NULL; + + bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE); + if (clt->clt_persist) + clt->clt_toread = TOREAD_HTTP_HEADER; + else + clt->clt_toread = TOREAD_HTTP_NONE; + clt->clt_done = 0; + + done: + server_reset_http(clt); + return (0); + fail: + bufferevent_disable(clt->clt_bev, EV_READ|EV_WRITE); + bufferevent_free(clt->clt_bev); + clt->clt_bev = NULL; + abort: + if (fd != -1) + close(fd); + if (evb != NULL) + evbuffer_free(evb); + server_abort_http(clt, code, desc->http_path); + return (-1); +} + +void +server_file_error(struct bufferevent *bev, short error, void *arg) +{ + struct client *clt = arg; + struct evbuffer *dst; + + if (error & EVBUFFER_TIMEOUT) { + server_close(clt, "buffer event timeout"); + return; + } + if (error & EVBUFFER_ERROR) { + if (errno == EFBIG) { + bufferevent_enable(bev, EV_READ); + return; + } + server_close(clt, "buffer event error"); + return; + } + if (error & (EVBUFFER_READ|EVBUFFER_WRITE|EVBUFFER_EOF)) { + bufferevent_disable(bev, EV_READ|EV_WRITE); + + clt->clt_done = 1; + + if (clt->clt_persist) { + /* Close input file and wait for next HTTP request */ + if (clt->clt_fd != -1) + close(clt->clt_fd); + clt->clt_fd = -1; + clt->clt_toread = TOREAD_HTTP_HEADER; + server_reset_http(clt); + bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE); + return; + } + + dst = EVBUFFER_OUTPUT(clt->clt_bev); + if (EVBUFFER_LENGTH(dst)) { + /* Finish writing all data first */ + bufferevent_enable(clt->clt_bev, EV_WRITE); + return; + } + + server_close(clt, "done"); + return; + } + server_close(clt, "unknown event error"); + return; +} |