libuhttpd/src/connection.c

507 lines
14 KiB
C

/*
* MIT License
*
* Copyright (c) 2019 Jianhui Zhao <zhaojh329@gmail.com>
*
* 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 <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <assert.h>
#include "connection.h"
#include "uhttpd.h"
#include "utils.h"
#include "ssl.h"
static void conn_send(struct uh_connection *conn, const void *data, ssize_t len)
{
buffer_put_data(&conn->wb, data, len);
ev_io_start(conn->srv->loop, &conn->iow);
}
static void conn_printf(struct uh_connection *conn, const char *format, ...)
{
struct buffer *wb = &conn->wb;
va_list arg;
va_start(arg, format);
buffer_put_vprintf(wb, format, arg);
va_end(arg);
ev_io_start(conn->srv->loop, &conn->iow);
}
static void conn_vprintf(struct uh_connection *conn, const char *format, va_list arg)
{
buffer_put_vprintf(&conn->wb, format, arg);
ev_io_start(conn->srv->loop, &conn->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, int content_length, const char *extra_headers)
{
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: %d\r\n", content_length);
conn_send(conn, "\r\n", 2);
}
static void conn_error(struct uh_connection *conn, int code, const char *reason)
{
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));
conn->flags |= CONN_F_SEND_AND_CLOSE;
}
static void conn_redirect(struct uh_connection *conn, int code, const char *location, ...)
{
struct buffer *wb = &conn->wb;
va_list arg;
assert((code == 301 || code == 302) && 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);
}
/* 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 const char *conn_get_url(struct uh_connection *conn)
{
struct uh_request *req = &conn->req;
if (!req->url)
req->url = strndup(O2D(conn, req->url_info.offset), req->url_info.len);
return req->url;
}
static const char *conn_get_header(struct uh_connection *conn, const char *name)
{
struct uh_request *req = &conn->req;
int i, j;
for (i = 0; i < UHTTPD_MAX_HEADER_NUM; i++) {
if (!req->headers[i].name)
break;
if (!strcmp(req->headers[i].name, name))
return req->headers[i].value;
}
if (i == UHTTPD_MAX_HEADER_NUM)
return "";
for (j = 0; j < UHTTPD_MAX_HEADER_NUM; j++) {
if (req->headers_info[j].name_offset > 0) {
const char *p = O2D(conn, req->headers_info[j].name_offset);
if (!strncmp(p, name, req->headers_info[j].name_len)) {
req->headers[i].name = strndup(p, req->headers_info[j].name_len);
req->headers[i].value = strndup(O2D(conn, req->headers_info[j].value_offset), req->headers_info[j].value_len);
req->headers_info[j].name_len = 0;
return req->headers[i].value;
}
}
}
return "";
}
static const char *conn_get_body(struct uh_connection *conn, int *len)
{
struct uh_request *req = &conn->req;
const char *at = O2D(conn, req->body.offset);
*len = req->body.len;
return at;
}
static int on_url_cb(struct http_parser *parser, const char *at, size_t length)
{
struct uh_connection *conn = (struct uh_connection *)parser->data;
struct uh_request *req = &conn->req;
req->url_info.offset = ROF(conn, at);
req->url_info.len = length;
return 0;
}
static int on_header_field_cb(struct http_parser *parser, const char *at, size_t length)
{
struct uh_connection *conn = (struct uh_connection *)parser->data;
struct uh_request *req = &conn->req;
int n = req->header_num;
if (n == UHTTPD_MAX_HEADER_NUM) {
uh_log_err("Header too more\n");
return 0;
}
req->headers_info[n].name_offset = ROF(conn, at);
req->headers_info[n].name_len = length;
return 0;
}
static int on_header_value_cb(struct http_parser *parser, const char *at, size_t length)
{
struct uh_connection *conn = (struct uh_connection *)parser->data;
struct uh_request *req = &conn->req;
int n = req->header_num;
req->headers_info[n].value_offset = ROF(conn, at);
req->headers_info[n].value_len = length;
req->header_num++;
return 0;
}
static int on_body_cb(struct http_parser *parser, const char *at, size_t length)
{
struct uh_connection *conn = (struct uh_connection *)parser->data;
struct uh_request *req = &conn->req;
req->body.offset = ROF(conn, at);
req->body.len = length;
return 0;
}
static int on_message_complete_cb(struct http_parser *parser)
{
struct uh_connection *conn = (struct uh_connection *)parser->data;
struct uh_request *req = &conn->req;
int i;
if (conn->srv->on_request)
conn->srv->on_request(conn);
else
conn_error(conn, 404, NULL);
buffer_pull(&conn->rb, NULL, buffer_length(&conn->rb));
if (req->url)
free(req->url);
for (i = 0; i < UHTTPD_MAX_HEADER_NUM; i++) {
if (req->headers[i].name)
free(req->headers[i].name);
if (req->headers[i].value)
free(req->headers[i].value);
}
memset(req, 0, sizeof(struct uh_request));
return 0;
}
static struct http_parser_settings settings = {
.on_url = on_url_cb,
.on_header_field = on_header_field_cb,
.on_header_value = on_header_value_cb,
.on_body = on_body_cb,
.on_message_complete = on_message_complete_cb
};
static void conn_free(struct uh_connection *conn)
{
struct ev_loop *loop = conn->srv->loop;
struct sockaddr_in *addr = &conn->addr;
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->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->sock > 0)
close(conn->sock);
uh_log_debug("Connection(%s:%d) closed\n", inet_ntoa(addr->sin_addr), ntohs(addr->sin_port));
free(conn);
}
#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 *conn = container_of(w, struct uh_connection, iow);
int ret;
#if UHTTPD_SSL_SUPPORT
if (conn->ssl)
ret = buffer_pull_to_fd(&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), NULL, NULL);
if (ret < 0) {
uh_log_err("write error: %s\n", strerror(errno));
conn_free(conn);
return;
}
if (buffer_length(&conn->wb) == 0) {
if (conn->flags & CONN_F_SEND_AND_CLOSE)
conn_free(conn);
else
ev_io_stop(loop, w);
}
}
#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 *conn = container_of(w, struct uh_connection, ior);
struct http_parser *parser = &conn->parser;
static uint8_t sep[] = {'\r', '\n', '\r', '\n'};
struct buffer *rb = &conn->rb;
int ret, length, nparsed;
bool eof;
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(rb, w->fd, -1, &eof, conn_ssl_read, conn->ssl);
else
#endif
ret = buffer_put_fd(rb, w->fd, -1, &eof, NULL, NULL);
if (ret < 0) {
conn_error(conn, 500, NULL);
uh_log_err("read error: %s\n", strerror(errno));
return;
}
if (eof) {
conn_free(conn);
return;
}
if (buffer_find(rb, 0, 1024, sep, 4) < 0)
return;
length = buffer_length(rb);
nparsed = http_parser_execute(parser, &settings, (const char *)rb->data, length);
if (parser->upgrade)
conn_error(conn, 501, NULL);
else if (nparsed != length)
conn_error(conn, 400, http_errno_description(parser->http_errno));
}
static void keepalive_cb(struct ev_loop *loop, struct ev_timer *w, int revents)
{
struct uh_connection *conn = container_of(w, struct uh_connection, 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, 408, NULL);
}
struct uh_connection *uh_new_connection(struct uh_server *srv, int sock, struct sockaddr_in *addr)
{
struct uh_connection *conn;
conn = calloc(1, sizeof(struct uh_connection));
if (!conn) {
uh_log_err("malloc: %s\n", strerror(errno));
return NULL;
}
conn->srv = srv;
conn->sock = sock;
conn->activity = ev_now(srv->loop);
memcpy(&conn->addr, addr, sizeof(struct sockaddr_in));
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 (srv->ssl_ctx)
conn->ssl = uh_ssl_new(srv->ssl_ctx, sock);
#endif
http_parser_init(&conn->parser, HTTP_REQUEST);
conn->parser.data = conn;
conn->free = conn_free;
conn->send = conn_send;
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->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_url = conn_get_url;
conn->get_header = conn_get_header;
conn->get_body = conn_get_body;
return conn;
}