653 lines
16 KiB
C
653 lines
16 KiB
C
/*
|
|
* Copyright (C) 2017 Jianhui Zhao <jianhuizhao329@gmail.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
|
|
* USA
|
|
*/
|
|
|
|
#include <ctype.h>
|
|
#include <assert.h>
|
|
#include <arpa/inet.h>
|
|
#include <linux/limits.h>
|
|
#include <libubox/blobmsg.h>
|
|
|
|
#include "uhttpd.h"
|
|
#include "client.h"
|
|
#include "file.h"
|
|
#include "utils.h"
|
|
#include "uh_ssl.h"
|
|
#include "log.h"
|
|
|
|
static const char *const http_versions[] = {
|
|
[UH_HTTP_VER_09] = "HTTP/0.9",
|
|
[UH_HTTP_VER_10] = "HTTP/1.0",
|
|
[UH_HTTP_VER_11] = "HTTP/1.1"
|
|
};
|
|
|
|
static const char *const http_methods[] = {
|
|
[UH_HTTP_METHOD_GET] = "GET",
|
|
[UH_HTTP_METHOD_POST] = "POST",
|
|
[UH_HTTP_METHOD_HEAD] = "HEAD"
|
|
};
|
|
|
|
static inline void client_send(struct uh_client *cl, const void *data, int len)
|
|
{
|
|
ustream_write(cl->us, data, len, true);
|
|
}
|
|
|
|
static void client_send_header(struct uh_client *cl, int code, const char *summary, int length)
|
|
{
|
|
cl->printf(cl, "%s %03i %s\r\n", http_versions[cl->request.version], code, summary);
|
|
cl->printf(cl, "Server: Libuhttpd %s\r\n", UHTTPD_VERSION_STRING);
|
|
|
|
if (length < 0) {
|
|
cl->printf(cl, "Transfer-Encoding: chunked\r\n");
|
|
} else {
|
|
cl->printf(cl, "Content-Length: %d\r\n", length);
|
|
}
|
|
|
|
cl->response_length = length;
|
|
}
|
|
|
|
static inline void client_append_header(struct uh_client *cl, const char *name, const char *value)
|
|
{
|
|
cl->printf(cl, "%s: %s\r\n", name, value);
|
|
}
|
|
|
|
static inline void client_header_end(struct uh_client *cl)
|
|
{
|
|
cl->printf(cl, "\r\n");
|
|
}
|
|
|
|
static inline void client_redirect(struct uh_client *cl, int code, const char *fmt, ...)
|
|
{
|
|
va_list arg;
|
|
const char *summary = ((code == 301) ? "Moved Permanently" : "Found");
|
|
|
|
assert((code == 301 || code == 302) && fmt);
|
|
|
|
cl->send_header(cl, code, summary, 0);
|
|
cl->printf(cl, "Location: ");
|
|
va_start(arg, fmt);
|
|
cl->vprintf(cl, fmt, arg);
|
|
va_end(arg);
|
|
|
|
cl->printf(cl, "\r\n\r\n");
|
|
cl->request_done(cl);
|
|
}
|
|
|
|
static void client_send_error(struct uh_client *cl, int code, const char *summary, const char *fmt, ...)
|
|
{
|
|
va_list arg;
|
|
|
|
cl->send_header(cl, code, summary, -1);
|
|
cl->printf(cl, "Content-Type: text/html\r\n\r\n");
|
|
|
|
cl->chunk_printf(cl, "<h1>%s</h1>", summary);
|
|
|
|
if (fmt) {
|
|
va_start(arg, fmt);
|
|
cl->chunk_vprintf(cl, fmt, arg);
|
|
va_end(arg);
|
|
}
|
|
|
|
cl->request_done(cl);
|
|
}
|
|
|
|
static inline const char *client_get_version(struct uh_client *cl)
|
|
{
|
|
return http_versions[cl->request.version];
|
|
}
|
|
|
|
static inline const char *client_get_method(struct uh_client *cl)
|
|
{
|
|
return http_methods[cl->request.method];
|
|
}
|
|
|
|
static inline const char *client_get_peer_addr(struct uh_client *cl)
|
|
{
|
|
return inet_ntoa(cl->peer_addr.sin_addr);
|
|
}
|
|
|
|
static inline int client_get_peer_port(struct uh_client *cl)
|
|
{
|
|
return ntohs(cl->peer_addr.sin_port);
|
|
}
|
|
|
|
static inline const char *client_get_url(struct uh_client *cl)
|
|
{
|
|
return kvlist_get(&cl->request.url, "url");
|
|
}
|
|
|
|
static inline const char *client_get_path(struct uh_client *cl)
|
|
{
|
|
return kvlist_get(&cl->request.url, "path");
|
|
}
|
|
|
|
static inline const char *client_get_query(struct uh_client *cl)
|
|
{
|
|
return kvlist_get(&cl->request.url, "query");
|
|
}
|
|
|
|
const char *client_get_var(struct uh_client *cl, const char *name)
|
|
{
|
|
return kvlist_get(&cl->request.var, name);
|
|
}
|
|
|
|
static inline const char *client_get_header(struct uh_client *cl, const char *name)
|
|
{
|
|
return kvlist_get(&cl->request.header, name);
|
|
}
|
|
|
|
static inline const char *client_get_body(struct uh_client *cl, int *len)
|
|
{
|
|
*len = cl->dispatch.post_len;
|
|
return cl->dispatch.body;
|
|
}
|
|
|
|
static int post_post_data(struct uh_client *cl, const char *data, int len)
|
|
{
|
|
struct dispatch *d = &cl->dispatch;
|
|
d->post_len += len;
|
|
|
|
if (d->post_len > UH_POST_MAX_POST_SIZE)
|
|
goto err;
|
|
|
|
if (d->post_len > UH_POST_DATA_BUF_SIZE) {
|
|
d->body = realloc(d->body, UH_POST_MAX_POST_SIZE);
|
|
if (!d->body) {
|
|
cl->send_error(cl, 500, "Internal Server Error", "No memory");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
memcpy(d->body, data, len);
|
|
return len;
|
|
err:
|
|
cl->send_error(cl, 413, "Request Entity Too Large", NULL);
|
|
return 0;
|
|
}
|
|
|
|
static void post_post_done(struct uh_client *cl)
|
|
{
|
|
char *path = kvlist_get(&cl->request.url, "path");
|
|
|
|
if (cl->srv->on_request(cl) == UH_REQUEST_DONE)
|
|
return;
|
|
|
|
if (handle_file_request(cl, path))
|
|
return;
|
|
|
|
if (cl->srv->on_error404) {
|
|
cl->srv->on_error404(cl);
|
|
return;
|
|
}
|
|
|
|
cl->send_error(cl, 404, "Not Found", "The requested PATH %s was not found on this server.", path);
|
|
}
|
|
|
|
static void post_data_free(struct uh_client *cl)
|
|
{
|
|
struct dispatch *d = &cl->dispatch;
|
|
free(d->body);
|
|
}
|
|
|
|
static void uh_handle_request(struct uh_client *cl)
|
|
{
|
|
char *path = kvlist_get(&cl->request.url, "path");
|
|
|
|
if (cl->srv->on_request) {
|
|
struct dispatch *d = &cl->dispatch;
|
|
|
|
switch (cl->request.method) {
|
|
case UH_HTTP_METHOD_GET:
|
|
if (cl->srv->on_request(cl) == UH_REQUEST_DONE)
|
|
return;
|
|
break;
|
|
case UH_HTTP_METHOD_POST:
|
|
d->post_data = post_post_data;
|
|
d->post_done = post_post_done;
|
|
d->free = post_data_free;
|
|
d->body = calloc(1, UH_POST_DATA_BUF_SIZE);
|
|
if (!d->body)
|
|
cl->send_error(cl, 500, "Internal Server Error", "No memory");
|
|
return;
|
|
default:
|
|
cl->send_error(cl, 400, "Bad Request", "Invalid Request");
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (handle_file_request(cl, path))
|
|
return;
|
|
|
|
if (cl->srv->on_error404) {
|
|
cl->srv->on_error404(cl);
|
|
return;
|
|
}
|
|
|
|
cl->send_error(cl, 404, "Not Found", "The requested PATH %s was not found on this server.", path);
|
|
}
|
|
|
|
static inline void connection_close(struct uh_client *cl)
|
|
{
|
|
cl->us->eof = true;
|
|
cl->state = CLIENT_STATE_CLOSE;
|
|
ustream_state_change(cl->us);
|
|
}
|
|
|
|
static inline void keepalive_cb(struct uloop_timeout *timeout)
|
|
{
|
|
struct uh_client *cl = container_of(timeout, struct uh_client, timeout);
|
|
|
|
connection_close(cl);
|
|
}
|
|
|
|
static void dispatch_done(struct uh_client *cl)
|
|
{
|
|
if (cl->dispatch.free)
|
|
cl->dispatch.free(cl);
|
|
|
|
memset(&cl->dispatch, 0, sizeof(struct dispatch));
|
|
}
|
|
|
|
static inline int hdr_get_len(struct kvlist *kv, const void *data)
|
|
{
|
|
return strlen(data) + 1;
|
|
}
|
|
|
|
static void client_request_done(struct uh_client *cl)
|
|
{
|
|
if (cl->response_length < 0)
|
|
cl->printf(cl, "0\r\n\r\n");
|
|
|
|
dispatch_done(cl);
|
|
|
|
if (cl->connection_close) {
|
|
connection_close(cl);
|
|
return;
|
|
}
|
|
|
|
cl->state = CLIENT_STATE_INIT;
|
|
|
|
memset(&cl->request, 0, sizeof(cl->request));
|
|
memset(&cl->dispatch, 0, sizeof(cl->dispatch));
|
|
kvlist_init(&cl->request.url, hdr_get_len);
|
|
kvlist_init(&cl->request.var, hdr_get_len);
|
|
kvlist_init(&cl->request.header, hdr_get_len);
|
|
uloop_timeout_set(&cl->timeout, UHTTPD_CONNECTION_TIMEOUT * 1000);
|
|
}
|
|
|
|
static void client_free(struct uh_client *cl)
|
|
{
|
|
if (cl) {
|
|
dispatch_done(cl);
|
|
uloop_timeout_cancel(&cl->timeout);
|
|
if (cl->srv->ssl)
|
|
uh_ssl_client_detach(cl);
|
|
ustream_free(&cl->sfd.stream);
|
|
shutdown(cl->sfd.fd.fd, SHUT_RDWR);
|
|
close(cl->sfd.fd.fd);
|
|
list_del(&cl->list);
|
|
kvlist_free(&cl->request.url);
|
|
kvlist_free(&cl->request.var);
|
|
kvlist_free(&cl->request.header);
|
|
cl->srv->nclients--;
|
|
|
|
if (cl->srv->on_client_free)
|
|
cl->srv->on_client_free(cl);
|
|
|
|
free(cl);
|
|
}
|
|
}
|
|
|
|
static void parse_var(struct uh_client *cl, char *query)
|
|
{
|
|
struct kvlist *kv = &cl->request.var;
|
|
char *k, *v;
|
|
|
|
while (query && *query) {
|
|
k = query;
|
|
query = strchr(query, '&');
|
|
if (query)
|
|
*query++ = 0;
|
|
|
|
v = strchr(k, '=');
|
|
if (v)
|
|
*v++ = 0;
|
|
|
|
if (*k && v)
|
|
kvlist_set(kv, k, v);
|
|
}
|
|
}
|
|
|
|
static int client_parse_request(struct uh_client *cl, char *data)
|
|
{
|
|
struct http_request *req = &cl->request;
|
|
char *type, *url, *version, *p;
|
|
int h_method, h_version;
|
|
static char path[PATH_MAX];
|
|
|
|
type = strtok(data, " ");
|
|
url = strtok(NULL, " ");
|
|
version = strtok(NULL, " ");
|
|
if (!type || !url || !version)
|
|
return CLIENT_STATE_DONE;
|
|
|
|
h_method = find_idx(http_methods, ARRAY_SIZE(http_methods), type);
|
|
h_version = find_idx(http_versions, ARRAY_SIZE(http_versions), version);
|
|
if (h_method < 0 || h_version < 0) {
|
|
req->version = UH_HTTP_VER_10;
|
|
return CLIENT_STATE_DONE;
|
|
}
|
|
|
|
kvlist_set(&req->url, "url", url);
|
|
|
|
p = strchr(url, '?');
|
|
if (p) {
|
|
*p = 0;
|
|
if (p[1]) {
|
|
kvlist_set(&req->url, "query", p + 1);
|
|
parse_var(cl, p + 1);
|
|
}
|
|
}
|
|
|
|
if (uh_urldecode(path, sizeof(path) - 1, url, strlen(url)) < 0)
|
|
return CLIENT_STATE_DONE;
|
|
|
|
kvlist_set(&req->url, "path", path);
|
|
|
|
req->method = h_method;
|
|
req->version = h_version;
|
|
if (req->version < UH_HTTP_VER_11)
|
|
cl->connection_close = true;
|
|
|
|
return CLIENT_STATE_HEADER;
|
|
}
|
|
|
|
static bool client_init_cb(struct uh_client *cl, char *buf, int len)
|
|
{
|
|
char *newline;
|
|
|
|
newline = strstr(buf, "\r\n");
|
|
if (!newline)
|
|
return false;
|
|
|
|
if (newline == buf) {
|
|
ustream_consume(cl->us, 2);
|
|
return true;
|
|
}
|
|
|
|
*newline = 0;
|
|
|
|
cl->state = client_parse_request(cl, buf);
|
|
ustream_consume(cl->us, newline + 2 - buf);
|
|
if (cl->state == CLIENT_STATE_DONE)
|
|
cl->send_error(cl, 400, "Bad Request", NULL);
|
|
|
|
return true;
|
|
}
|
|
|
|
static void client_poll_post_data(struct uh_client *cl)
|
|
{
|
|
struct dispatch *d = &cl->dispatch;
|
|
struct http_request *r = &cl->request;
|
|
char *buf;
|
|
int len;
|
|
|
|
if (cl->state == CLIENT_STATE_DONE)
|
|
return;
|
|
|
|
while (1) {
|
|
int cur_len;
|
|
|
|
buf = ustream_get_read_buf(cl->us, &len);
|
|
if (!buf || !len)
|
|
break;
|
|
|
|
if (!d->post_data)
|
|
return;
|
|
|
|
cur_len = min(r->content_length, len);
|
|
if (cur_len) {
|
|
if (d->post_data)
|
|
cur_len = d->post_data(cl, buf, cur_len);
|
|
|
|
r->content_length -= cur_len;
|
|
ustream_consume(cl->us, cur_len);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (!r->content_length && cl->state != CLIENT_STATE_DONE) {
|
|
if (cl->dispatch.post_done)
|
|
cl->dispatch.post_done(cl);
|
|
|
|
cl->state = CLIENT_STATE_DONE;
|
|
}
|
|
}
|
|
|
|
static inline bool client_data_cb(struct uh_client *cl, char *buf, int len)
|
|
{
|
|
client_poll_post_data(cl);
|
|
return false;
|
|
}
|
|
|
|
static void client_parse_header(struct uh_client *cl, char *data)
|
|
{
|
|
struct http_request *req = &cl->request;
|
|
char *err;
|
|
char *name;
|
|
char *val;
|
|
|
|
if (!*data) {
|
|
uloop_timeout_cancel(&cl->timeout);
|
|
cl->state = CLIENT_STATE_DATA;
|
|
uh_handle_request(cl);
|
|
return;
|
|
}
|
|
|
|
val = uh_split_header(data);
|
|
if (!val) {
|
|
cl->state = CLIENT_STATE_DONE;
|
|
return;
|
|
}
|
|
|
|
for (name = data; *name; name++)
|
|
if (isupper(*name))
|
|
*name = tolower(*name);
|
|
|
|
if (!strcmp(data, "content-length")) {
|
|
req->content_length = strtoul(val, &err, 0);
|
|
if (err && *err) {
|
|
cl->send_error(cl, 400, "Bad Request", "Invalid Content-Length");
|
|
return;
|
|
}
|
|
} else if (!strcmp(data, "transfer-encoding") && !strcmp(val, "chunked")) {
|
|
cl->send_error(cl, 501, "Not Implemented", "Chunked body is not implemented");
|
|
return;
|
|
} else if (!strcmp(data, "connection") && !strcasecmp(val, "close")) {
|
|
cl->connection_close = true;
|
|
}
|
|
|
|
kvlist_set(&req->header, data, val);
|
|
|
|
cl->state = CLIENT_STATE_HEADER;
|
|
}
|
|
|
|
static bool client_header_cb(struct uh_client *cl, char *buf, int len)
|
|
{
|
|
char *newline;
|
|
int line_len;
|
|
|
|
newline = strstr(buf, "\r\n");
|
|
if (!newline)
|
|
return false;
|
|
|
|
*newline = 0;
|
|
client_parse_header(cl, buf);
|
|
line_len = newline + 2 - buf;
|
|
ustream_consume(cl->us, line_len);
|
|
if (cl->state == CLIENT_STATE_DATA)
|
|
return client_data_cb(cl, newline + 2, len - line_len);
|
|
|
|
return true;
|
|
}
|
|
|
|
typedef bool (*read_cb_t)(struct uh_client *cl, char *buf, int len);
|
|
static read_cb_t read_cbs[] = {
|
|
[CLIENT_STATE_INIT] = client_init_cb,
|
|
[CLIENT_STATE_HEADER] = client_header_cb,
|
|
[CLIENT_STATE_DATA] = client_data_cb,
|
|
};
|
|
|
|
void uh_client_read_cb(struct uh_client *cl)
|
|
{
|
|
struct ustream *us = cl->us;
|
|
char *str;
|
|
int len;
|
|
|
|
do {
|
|
str = ustream_get_read_buf(us, &len);
|
|
if (!str || !len)
|
|
return;
|
|
|
|
if (cl->state >= ARRAY_SIZE(read_cbs) || !read_cbs[cl->state])
|
|
return;
|
|
|
|
if (!read_cbs[cl->state](cl, str, len)) {
|
|
if (len == us->r.buffer_len && cl->state != CLIENT_STATE_DATA)
|
|
cl->send_error(cl, 413, "Request Entity Too Large", NULL);
|
|
break;
|
|
}
|
|
} while(1);
|
|
}
|
|
|
|
static inline void client_ustream_read_cb(struct ustream *s, int bytes)
|
|
{
|
|
struct uh_client *cl = container_of(s, struct uh_client, sfd.stream);
|
|
uh_client_read_cb(cl);
|
|
}
|
|
|
|
static void client_ustream_write_cb(struct ustream *s, int bytes)
|
|
{
|
|
struct uh_client *cl = container_of(s, struct uh_client, sfd.stream);
|
|
|
|
if (cl->dispatch.write_cb)
|
|
cl->dispatch.write_cb(cl);
|
|
}
|
|
|
|
void uh_client_notify_state(struct uh_client *cl)
|
|
{
|
|
struct ustream *us = cl->us;
|
|
|
|
if (!us->write_error) {
|
|
if (cl->state == CLIENT_STATE_DATA)
|
|
return;
|
|
|
|
if (!us->eof || us->w.data_bytes)
|
|
return;
|
|
}
|
|
|
|
client_free(cl);
|
|
}
|
|
|
|
static inline void client_notify_state(struct ustream *s)
|
|
{
|
|
struct uh_client *cl = container_of(s, struct uh_client, sfd.stream);
|
|
|
|
uh_client_notify_state(cl);
|
|
}
|
|
|
|
void uh_accept_client(struct uh_server *srv, bool ssl)
|
|
{
|
|
struct uh_client *cl;
|
|
unsigned int sl;
|
|
int sfd;
|
|
struct sockaddr_in addr;
|
|
|
|
sl = sizeof(addr);
|
|
sfd = accept(srv->fd.fd, (struct sockaddr *)&addr, &sl);
|
|
if (sfd < 0) {
|
|
uh_log_err("accept");
|
|
return;
|
|
}
|
|
|
|
cl = calloc(1, sizeof(struct uh_client));
|
|
if (!cl) {
|
|
uh_log_err("calloc");
|
|
goto err;
|
|
}
|
|
|
|
memcpy(&cl->peer_addr, &addr, sizeof(addr));
|
|
|
|
cl->us = &cl->sfd.stream;
|
|
if (ssl) {
|
|
uh_ssl_client_attach(cl);
|
|
} else {
|
|
cl->us->notify_read = client_ustream_read_cb;
|
|
cl->us->notify_write = client_ustream_write_cb;
|
|
cl->us->notify_state = client_notify_state;
|
|
}
|
|
|
|
cl->us->string_data = true;
|
|
ustream_fd_init(&cl->sfd, sfd);
|
|
|
|
cl->timeout.cb = keepalive_cb;
|
|
uloop_timeout_set(&cl->timeout, UHTTPD_CONNECTION_TIMEOUT * 1000);
|
|
|
|
list_add(&cl->list, &srv->clients);
|
|
kvlist_init(&cl->request.url, hdr_get_len);
|
|
kvlist_init(&cl->request.var, hdr_get_len);
|
|
kvlist_init(&cl->request.header, hdr_get_len);
|
|
|
|
cl->srv = srv;
|
|
cl->srv->nclients++;
|
|
|
|
cl->free = client_free;
|
|
cl->send_error = client_send_error;
|
|
cl->send_header = client_send_header;
|
|
cl->append_header = client_append_header;
|
|
cl->header_end = client_header_end;
|
|
cl->redirect = client_redirect;
|
|
cl->request_done = client_request_done;
|
|
|
|
cl->send = client_send;
|
|
cl->printf = uh_printf;
|
|
cl->vprintf = uh_vprintf;
|
|
cl->chunk_send = uh_chunk_send;
|
|
cl->chunk_printf = uh_chunk_printf;
|
|
cl->chunk_vprintf = uh_chunk_vprintf;
|
|
|
|
cl->get_version = client_get_version;
|
|
cl->get_method = client_get_method;
|
|
cl->get_peer_addr = client_get_peer_addr;
|
|
cl->get_peer_port = client_get_peer_port;
|
|
cl->get_url = client_get_url;
|
|
cl->get_path = client_get_path;
|
|
cl->get_query = client_get_query;
|
|
cl->get_var = client_get_var;
|
|
cl->get_header = client_get_header;
|
|
cl->get_body = client_get_body;
|
|
|
|
if (srv->on_accept)
|
|
srv->on_accept(cl);
|
|
|
|
return;
|
|
err:
|
|
close(sfd);
|
|
}
|
|
|