/* ############ Web server functions ########### */
#include "Conn_config.h"
#include <openssl/sha.h>
#include <openssl/evp.h>
#include "Conn_intern.h"
#include "Conn_web.h"
/*
* Allocates a web server structure (Conn_web)
*/
static struct Conn_web *Conn_web_alloc(void)
{
struct Conn_web *ret;
ret = (struct Conn_web *) malloc(sizeof(struct Conn_web));
if (ret == NULL)
return NULL;
ret->urls = NULL;
return ret;
}
/*
* Frees a Conn_web structure
*/
static void Conn_web_free(struct Conn_web *web)
{
struct Conn_web_url *u, *next;
u = web->urls;
while (u) {
next = u->next;
free(u->url);
if (u->type == CONN_WEB_TYPE_PATH)
free(u->path);
free(u);
u = next;
}
free(web);
}
/*
* Creates a web server and attach it to a C
* Returns 0 if OK, -1 on error.
*/
int Conn_web_create(struct Conn *C)
{
struct Conn_web *web;
Log(10, "%s\n", __func__);
web = Conn_web_alloc();
if (!web)
return -1;
if (C->web)
Conn_web_free(C->web);
C->web = web;
Log(10, "\tattached!\n");
return 0;
}
/*
* Returns the string request type
*/
static char *Conn_web_req_type(const unsigned char req_type)
{
switch (req_type) {
case CONN_WEB_REQ_GET: return "GET";
case CONN_WEB_REQ_POST: return "POST";
}
return "UNK";
}
/*
* Attach a function to an URL
* Example: Conn_web_script(C, "/cgi-bin/script1", function_script1)
*/
int Conn_web_script(struct Conn *C, const char *url,
void(*cb)(struct Conn *C))
{
struct Conn_web_url *u, *p;
u = (struct Conn_web_url *) malloc(sizeof(struct Conn_web_url));
if (!u)
return -1;
u->type = CONN_WEB_TYPE_SCRIPT;
u->next = NULL;
u->url = strdup(url);
if (!u->url) {
free(u);
return -1;
}
u->cb = cb;
if (C->web->urls == NULL) {
C->web->urls = u;
} else {
p = C->web->urls;
while (p->next)
p = p->next;
p->next = u;
}
return 0;
}
/*
* Attach a dir/file to an URL
* Example: Conn_web_path(C, "/img", "/home/images/v1")
*/
int Conn_web_path(struct Conn *C, const char *url, const char *path)
{
struct Conn_web_url *u, *p;
u = (struct Conn_web_url *) malloc(sizeof(struct Conn_web_url));
if (!u)
return -1;
u->type = CONN_WEB_TYPE_PATH;
u->next = NULL;
u->url = strdup(url);
if (!u->url) {
free(u);
return -1;
}
u->path = strdup(path);
if (!u->path) {
free(u->url);
free(u);
return -1;
}
if (C->web->urls == NULL) {
C->web->urls = u;
} else {
p = C->web->urls;
while (p->next)
p = p->next;
p->next = u;
}
return 0;
}
/*
* Loads a file and outputs it to a connection
*/
static int Conn_web_read_file(struct Conn *C, const char *path)
{
/*
We have a problem here. If file is big, we need to fill a buffer
and return. when buffer is getting low, refill.
*/
return 0;
}
static void Conn_web_dispatch_path(struct Conn *C, struct Conn_web_url *u)
{
char tmp[128], body[128];
Log(2, "%llu %s req_type=[%s] url=[%s] http_protcol=%hhu\n",
C->id, __func__, Conn_web_req_type(C->web_req->req_type),
C->web_req->url, C->web_req->http_protocol);
if (C->web_req->req_type != CONN_WEB_REQ_GET) {
/* TODO: send some error */
return;
}
snprintf(body, sizeof(body), "path");
snprintf(tmp, sizeof(tmp),
"HTTP/1.1 200 OK\r\n"
"Content-Length: %zu\r\n"
"\r\n"
"%s",
strlen(body), body);
Conn_enqueue(C, tmp, (unsigned int) strlen(tmp));
}
/*
* Dispatch a web server request
*/
void Conn_web_dispatch(struct Conn *C)
{
struct Conn_web_url *u = NULL;
char *end_header, *start_header, *sep, *url, *paras = "";
char tmp[128];
unsigned char http_protocol = 11, tmp8;
unsigned char req_type = 0;
if (C->web_req->u) {
Log(2, "%llu %s We have C->web_req->u; just call the callback\n",
C->id, __func__);
C->web_req->u->cb(C);
return;
}
url = Conn_ibuf(C);
Log(1, "\turl=%s\n", url);
end_header = Conn_strstr(C, "\r\n\r\n");
if (end_header == NULL)
return;
/* parsing first line */
sep = Conn_strstr(C, "\r\n");
*sep = '\0';
start_header = sep + 1;
if (strncmp(url, "GET ", 4) == 0) {
req_type = CONN_WEB_REQ_GET;
url += 4;
} else if (strncmp(url, "POST ", 5) == 0) {
req_type = CONN_WEB_REQ_POST;
url += 5;
}
if (unlikely(req_type == 0)) {
/* Invalid request */
snprintf(tmp, sizeof(tmp), "HTTP/1.1 500 Invalid request\r\n"
"\r\n"
"Invalid request");
Conn_enqueue(C, tmp, (unsigned int) strlen(tmp));
return;
}
sep = strchr(url, ' ');
if (sep)
*sep = '\0';
Log(2, "\tRequest req_type=%s [%s]!\n",
Conn_web_req_type(req_type), url);
/* TODO: parse HTTP type */
if (sep) {
sep = strstr(sep + 1, "HTTP/");
if (sep) {
sep += 5;
tmp8 = (unsigned char)(sep[0] - '0');
if ((sep[1] == '.') && (sep[2] != '\0'))
http_protocol = (unsigned char)(tmp8 * 10 + sep[2] - '0');
}
}
sep = strchr(url, '?');
if (sep) {
*sep = '\0';
paras = sep + 1;
}
u = C->web->urls;
while (u) {
char *dump;
int match;
Log(2, "\tComparing [%s] with [%s]...\n",
url, u->url);
if (u->type == CONN_WEB_TYPE_PATH)
match = strncmp(url, u->url, strlen(u->url));
else
match = strcmp(url, u->url);
if (match != 0) {
u = u->next;
continue;
}
C->web_req->req_type = req_type;
C->web_req->url = strdup(url);
C->web_req->paras = strdup(paras);
C->web_req->http_protocol = http_protocol;
C->web_req->header = start_header;
Conn_eat(C, end_header + 4 - Conn_ibuf(C));
dump = Conn_dump(Conn_ibuf(C), Conn_iqlen(C));
Log(0, "\tNOW ibuf (after eating header): [%s]\n", dump);
free(dump);
if (u->type == CONN_WEB_TYPE_PATH) {
Conn_web_dispatch_path(C, u);
} else {
Log(0, "\tDEBUG: Set C->web->u!\n");
C->web_req->u = u;
u->cb(C);
}
free(C->web_req->paras); C->web_req->paras = NULL;
free(C->web_req->url); C->web_req->url = NULL;
C->web_req->header = NULL;
/* TODO: also, if not keepalive is present */
if (C->web_req->http_protocol == 10)
Conn_close(C);
/* TODO: start timer to close connection */
break;
}
if (unlikely(!u)) {
/* not found */
Log(10, "\tNo match, sending 404\n");
snprintf(tmp, sizeof(tmp), "HTTP/1.1 404 Not found\r\n"
"\r\n"
"Path not found");
Conn_enqueue(C, tmp, strlen(tmp));
}
}
/*
* Returns a pointer to the HTTP headers
*/
char *Conn_web_header(const struct Conn *C)
{
return C->web_req->header;
}
/*
* Lookups a header (example Content-Type) and fills @out with the result.
* Returns -1 on error, 0 if not found, 1 if found.
*/
int Conn_web_header_lookup(char *out, const size_t out_size,
const struct Conn *C, const char *h)
{
unsigned short int hlen, i;
const char *pos;
Log(10, "%llu %s h=%s\n", C->id, __func__, h);
*out = '\0';
pos = C->web_req->header;
if (!pos) {
Log(0, "\tHeader is NULL!\n");
return -1;
}
hlen = strlen(h);
while (1) {
Log(20, "\tpos=%p: %s\n", pos, pos);
if (strncmp(pos, "\r\n", 2) == 0)
return -1;
if (strncasecmp(pos, h, hlen) != 0) {
pos = strstr(pos, "\r\n");
if (pos == NULL)
return 0;
pos += 2;
continue;
}
/* Found it! */
pos += hlen;
while (*pos == ' ')
pos++;
if (*pos != ':')
return -1;
pos++;
while (*pos == ' ')
pos++;
i = 0;
while (1) {
if ((i == out_size - 1) || (*pos == '\r')) {
out[i] = '\0';
return 1;
}
out[i++] = *pos;
pos++;
}
}
}
/* websocket */
char Conn_web_is_ws(const struct Conn *C)
{
if (!C->web_req)
return 0;
return C->web_req->req_type == CONN_WEB_REQ_WS;
}
void Conn_web_ws_negociate(struct Conn *C)
{
int r;
char key[128], accept[64];
unsigned char sha1[20];
Log(20, "%llu %s\n", C->id, __func__);
r = Conn_web_header_lookup(key, sizeof(key) - 40 - 1, C,
"Sec-WebSocket-Key");
if (r != 1) {
Log(20, "\tCannot find Sec-WebSocket-Key header!\n");
Conn_close(C);
return;
}
Log(21, "\treceived key=[%s]\n", key);
strcat(key, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
Log(21, "\tfinal key=%s\n", key);
/* Compute base64(sha1(key)) */
SHA1((unsigned char *) key, strlen(key), sha1);
EVP_EncodeBlock((unsigned char *) accept, sha1, sizeof(sha1));
Conn_eatall(C);
Conn_printf(C,
"HTTP/1.1 101 Switching Protocols\r\n"
"Server: websocket1\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Accept: %s\r\n"
"\r\n",
accept);
Conn_kick(C);
C->web_req->req_type = CONN_WEB_REQ_WS;
}
void Conn_web_ws_log(const struct Conn_web_ws *w)
{
Log(0, "fin=%hhu opcode=%hhu mask=%hhu"
" len=%llu maskkey=0x%hhx%hhx%hhx%hhx\n",
w->fin, w->opcode, w->mask, w->len,
w->maskkey[0], w->maskkey[1], w->maskkey[2], w->maskkey[3]);
}
/*
* Parses a buffer for a websocket packet
* Example: 88 82 5c c7 68 b9 5f 2e
* Returns -1 on error, 0 if we do not have enough input, else the len
*/
int Conn_web_ws_parse(struct Conn_web_ws *w, struct Conn *C)
{
unsigned char len_bytes = 0;
char *buf;
unsigned int i, len;
Log(20, "%llu %s\n", C->id, __func__);
if (Conn_iqlen(C) < 2)
return 0;
buf = Conn_ibuf(C);
w->fin = buf[0] >> 7;
w->opcode = buf[0];
w->mask = buf[1] >> 7;
w->len = buf[1] & 0x7F;
Conn_eat(C, 2);
if (w->opcode == 0x8) {
Log(20, "\tremote sent close opcode\n");
return -1;
}
if (unlikely(w->opcode != 0x1)) {
Log(20, "\topcode is not 'text' (%hhu)\n", w->opcode);
return -1;
}
if (w->mask != 1) {
Log(20, "\tinput is not masked!\n");
return -1;
}
if (w->len <= 125) {
len_bytes = 0;
} else if (w->len == 126) {
len_bytes = 2;
} else {
len_bytes = 8;
}
if (Conn_iqlen(C) < (unsigned int)(len_bytes + 4))
return 0;
buf = Conn_ibuf(C);
if (w->len == 126) {
w->len = (buf[0] << 8) | buf[1];
} else if (w->len == 127) {
w->len = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
buf += 4;
w->len <<= 32;
w->len |= (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
}
Conn_eat(C, len_bytes);
Log(20, "\tw->len=%llu\n", w->len);
buf = Conn_ibuf(C);
w->maskkey[0] = buf[0];
w->maskkey[1] = buf[1];
w->maskkey[2] = buf[2];
w->maskkey[3] = buf[3];
Conn_eat(C, 4);
if (Conn_iqlen(C) < w->len)
return 0;
buf = Conn_ibuf(C);
len = Conn_iqlen(C);
for (i = 0; i < len; i++)
buf[i] ^= w->maskkey[i % 4];
return len;
}
/*
* Send bytes respecting the websocket protocol
* @opcode (1 = text)
* @final: 0 (more data will follow) or 1 (final data)
*/
void Conn_web_ws_enqueue(struct Conn *C, const unsigned char opcode,
const unsigned char final, const char *s, const unsigned int len)
{
unsigned char buf[10], i;
i = 0;
buf[i++] = (!!final << 7) | opcode; // FIN | opcode (1 = text)
if (len <= 125) {
buf[i++] = len;
} else if (len <= 65535) {
buf[i++] = 126;
buf[i++] = len >> 8;
buf[i++] = len;
} else {
buf[i++] = 0;
buf[i++] = 0;
buf[i++] = 0;
buf[i++] = 0;
buf[i++] = len >> 24;
buf[i++] = len >> 16;
buf[i++] = len >> 8;
buf[i++] = len;
}
Conn_enqueue_wait(C, buf, i);
Conn_enqueue(C, s, len);
}