/* * MIT License * * Copyright (c) 2019 Jianhui Zhao * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include #include #include #include #include #include #include #include "uhttpd_internal.h" #include "utils.h" #include "file.h" #include "ssl.h" static void conn_done(struct uh_connection *conn) { struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; struct ev_loop *loop = conni->srv->loop; if (conni->flags & CONN_F_CLOSED) return; if (!http_should_keep_alive(&conni->parser)) conni->flags |= CONN_F_SEND_AND_CLOSE; if (conni->flags & CONN_F_SEND_AND_CLOSE) ev_io_stop(loop, &conni->ior); ev_io_start(loop, &conni->iow); ev_timer_stop(loop, &conni->timer); /* This is needed for a connection requested multiple times on different path */ conni->handler = NULL; } static void conn_send(struct uh_connection *conn, const void *data, ssize_t len) { struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; if (conni->flags & CONN_F_CLOSED) return; buffer_put_data(&conni->wb, data, len); ev_io_start(conni->srv->loop, &conni->iow); } static void conn_send_file(struct uh_connection *conn, const char *path, off_t offset, int64_t len) { struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; size_t min = 8192; struct stat st; int fd; if (conni->flags & CONN_F_CLOSED) return; if (len == 0) return; fd = open(path, O_RDONLY); if (fd < 0) { uh_log_err("open: %s\n", strerror(errno)); return; } fstat(fd, &st); if (offset >= st.st_size) { close(fd); return; } lseek(fd, offset, SEEK_SET); st.st_size -= offset; if (len < 0 || len > st.st_size) len = st.st_size; /* If the file is not greater than 8K, then append it to the HTTP head, send once */ if (len <= min) { buffer_put_fd(&conni->wb, fd, len, NULL); close(fd); } else { conni->file.size = len; conni->file.fd = fd; #if UHTTPD_SSL_SUPPORT if (conni->ssl) fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); #endif } ev_io_start(conni->srv->loop, &conni->iow); } static void conn_printf(struct uh_connection *conn, const char *format, ...) { struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; struct buffer *wb = &conni->wb; va_list arg; if (conni->flags & CONN_F_CLOSED) return; va_start(arg, format); buffer_put_vprintf(wb, format, arg); va_end(arg); ev_io_start(conni->srv->loop, &conni->iow); } static void conn_vprintf(struct uh_connection *conn, const char *format, va_list arg) { struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; if (conni->flags & CONN_F_CLOSED) return; buffer_put_vprintf(&conni->wb, format, arg); ev_io_start(conni->srv->loop, &conni->iow); } static void conn_chunk_send(struct uh_connection *conn, const void *data, ssize_t len) { conn_printf(conn, "%X\r\n", len); conn_send(conn, data, len); conn_printf(conn, "\r\n", len); } static void conn_chunk_vprintf(struct uh_connection *conn, const char *format, va_list arg) { char buf[256]; va_list arg2; int len; va_copy(arg2, arg); len = vsnprintf(buf, sizeof(buf), format, arg2); va_end(arg2); conn_printf(conn, "%X\r\n", len); if (len < sizeof(buf)) conn_send(conn, buf, len); else conn_vprintf(conn, format, arg); conn_printf(conn, "\r\n", len); } static void conn_chunk_printf(struct uh_connection *conn, const char *format, ...) { va_list arg; va_start(arg, format); conn_chunk_vprintf(conn, format, arg); va_end(arg); } static inline void conn_chunk_end(struct uh_connection *conn) { conn_chunk_send(conn, NULL, 0); } static void conn_send_status_line(struct uh_connection *conn, int code, const char *extra_headers) { conn_printf(conn, "HTTP/1.1 %d %s\r\nServer: Libuhttpd/%s\r\n", code, http_status_str(code), UHTTPD_VERSION_STRING); if (extra_headers) conn_send(conn, extra_headers, strlen(extra_headers)); } static void conn_send_head(struct uh_connection *conn, int code, int64_t content_length, const char *extra_headers) { struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; conn_send_status_line(conn, code, extra_headers); if (content_length < 0) conn_printf(conn, "%s", "Transfer-Encoding: chunked\r\n"); else conn_printf(conn, "Content-Length: %" PRIu64 "\r\n", content_length); if (!http_should_keep_alive(&conni->parser)) conn_printf(conn, "%s", "Connection: close\r\n"); conn_send(conn, "\r\n", 2); } static void conn_error(struct uh_connection *conn, int code, const char *reason) { struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; if (conni->flags & CONN_F_SEND_AND_CLOSE) return; if (!reason) reason = http_status_str(code); conn_send_head(conn, code, strlen(reason), "Content-Type: text/plain\r\nConnection: close\r\n"); conn_send(conn, reason, strlen(reason)); conni->flags |= CONN_F_SEND_AND_CLOSE; conn_done(conn); } static void conn_redirect(struct uh_connection *conn, int code, const char *location, ...) { struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; struct buffer *wb = &conni->wb; va_list arg; assert((code == HTTP_STATUS_MOVED_PERMANENTLY || code == HTTP_STATUS_FOUND) && location); conn_send_status_line(conn, code, NULL); conn_printf(conn, "Location: "); va_start(arg, location); buffer_put_vprintf(wb, location, arg); va_end(arg); conn_send(conn, "\r\n", 2); conn_printf(conn, "Content-Length: 0\r\n"); conn_send(conn, "\r\n", 2); conn_done(conn); } static const struct sockaddr *conn_get_addr(struct uh_connection *conn) { struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; return &conni->addr.sa; } static enum http_method conn_get_method(struct uh_connection *conn) { struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; return conni->parser.method; } static const char *conn_get_method_str(struct uh_connection *conn) { struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; return http_method_str(conni->parser.method); } /* offset of the request field */ #define ROF(c, a) (a - (const char *)c->rb.data) /* data of the request field */ #define O2D(c, o) ((const char *)c->rb.data + o) static struct uh_str conn_get_path(struct uh_connection *conn) { struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; struct http_parser_url *u = &conni->url_parser; struct uh_request *req = &conni->req; struct uh_str path; path.p = O2D(conni, u->field_data[UF_PATH].off) + req->url.offset; path.len = u->field_data[UF_PATH].len; return path; } static struct uh_str conn_get_query(struct uh_connection *conn) { struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; struct http_parser_url *u = &conni->url_parser; struct uh_request *req = &conni->req; struct uh_str query = {}; if (!(u->field_set & (1 << UF_QUERY))) return query; query.p = O2D(conni, u->field_data[UF_QUERY].off) + req->url.offset; query.len = u->field_data[UF_QUERY].len; return query; } static struct uh_str conn_get_header(struct uh_connection *conn, const char *name) { struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; struct uh_request *req = &conni->req; int name_len = strlen(name); struct uh_str value = {}; int i; for (i = 0; i < UHTTPD_MAX_HEADER_NUM; i++) { if (req->headers[i].field.offset == 0) return value; if (req->headers[i].field.length != name_len) continue; if (!strncasecmp(O2D(conni, req->headers[i].field.offset), name, name_len)) { value.p = O2D(conni, req->headers[i].value.offset); value.len = req->headers[i].value.length; } } return value; } static void conn_traverse_headers(struct uh_connection *conn, bool (*cb)(const struct uh_str name, const struct uh_str value, void *arg), void *arg) { struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; struct uh_request *req = &conni->req; struct uh_str name, value; int i; for (i = 0; i < UHTTPD_MAX_HEADER_NUM; i++) { if (req->headers[i].field.offset == 0) return; name.p = O2D(conni, req->headers[i].field.offset); name.len = req->headers[i].field.length; value.p = O2D(conni, req->headers[i].value.offset); value.len = req->headers[i].value.length; if (!cb(name, value, arg)) return; } } static uint64_t conn_get_content_length(struct uh_connection *conn) { struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; return conni->parser.content_length; } static struct uh_str conn_get_body(struct uh_connection *conn) { struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; struct uh_request *req = &conni->req; struct uh_str body; body.p = O2D(conni, req->body.offset); body.len = req->body.length; return body; } static struct uh_str conn_extract_body(struct uh_connection *conn) { struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; struct uh_str body = conn_get_body(conn); conni->req.body.consumed = true; return body; } static void conn_close(struct uh_connection *conn) { struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; http_parser_pause(&conni->parser, true); conni->flags |= CONN_F_CLOSED; } static int on_message_begin_cb(struct http_parser *parser) { struct uh_connection_internal *conn = (struct uh_connection_internal *)parser->data; struct uh_request *req = &conn->req; memset(req, 0, sizeof(struct uh_request)); req->last_was_header_value = true; http_parser_url_init(&conn->url_parser); ev_timer_start(conn->srv->loop, &conn->timer); return 0; } static int on_url_cb(struct http_parser *parser, const char *at, size_t length) { struct uh_connection_internal *conn = (struct uh_connection_internal *)parser->data; struct uh_request *req = &conn->req; if (req->url.offset == 0) req->url.offset = ROF(conn, at); req->url.length += length; return 0; } static int on_header_field_cb(struct http_parser *parser, const char *at, size_t length) { struct uh_connection_internal *conn = (struct uh_connection_internal *)parser->data; struct uh_request *req = &conn->req; if (req->last_was_header_value) { req->last_was_header_value = false; req->header_num++; if (req->header_num == UHTTPD_MAX_HEADER_NUM) { uh_log_err("Header too more\n"); return 1; } req->headers[req->header_num - 1].field.offset = ROF(conn, at); } req->headers[req->header_num - 1].field.length += length; return 0; } static int on_header_value_cb(struct http_parser *parser, const char *at, size_t length) { struct uh_connection_internal *conn = (struct uh_connection_internal *)parser->data; struct uh_request *req = &conn->req; if (!req->last_was_header_value) { req->last_was_header_value = true; req->headers[req->header_num - 1].value.offset = ROF(conn, at); } req->headers[req->header_num - 1].value.length += length; return 0; } static int on_headers_complete(struct http_parser *parser) { struct uh_connection_internal *conn = (struct uh_connection_internal *)parser->data; struct uh_server_internal *srv = conn->srv; struct uh_request *req = &conn->req; struct uh_path_handler *h = srv->handlers; struct uh_plugin *p = srv->plugins; struct uh_str path; http_parser_parse_url(O2D(conn, req->url.offset), req->url.length, false, &conn->url_parser); path = conn->com.get_path(&conn->com); while (h) { if (strlen(h->path) == path.len && !strncmp(path.p, h->path, path.len)) { conn->handler = h->handler; goto done; } h = h->next; } while (p) { if (strlen(p->h->path) == path.len && !strncmp(path.p, p->h->path, path.len)) { conn->handler = p->h->handler; goto done; } p = p->next; } done: if (!conn->handler) conn->handler = srv->default_handler; if (!conn->handler) { conn_error(&conn->com, HTTP_STATUS_NOT_FOUND, NULL); return -1; } conn->handler(&conn->com, UH_EV_HEAD_COMPLETE); if (conn->flags & CONN_F_SEND_AND_CLOSE) return -1; return 0; } static int on_body_cb(struct http_parser *parser, const char *at, size_t length) { struct uh_connection_internal *conn = (struct uh_connection_internal *)parser->data; struct uh_request *req = &conn->req; if (req->body.offset == 0) req->body.offset = ROF(conn, at); req->body.length += length; conn->handler(&conn->com, UH_EV_BODY); if (conn->flags & CONN_F_SEND_AND_CLOSE) return -1; if (req->body.consumed) { req->body.consumed = false; buffer_discard(&conn->rb, req->body.length); req->length -= req->body.length; req->body.length = 0; } return 0; } static int on_message_complete_cb(struct http_parser *parser) { struct uh_connection_internal *conn = (struct uh_connection_internal *)parser->data; struct uh_server_internal *srv = conn->srv; ev_timer_stop(srv->loop, &conn->timer); conn->handler(&conn->com, UH_EV_COMPLETE); http_parser_pause(parser, true); return 0; } static struct http_parser_settings settings = { .on_message_begin = on_message_begin_cb, .on_url = on_url_cb, .on_header_field = on_header_field_cb, .on_header_value = on_header_value_cb, .on_headers_complete = on_headers_complete, .on_body = on_body_cb, .on_message_complete = on_message_complete_cb }; void conn_free(struct uh_connection_internal *conn) { struct ev_loop *loop = conn->srv->loop; char addr_str[INET6_ADDRSTRLEN]; int port; ev_timer_stop(loop, &conn->timer); ev_io_stop(loop, &conn->ior); ev_io_stop(loop, &conn->iow); buffer_free(&conn->rb); buffer_free(&conn->wb); if (conn->file.fd > 0) close(conn->file.fd); if (conn->prev) conn->prev->next = conn->next; else conn->srv->conns = conn->next; if (conn->next) conn->next->prev = conn->prev; #if UHTTPD_SSL_SUPPORT uh_ssl_free(conn->ssl); #endif if (conn->srv->conn_closed_cb) conn->srv->conn_closed_cb(&conn->com); if (conn->sock > 0) close(conn->sock); if (uh_log_get_threshold() == LOG_DEBUG) { saddr2str(&conn->addr.sa, addr_str, sizeof(addr_str), &port); uh_log_debug("Connection(%s %d) closed\n", addr_str, port); } free(conn); } static void conn_http_parse(struct uh_connection_internal *conn) { struct http_parser *parser = &conn->parser; struct uh_request *req = &conn->req; struct buffer *rb = &conn->rb; uint8_t *data = buffer_data(rb) + req->length; size_t length = buffer_length(rb) - req->length; size_t nparsed; if (parser->http_errno == HPE_PAUSED) return; nparsed = http_parser_execute(parser, &settings, (const char *)data, length); if (conn->flags & CONN_F_CLOSED) { conn_free(conn); return; } switch (parser->http_errno) { case HPE_PAUSED: case HPE_OK: if (parser->upgrade) { conn_error(&conn->com, HTTP_STATUS_NOT_IMPLEMENTED, NULL); return; } req->length += nparsed; /* paused in on_message_complete */ if (parser->http_errno == HPE_PAUSED) { buffer_pull(rb, NULL, req->length); req->length = 0; } return; default: conn_error(&conn->com, HTTP_STATUS_BAD_REQUEST, http_errno_description(parser->http_errno)); return; } } #if UHTTPD_SSL_SUPPORT static int conn_ssl_write(int fd, void *buf, size_t count, void *ssl) { int ret = uh_ssl_write(ssl, buf, count); if (ret < 0) { if (ret == UH_SSL_ERROR_AGAIN) return P_FD_PENDING; return P_FD_ERR; } return ret; } #endif static void conn_write_cb(struct ev_loop *loop, struct ev_io *w, int revents) { struct uh_connection_internal *conn = container_of(w, struct uh_connection_internal, iow); int ret; #if UHTTPD_SSL_SUPPORT if (conn->ssl) ret = buffer_pull_to_fd_ex(&conn->wb, w->fd, buffer_length(&conn->wb), conn_ssl_write, conn->ssl); else #endif ret = buffer_pull_to_fd(&conn->wb, w->fd, buffer_length(&conn->wb)); if (ret < 0) { uh_log_err("write error: %s\n", strerror(errno)); conn_free(conn); return; } if (buffer_length(&conn->wb) == 0) { if (conn->file.fd > 0) { #if UHTTPD_SSL_SUPPORT if (conn->ssl) { bool eof; if (buffer_put_fd(&conn->wb, conn->file.fd, 8192, &eof) < 0 || eof) { close(conn->file.fd); conn->file.fd = -1; } return; } else { #endif ret = sendfile(w->fd, conn->file.fd, NULL, conn->file.size); if (ret < 0) { if (errno != EAGAIN) { uh_log_err("write error: %s\n", strerror(errno)); conn_free(conn); } return; } if (ret < conn->file.size) { conn->file.size -= ret; return; } close(conn->file.fd); conn->file.fd = -1; #if UHTTPD_SSL_SUPPORT } #endif } if (conn->flags & CONN_F_SEND_AND_CLOSE) { conn_free(conn); } else { ev_io_stop(loop, w); http_parser_pause(&conn->parser, false); if (buffer_length(&conn->rb) > 0) conn_http_parse(conn); } } } #if UHTTPD_SSL_SUPPORT static int conn_ssl_read(int fd, void *buf, size_t count, void *ssl) { int ret = uh_ssl_read(ssl, buf, count); if (ret < 0) { if (ret == UH_SSL_ERROR_AGAIN) return P_FD_PENDING; return P_FD_ERR; } return ret; } #endif static void conn_read_cb(struct ev_loop *loop, struct ev_io *w, int revents) { struct uh_connection_internal *conn = container_of(w, struct uh_connection_internal, ior); struct buffer *rb = &conn->rb; bool eof; int ret; if (conn->flags & CONN_F_SEND_AND_CLOSE) { ev_io_stop(loop, w); return; } #if UHTTPD_SSL_SUPPORT if (conn->ssl && !(conn->flags & CONN_F_SSL_HANDSHAKE_DONE)) { ret = uh_ssl_handshake(conn->ssl); if (ret == UH_SSL_ERROR_AGAIN) return; if (ret == UH_SSL_ERROR_UNKNOWN) { conn_free(conn); return; } conn->flags |= CONN_F_SSL_HANDSHAKE_DONE; } #endif conn->activity = ev_now(loop); #if UHTTPD_SSL_SUPPORT if (conn->ssl) ret = buffer_put_fd_ex(rb, w->fd, -1, &eof, conn_ssl_read, conn->ssl); else #endif ret = buffer_put_fd(rb, w->fd, -1, &eof); if (ret < 0) { uh_log_err("read error: %s\n", strerror(errno)); goto done; } if (eof) goto done; conn_http_parse(conn); return; done: conn_free(conn); } static void keepalive_cb(struct ev_loop *loop, struct ev_timer *w, int revents) { struct uh_connection_internal *conn = container_of(w, struct uh_connection_internal, timer); ev_tstamp after = conn->activity + UHTTPD_CONNECTION_TIMEOUT - ev_now(loop); if (conn->flags & CONN_F_SEND_AND_CLOSE) { ev_timer_stop(loop, w); return; } if (after > 0) { ev_timer_set(w, after, 0.0); ev_timer_start(loop, w); return; } conn_error(&conn->com, HTTP_STATUS_REQUEST_TIMEOUT, NULL); } static struct uh_server *conn_get_server(struct uh_connection *conn) { struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; return &conni->srv->com; } static struct ev_loop *conn_get_loop(struct uh_connection *conn) { struct uh_connection_internal *conni = (struct uh_connection_internal *)conn; return conni->srv->loop; } static void conn_init_cb(struct uh_connection *conn) { conn->get_server = conn_get_server; conn->get_loop = conn_get_loop; conn->done = conn_done; conn->send = conn_send; conn->send_file = conn_send_file; conn->printf = conn_printf; conn->vprintf = conn_vprintf; conn->send_status_line = conn_send_status_line; conn->send_head = conn_send_head; conn->error = conn_error; conn->redirect = conn_redirect; conn->serve_file = serve_file; conn->chunk_send = conn_chunk_send; conn->chunk_printf = conn_chunk_printf; conn->chunk_vprintf = conn_chunk_vprintf; conn->chunk_end = conn_chunk_end; conn->get_addr = conn_get_addr; conn->get_method = conn_get_method; conn->get_method_str = conn_get_method_str; conn->get_path = conn_get_path; conn->get_query = conn_get_query; conn->get_header = conn_get_header; conn->traverse_headers = conn_traverse_headers; conn->get_content_length = conn_get_content_length; conn->get_body = conn_get_body; conn->extract_body = conn_extract_body; conn->close = conn_close; } struct uh_connection_internal *uh_new_connection(struct uh_listener *l, int sock, struct sockaddr *addr) { struct uh_server_internal *srv = l->srv; struct uh_connection_internal *conn; conn = calloc(1, sizeof(struct uh_connection_internal)); if (!conn) { uh_log_err("malloc: %s\n", strerror(errno)); return NULL; } conn->srv = srv; conn->sock = sock; conn->activity = ev_now(srv->loop); if (addr->sa_family == AF_INET) memcpy(&conn->addr, addr, sizeof(struct sockaddr_in)); else memcpy(&conn->addr, addr, sizeof(struct sockaddr_in6)); ev_io_init(&conn->iow, conn_write_cb, sock, EV_WRITE); ev_io_init(&conn->ior, conn_read_cb, sock, EV_READ); ev_io_start(srv->loop, &conn->ior); ev_timer_init(&conn->timer, keepalive_cb, UHTTPD_CONNECTION_TIMEOUT, 0.0); ev_timer_start(srv->loop, &conn->timer); #if UHTTPD_SSL_SUPPORT if (l->ssl) conn->ssl = uh_ssl_new(srv->ssl_ctx, sock); #endif http_parser_init(&conn->parser, HTTP_REQUEST); conn->parser.data = conn; conn_init_cb(&conn->com); return conn; }