FUCK mongoose
This commit is contained in:
parent
6a284b89a9
commit
6cb858f7d2
4
Makefile
4
Makefile
@ -26,8 +26,8 @@ FFmpeg/libswscale/libswscale.a: emscr
|
|||||||
llvm-ar d FFmpeg/libavutil/libavutil.a log2_tab.o
|
llvm-ar d FFmpeg/libavutil/libavutil.a log2_tab.o
|
||||||
llvm-ar d FFmpeg/libavcodec/libavcodec.a reverse.o
|
llvm-ar d FFmpeg/libavcodec/libavcodec.a reverse.o
|
||||||
|
|
||||||
wsrA: main.c mongoose.c
|
wsrA: main2.c
|
||||||
cc -s -O3 -D_GNU_SOURCE -o wsrA main.c mongoose.c
|
cc -s -O3 -D_GNU_SOURCE -o wsrA main2.c picohttpparser.c
|
||||||
|
|
||||||
support.js: emscr ogg/libogg.a vorbis/lib/libvorbis.a FFmpeg/libswscale/libswscale.a
|
support.js: emscr ogg/libogg.a vorbis/lib/libvorbis.a FFmpeg/libswscale/libswscale.a
|
||||||
emcc -o support -fPIC -flto -IFFmpeg -Iogg/include -Ivorbis/include -LFFmpeg/libavcodec -l:libavcodec.a -LFFmpeg/libswscale -l:libswscale.a -LFFmpeg/libavutil -l:libavutil.a -Lvorbis/lib -l:libvorbis.a -Logg -l:libogg.a support.c -pthread -msimd128 -O3 -sMAYBE_WASM2JS -sUSE_PTHREADS=1 -sEXPORT_ALL=1 -sMAIN_MODULE=1 -sTOTAL_MEMORY=128MB
|
emcc -o support -fPIC -flto -IFFmpeg -Iogg/include -Ivorbis/include -LFFmpeg/libavcodec -l:libavcodec.a -LFFmpeg/libswscale -l:libswscale.a -LFFmpeg/libavutil -l:libavutil.a -Lvorbis/lib -l:libvorbis.a -Logg -l:libogg.a support.c -pthread -msimd128 -O3 -sMAYBE_WASM2JS -sUSE_PTHREADS=1 -sEXPORT_ALL=1 -sMAIN_MODULE=1 -sTOTAL_MEMORY=128MB
|
||||||
|
3
blarf.js
3
blarf.js
@ -385,6 +385,7 @@
|
|||||||
ws.binaryType = "arraybuffer"
|
ws.binaryType = "arraybuffer"
|
||||||
ws.onmessage = function(ev) {
|
ws.onmessage = function(ev) {
|
||||||
ebml.poosh(new Uint8Array(ev.data))
|
ebml.poosh(new Uint8Array(ev.data))
|
||||||
|
ebml.parse()
|
||||||
}
|
}
|
||||||
ws.onclose = function(ev) {
|
ws.onclose = function(ev) {
|
||||||
setTimeout(reconnect_ws, 5000)
|
setTimeout(reconnect_ws, 5000)
|
||||||
@ -393,8 +394,6 @@
|
|||||||
reconnect_ws()
|
reconnect_ws()
|
||||||
|
|
||||||
function render(timestamp) {
|
function render(timestamp) {
|
||||||
ebml.parse()
|
|
||||||
|
|
||||||
document.querySelector(".MKVControls").style.opacity = Math.max(0, Math.min(1, 5 - (timestamp - LastControlsInterrupt) / 1000))
|
document.querySelector(".MKVControls").style.opacity = Math.max(0, Math.min(1, 5 - (timestamp - LastControlsInterrupt) / 1000))
|
||||||
|
|
||||||
while(RenderStartTime && VideoQueue.length && VideoQueue[0].t + VideoBufferingOffset <= (timestamp - RenderStartTime)) {
|
while(RenderStartTime && VideoQueue.length && VideoQueue[0].t + VideoBufferingOffset <= (timestamp - RenderStartTime)) {
|
||||||
|
227
cb64.h
Normal file
227
cb64.h
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
#ifndef HEADER_CB64_H
|
||||||
|
#define HEADER_CB64_H
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define TRUE 1
|
||||||
|
#define FALSE 0
|
||||||
|
|
||||||
|
#define B64_TABLE_SIZE 64
|
||||||
|
|
||||||
|
#define B64_ENCODE_OK 0
|
||||||
|
#define B64_ENCODE_FAIL -1
|
||||||
|
#define B64_DECODE_OK 0
|
||||||
|
#define B64_DECODE_FAIL -1
|
||||||
|
|
||||||
|
struct base64_table_dict
|
||||||
|
{
|
||||||
|
char key;
|
||||||
|
uint8_t val;
|
||||||
|
} b64_dict_t;
|
||||||
|
|
||||||
|
static struct base64_table_dict dict[B64_TABLE_SIZE];
|
||||||
|
|
||||||
|
static const uint8_t BASE_64_TABLE[B64_TABLE_SIZE] = {
|
||||||
|
0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
|
||||||
|
0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50,
|
||||||
|
0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
|
||||||
|
0x59, 0x5A, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,
|
||||||
|
0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E,
|
||||||
|
0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76,
|
||||||
|
0x77, 0x78, 0x79, 0x7A, 0x30, 0x31, 0x32, 0x33,
|
||||||
|
0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2B, 0x2F
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint8_t PADDING = 0x3D;
|
||||||
|
static const uint8_t SIX_BIT_MASK = 0x3F; // 63 // 111111
|
||||||
|
static const uint8_t EIGHT_BIT_MASK = 0xFF; // 255 // 11111111
|
||||||
|
|
||||||
|
static void init_b64_table_dict()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < B64_TABLE_SIZE; i++)
|
||||||
|
{
|
||||||
|
b64_dict_t.key = BASE_64_TABLE[i];
|
||||||
|
b64_dict_t.val = i;
|
||||||
|
dict[i] = b64_dict_t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct base64_table_dict base64_table_dict_find(char key)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < B64_TABLE_SIZE; i++)
|
||||||
|
{
|
||||||
|
if (dict[i].key == key)
|
||||||
|
return dict[i];
|
||||||
|
}
|
||||||
|
return b64_dict_t;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t size_char_ptr(const unsigned char* arr)
|
||||||
|
{
|
||||||
|
uint64_t size = 0;
|
||||||
|
|
||||||
|
while(*arr)
|
||||||
|
{
|
||||||
|
size++;
|
||||||
|
arr++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
int encode_b64(const unsigned char* src, size_t src_size,
|
||||||
|
unsigned char** dst, size_t* dst_size)
|
||||||
|
{
|
||||||
|
if (src_size == 0)
|
||||||
|
src_size = size_char_ptr(src);
|
||||||
|
|
||||||
|
uint32_t _dst_size = 4 * ((src_size + 2) / 3);
|
||||||
|
|
||||||
|
// caller should call free()
|
||||||
|
unsigned char* _dst = (unsigned char*) malloc(sizeof(*_dst) * _dst_size + 3);
|
||||||
|
if (_dst == NULL)
|
||||||
|
return B64_ENCODE_FAIL;
|
||||||
|
|
||||||
|
FILE* f = fmemopen((void*)src, src_size, "r");
|
||||||
|
FILE* base64_res_f = fmemopen((void*)_dst, sizeof(*_dst) * _dst_size + 3, "w");
|
||||||
|
|
||||||
|
unsigned char buffer[4];
|
||||||
|
uint32_t size_out = 0;
|
||||||
|
uint32_t total_read = 0;
|
||||||
|
|
||||||
|
while(TRUE)
|
||||||
|
{
|
||||||
|
// read data per 3 bytes to buffer
|
||||||
|
size_t r = fread(buffer, 1, 3, f);
|
||||||
|
|
||||||
|
// check if the entire buffer has been read
|
||||||
|
if (r <= 0 || total_read == src_size)
|
||||||
|
break;
|
||||||
|
|
||||||
|
total_read += r;
|
||||||
|
|
||||||
|
// terminate the buffer with '\0'
|
||||||
|
buffer[r] = '\0';
|
||||||
|
|
||||||
|
uint32_t segment_count = 0;
|
||||||
|
uint32_t dec = 0;
|
||||||
|
for (int i = 0; i < r; i++)
|
||||||
|
{
|
||||||
|
// if (buffer[i] == '\0')
|
||||||
|
// continue;
|
||||||
|
|
||||||
|
uint32_t l_shift = 16 - segment_count * 8;
|
||||||
|
uint32_t s = (uint32_t) buffer[i];
|
||||||
|
dec |= s << l_shift;
|
||||||
|
segment_count = segment_count + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < segment_count+1; i++)
|
||||||
|
{
|
||||||
|
uint32_t r_shift = 18 - i * 6;
|
||||||
|
uint8_t idx_b = (uint8_t) (dec >> r_shift) & SIX_BIT_MASK;
|
||||||
|
|
||||||
|
uint8_t c_b64 = BASE_64_TABLE[idx_b];
|
||||||
|
|
||||||
|
fputc(c_b64, base64_res_f);
|
||||||
|
size_out = size_out + 1;
|
||||||
|
}
|
||||||
|
if (segment_count == 2)
|
||||||
|
{
|
||||||
|
fputc(PADDING, base64_res_f);
|
||||||
|
size_out = size_out + 2;
|
||||||
|
}
|
||||||
|
if (segment_count == 1)
|
||||||
|
{
|
||||||
|
fputc(PADDING, base64_res_f);
|
||||||
|
fputc(PADDING, base64_res_f);
|
||||||
|
size_out = size_out + 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
// terminate the buffer with '\0'
|
||||||
|
_dst[size_out] = '\0';
|
||||||
|
*dst = _dst;
|
||||||
|
*dst_size = size_out;
|
||||||
|
|
||||||
|
fclose(f);
|
||||||
|
fclose(base64_res_f);
|
||||||
|
|
||||||
|
return B64_ENCODE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int decode_b64(const unsigned char* src, size_t src_size,
|
||||||
|
unsigned char** dst, size_t* dst_size)
|
||||||
|
{
|
||||||
|
init_b64_table_dict();
|
||||||
|
if (src_size == 0)
|
||||||
|
src_size = size_char_ptr(src);
|
||||||
|
|
||||||
|
uint32_t _dst_size = src_size / 4 * 3;
|
||||||
|
|
||||||
|
// caller should call free()
|
||||||
|
unsigned char* _dst = (unsigned char*) malloc(sizeof(*_dst) * _dst_size + 1);
|
||||||
|
if (_dst == NULL)
|
||||||
|
return B64_DECODE_FAIL;
|
||||||
|
|
||||||
|
FILE* base64_in_f = fmemopen((void*)src, src_size, "r");
|
||||||
|
FILE* text_f = fmemopen((void*)_dst, sizeof(*_dst) * _dst_size + 1, "w");
|
||||||
|
|
||||||
|
unsigned char buffer[5];
|
||||||
|
uint32_t size_out = 0;
|
||||||
|
uint32_t total_read = 0;
|
||||||
|
|
||||||
|
while (TRUE)
|
||||||
|
{
|
||||||
|
// read data per 4 bytes to buffer
|
||||||
|
size_t r = fread(buffer, 1, 4, base64_in_f);
|
||||||
|
|
||||||
|
// check if the entire buffer has been read
|
||||||
|
if (r <= 0 || total_read == src_size)
|
||||||
|
break;
|
||||||
|
|
||||||
|
total_read += r;
|
||||||
|
|
||||||
|
// terminate the buffer with '\0'
|
||||||
|
buffer[r] = '\0';
|
||||||
|
|
||||||
|
uint32_t segment_count = 0;
|
||||||
|
uint32_t dec = 0;
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
if (buffer[i] == '\0' || buffer[i] == PADDING)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
struct base64_table_dict b64_idx_dict = base64_table_dict_find(buffer[i]);
|
||||||
|
uint32_t l_shift = 18 - segment_count * 6;
|
||||||
|
uint32_t b64_idx = (uint32_t) b64_idx_dict.val;
|
||||||
|
dec |= b64_idx << l_shift;
|
||||||
|
segment_count = segment_count + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < segment_count-1; i++)
|
||||||
|
{
|
||||||
|
uint32_t r_shift = 16 - i * 8;
|
||||||
|
uint8_t c_b255 = (uint8_t) (dec >> r_shift) & EIGHT_BIT_MASK;
|
||||||
|
|
||||||
|
fputc(c_b255, text_f);
|
||||||
|
size_out = size_out + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// terminate the buffer with '\0'
|
||||||
|
_dst[size_out] = '\0';
|
||||||
|
*dst = _dst;
|
||||||
|
*dst_size = size_out;
|
||||||
|
|
||||||
|
fclose(base64_in_f);
|
||||||
|
fclose(text_f);
|
||||||
|
|
||||||
|
return B64_DECODE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
168
main.c
168
main.c
@ -1,168 +0,0 @@
|
|||||||
#include"mongoose.h"
|
|
||||||
|
|
||||||
#include<getopt.h>
|
|
||||||
#include<stdio.h>
|
|
||||||
#include<string.h>
|
|
||||||
#include<stdbool.h>
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
LOADING_HEADER,
|
|
||||||
STREAMING,
|
|
||||||
} State;
|
|
||||||
|
|
||||||
static const uint8_t *STATE_CHANGE_STRING[] = {
|
|
||||||
[LOADING_HEADER] = "\x1F\x43\xB6\x75",
|
|
||||||
[STREAMING] = "\x1A\x45\xDF\xA3",
|
|
||||||
};
|
|
||||||
|
|
||||||
static State state = STREAMING;
|
|
||||||
static int stateChangeIdx;
|
|
||||||
|
|
||||||
static char *header;
|
|
||||||
static size_t headerSize;
|
|
||||||
|
|
||||||
static struct mg_connection *streamerConnected = NULL;
|
|
||||||
|
|
||||||
static struct {
|
|
||||||
char *wslisten;
|
|
||||||
char *tcplisten;
|
|
||||||
char *tlscert;
|
|
||||||
char *tlsca;
|
|
||||||
} settings;
|
|
||||||
|
|
||||||
static void ws_broadcast(struct mg_mgr *mgr, const char *data, size_t len) {
|
|
||||||
for(struct mg_connection *cli = mgr->conns; cli; cli = cli->next) {
|
|
||||||
if(cli->is_websocket) {
|
|
||||||
mg_ws_send(cli, data, len, WEBSOCKET_OP_BINARY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void fn(struct mg_connection *c, int ev, void *ev_data) {
|
|
||||||
if(ev == MG_EV_ACCEPT) {
|
|
||||||
if(mg_url_is_ssl(c->is_websocket ? settings.wslisten : settings.tcplisten)) {
|
|
||||||
struct mg_tls_opts opts = {.ca = mg_unpacked(settings.tlsca), .cert = mg_unpacked(settings.tlscert), .key = mg_unpacked(settings.tlscert)};
|
|
||||||
mg_tls_init(c, &opts);
|
|
||||||
}
|
|
||||||
} else if(ev == MG_EV_CLOSE) {
|
|
||||||
if(c == streamerConnected) {
|
|
||||||
streamerConnected = NULL;
|
|
||||||
}
|
|
||||||
} else if(ev == MG_EV_HTTP_MSG) {
|
|
||||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
|
||||||
mg_ws_upgrade(c, hm, NULL);
|
|
||||||
} else if(ev == MG_EV_WS_OPEN) {
|
|
||||||
if(state == STREAMING && header) {
|
|
||||||
mg_ws_send(c, header, headerSize, WEBSOCKET_OP_BINARY);
|
|
||||||
}
|
|
||||||
} else if(ev == MG_EV_WS_MSG) {
|
|
||||||
// Incoming WS messages are ignored.
|
|
||||||
} else if(ev == MG_EV_READ) {
|
|
||||||
if(!c->is_websocket) {
|
|
||||||
if(streamerConnected && streamerConnected != c) {
|
|
||||||
c->is_closing = 1;
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
streamerConnected = c;
|
|
||||||
}
|
|
||||||
} else return;
|
|
||||||
|
|
||||||
struct mg_iobuf *r = &c->recv;
|
|
||||||
|
|
||||||
if(state == LOADING_HEADER) {
|
|
||||||
header = realloc(header, headerSize + r->len);
|
|
||||||
memcpy(header + headerSize, r->buf, r->len);
|
|
||||||
headerSize += r->len;
|
|
||||||
|
|
||||||
char *clusterEl = memmem(header, headerSize, "\x1F\x43\xB6\x75", 4);
|
|
||||||
if(clusterEl) {
|
|
||||||
ws_broadcast(c->mgr, header, clusterEl - header);
|
|
||||||
ws_broadcast(c->mgr, clusterEl, header + headerSize - clusterEl);
|
|
||||||
|
|
||||||
headerSize = clusterEl - header;
|
|
||||||
state = STREAMING;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
int i;
|
|
||||||
for(i = 0; i < r->len; i++) {
|
|
||||||
if(r->buf[i] == STATE_CHANGE_STRING[state][stateChangeIdx]) {
|
|
||||||
stateChangeIdx++;
|
|
||||||
|
|
||||||
if(stateChangeIdx == strlen(STATE_CHANGE_STRING[state])) {
|
|
||||||
i++;
|
|
||||||
stateChangeIdx = 0;
|
|
||||||
state = LOADING_HEADER;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
stateChangeIdx = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(state == LOADING_HEADER) {
|
|
||||||
if(i > 4) {
|
|
||||||
ws_broadcast(c->mgr, r->buf, i - 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
header = realloc(header, headerSize = 4 + (r->len - i));
|
|
||||||
memcpy(header, STATE_CHANGE_STRING[STREAMING], 4);
|
|
||||||
memcpy(header + 4, r->buf + i, r->len - i);
|
|
||||||
} else {
|
|
||||||
ws_broadcast(c->mgr, r->buf, r->len);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
r->len = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
|
||||||
int help = 0, err = 0;
|
|
||||||
|
|
||||||
int c;
|
|
||||||
while((c = getopt(argc, argv, "a:c:i:o:h")) != -1) {
|
|
||||||
if(c == 'i') {
|
|
||||||
settings.tcplisten = optarg;
|
|
||||||
} else if(c == 'o') {
|
|
||||||
settings.wslisten = optarg;
|
|
||||||
} else if(c == 'a') {
|
|
||||||
settings.tlsca = optarg;
|
|
||||||
} else if(c == 'c') {
|
|
||||||
settings.tlscert = optarg;
|
|
||||||
} else if(c == 'h') {
|
|
||||||
help = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(help) {
|
|
||||||
fprintf(stderr, "Example usage: %s [-c /path/to/cert.pem] [-a /path/to/certauthority.pem] [-h] <-i tcp://[::]:1234> <-o ws://[::]:8000>\n", argv[0]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!settings.wslisten) {
|
|
||||||
fputs("Missing -o parameter. Try -h for help.\n", stderr);
|
|
||||||
err = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!settings.tcplisten) {
|
|
||||||
fputs("Missing -i parameter. Try -h for help\n", stderr);
|
|
||||||
err = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(err) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct mg_mgr mgr;
|
|
||||||
|
|
||||||
mg_mgr_init(&mgr);
|
|
||||||
|
|
||||||
mg_listen(&mgr, settings.tcplisten, fn, NULL);
|
|
||||||
mg_http_listen(&mgr, settings.wslisten, fn, NULL);
|
|
||||||
|
|
||||||
for (;;) mg_mgr_poll(&mgr, 1000);
|
|
||||||
|
|
||||||
mg_mgr_free(&mgr);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
502
main2.c
Normal file
502
main2.c
Normal file
@ -0,0 +1,502 @@
|
|||||||
|
#include<sys/socket.h>
|
||||||
|
#include<sys/epoll.h>
|
||||||
|
#include<netinet/in.h>
|
||||||
|
#include<netdb.h>
|
||||||
|
#include<sys/types.h>
|
||||||
|
#include<fcntl.h>
|
||||||
|
#include<unistd.h>
|
||||||
|
|
||||||
|
#include<stdbool.h>
|
||||||
|
#include<string.h>
|
||||||
|
#include<stdlib.h>
|
||||||
|
#include<stdio.h>
|
||||||
|
#include<assert.h>
|
||||||
|
#include<errno.h>
|
||||||
|
|
||||||
|
#include"picohttpparser.h"
|
||||||
|
#include"teeny-sha1.c"
|
||||||
|
#include"cb64.h"
|
||||||
|
|
||||||
|
#define EPOLL_EVS 2048
|
||||||
|
|
||||||
|
typedef int EPoll;
|
||||||
|
typedef int Socket;
|
||||||
|
|
||||||
|
typedef enum ClientType {
|
||||||
|
UNKNOWN,
|
||||||
|
CLI_STREAMER,
|
||||||
|
CLI_VIEWER
|
||||||
|
} ClientType;
|
||||||
|
|
||||||
|
typedef enum ClientState {
|
||||||
|
REQUEST,
|
||||||
|
ACTIVE,
|
||||||
|
WEBSOCKET,
|
||||||
|
} ClientState;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Socket fd;
|
||||||
|
|
||||||
|
ClientType type;
|
||||||
|
ClientState state;
|
||||||
|
|
||||||
|
size_t len, prevlen, cap;
|
||||||
|
uint8_t *buf;
|
||||||
|
|
||||||
|
// Only for streamers
|
||||||
|
struct phr_chunked_decoder chudec;
|
||||||
|
|
||||||
|
// Only for websockets
|
||||||
|
struct {
|
||||||
|
int opcode;
|
||||||
|
uint8_t *incoming;
|
||||||
|
size_t incomingSz;
|
||||||
|
} ws;
|
||||||
|
} Client;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
LOADING_HEADER,
|
||||||
|
STREAMING,
|
||||||
|
} StreamState;
|
||||||
|
static struct Stream {
|
||||||
|
StreamState state;
|
||||||
|
|
||||||
|
uint8_t *mkvHeader;
|
||||||
|
size_t mkvHeaderSz;
|
||||||
|
|
||||||
|
int stateChangeIdx;
|
||||||
|
} Stream;
|
||||||
|
|
||||||
|
static size_t clientsSz;
|
||||||
|
static Client **clients;
|
||||||
|
|
||||||
|
static char *ValidStreamPath = NULL;
|
||||||
|
|
||||||
|
static void consume(Client *cli, size_t n) {
|
||||||
|
memmove(cli->buf, cli->buf + n, cli->len - n);
|
||||||
|
cli->len -= n;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void transmit(Client *cli, const char *buf, size_t sz) {
|
||||||
|
while(sz) {
|
||||||
|
ssize_t s = send(cli->fd, buf, sz, 0);
|
||||||
|
|
||||||
|
if(s >= 0) {
|
||||||
|
buf += s;
|
||||||
|
sz -= s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void transmit_all(const char *buf, size_t sz) {
|
||||||
|
for(size_t i = 0; i < clientsSz; i++) {
|
||||||
|
if(clients[i]->state == WEBSOCKET) {
|
||||||
|
transmit(clients[i], buf, sz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#define WS_BIN 2
|
||||||
|
#define WS_CLOSE 8
|
||||||
|
#define WS_FIN 128
|
||||||
|
#define WS_HEADER_MAX 10
|
||||||
|
static int ws_header(size_t sz, uint8_t hdr[static WS_HEADER_MAX]) {
|
||||||
|
int i;
|
||||||
|
hdr[0] = WS_BIN | WS_FIN;
|
||||||
|
if(sz < 126) {
|
||||||
|
hdr[1] = sz;
|
||||||
|
i = 2;
|
||||||
|
} else if(sz < 65536) {
|
||||||
|
hdr[1] = 126;
|
||||||
|
hdr[2] = sz >> 8;
|
||||||
|
hdr[3] = sz & 0xFF;
|
||||||
|
i = 4;
|
||||||
|
} else {
|
||||||
|
hdr[1] = 127;
|
||||||
|
hdr[2] = (sz >> 56) & 0xFF;
|
||||||
|
hdr[3] = (sz >> 48) & 0xFF;
|
||||||
|
hdr[4] = (sz >> 40) & 0xFF;
|
||||||
|
hdr[5] = (sz >> 32) & 0xFF;
|
||||||
|
hdr[6] = (sz >> 24) & 0xFF;
|
||||||
|
hdr[7] = (sz >> 16) & 0xFF;
|
||||||
|
hdr[8] = (sz >> 8) & 0xFF;
|
||||||
|
hdr[9] = (sz >> 0) & 0xFF;
|
||||||
|
i = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ws_send(Client *cli, const uint8_t *buf, size_t sz) {
|
||||||
|
if(sz == 0) return;
|
||||||
|
|
||||||
|
uint8_t wshdr[WS_HEADER_MAX];
|
||||||
|
int wshdrsz = ws_header(sz, wshdr);
|
||||||
|
|
||||||
|
transmit(cli, wshdr, wshdrsz);
|
||||||
|
transmit(cli, buf, sz);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ws_broadcast(const uint8_t *buf, size_t sz) {
|
||||||
|
if(sz == 0) return;
|
||||||
|
|
||||||
|
uint8_t wshdr[WS_HEADER_MAX];
|
||||||
|
int wshdrsz = ws_header(sz, wshdr);
|
||||||
|
|
||||||
|
transmit_all(wshdr, wshdrsz);
|
||||||
|
transmit_all(buf, sz);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void stream_step(const uint8_t *newbuf, size_t newsz) {
|
||||||
|
if(Stream.state == LOADING_HEADER) {
|
||||||
|
Stream.mkvHeader = realloc(Stream.mkvHeader, Stream.mkvHeaderSz + newsz);
|
||||||
|
memcpy(Stream.mkvHeader + Stream.mkvHeaderSz, newbuf, newsz);
|
||||||
|
Stream.mkvHeaderSz += newsz;
|
||||||
|
|
||||||
|
uint8_t *clusterEl = memmem(Stream.mkvHeader, Stream.mkvHeaderSz, "\x1F\x43\xB6\x75", 4);
|
||||||
|
if(clusterEl) {
|
||||||
|
ws_broadcast(Stream.mkvHeader, clusterEl - Stream.mkvHeader);
|
||||||
|
ws_broadcast(clusterEl, Stream.mkvHeader + Stream.mkvHeaderSz - clusterEl);
|
||||||
|
|
||||||
|
Stream.mkvHeaderSz = clusterEl - Stream.mkvHeader;
|
||||||
|
Stream.state = STREAMING;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int i;
|
||||||
|
for(i = 0; i < newsz; i++) {
|
||||||
|
if(newbuf[i] == "\x1A\x45\xDF\xA3"[Stream.stateChangeIdx]) {
|
||||||
|
Stream.stateChangeIdx++;
|
||||||
|
|
||||||
|
if(Stream.stateChangeIdx == 4) {
|
||||||
|
i++;
|
||||||
|
Stream.stateChangeIdx = 0;
|
||||||
|
Stream.state = LOADING_HEADER;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Stream.stateChangeIdx = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Stream.state == LOADING_HEADER) {
|
||||||
|
if(i > 4) {
|
||||||
|
ws_broadcast(newbuf, i - 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream.mkvHeader = realloc(Stream.mkvHeader, Stream.mkvHeaderSz = 4 + (newsz - i));
|
||||||
|
memcpy(Stream.mkvHeader, "\x1A\x45\xDF\xA3", 4);
|
||||||
|
memcpy(Stream.mkvHeader + 4, newbuf + i, newsz - i);
|
||||||
|
} else {
|
||||||
|
ws_broadcast(newbuf, newsz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void receive_ws(Client *cli) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static int handle(Client *cli) {
|
||||||
|
while(cli->len != 0) {
|
||||||
|
if(cli->state == REQUEST) {
|
||||||
|
int minor_version;
|
||||||
|
struct phr_header headers[96];
|
||||||
|
const char *method, *path;
|
||||||
|
size_t method_len, path_len, num_headers = sizeof(headers) / sizeof(headers[0]);
|
||||||
|
int pret = phr_parse_request(cli->buf, cli->len, &method, &method_len, &path, &path_len, &minor_version, headers, &num_headers, cli->prevlen);
|
||||||
|
|
||||||
|
if(pret == -1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(pret == -2) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool connectionUpgrade = false;
|
||||||
|
bool upgradeWebSocket = false;
|
||||||
|
|
||||||
|
size_t wsAcceptLen;
|
||||||
|
unsigned char *wsAccept = NULL;
|
||||||
|
|
||||||
|
bool chunked = false;
|
||||||
|
|
||||||
|
for(size_t i = 0; i < num_headers; i++) {
|
||||||
|
if(strncmp(headers[i].name, "Upgrade", headers[i].name_len) == 0 && strncmp(headers[i].value, "websocket", headers[i].value_len) == 0) {
|
||||||
|
upgradeWebSocket = true;
|
||||||
|
} else if(strncmp(headers[i].name, "Connection", headers[i].name_len) == 0 && memmem(headers[i].value, headers[i].value_len, "Upgrade", 7)) {
|
||||||
|
connectionUpgrade = true;
|
||||||
|
} else if(strncmp(headers[i].name, "Transfer-Encoding", headers[i].name_len) == 0 && strncmp(headers[i].value, "chunked", headers[i].value_len) == 0) {
|
||||||
|
chunked = true;
|
||||||
|
} else if(strncmp(headers[i].name, "Sec-WebSocket-Key", headers[i].name_len) == 0) {
|
||||||
|
size_t acceptbufsz = headers[i].value_len + 36;
|
||||||
|
char acceptbuf[acceptbufsz];
|
||||||
|
memcpy(acceptbuf, headers[i].value, headers[i].value_len);
|
||||||
|
memcpy(acceptbuf + headers[i].value_len, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", 36);
|
||||||
|
|
||||||
|
char sha1bin[20];
|
||||||
|
char sha1hex[41];
|
||||||
|
sha1digest(sha1bin, sha1hex, acceptbuf, acceptbufsz);
|
||||||
|
|
||||||
|
encode_b64(sha1bin, sizeof(sha1bin), &wsAccept, &wsAcceptLen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(path_len == strlen(ValidStreamPath) && strncmp(path, ValidStreamPath, path_len) == 0) {
|
||||||
|
cli->type = CLI_STREAMER;
|
||||||
|
cli->state = ACTIVE;
|
||||||
|
|
||||||
|
if(upgradeWebSocket || connectionUpgrade || !chunked) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("New streamer client\n");
|
||||||
|
} else {
|
||||||
|
cli->type = CLI_VIEWER;
|
||||||
|
cli->state = WEBSOCKET;
|
||||||
|
|
||||||
|
if(!upgradeWebSocket || !connectionUpgrade || chunked || !wsAccept) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
char buf[1024];
|
||||||
|
int bufnum = snprintf(buf, sizeof(buf), "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: %.*s\r\n\r\n", (int) wsAcceptLen, wsAccept);
|
||||||
|
|
||||||
|
free(wsAccept);
|
||||||
|
|
||||||
|
transmit(cli, buf, bufnum);
|
||||||
|
|
||||||
|
if(Stream.state == STREAMING && Stream.mkvHeader) {
|
||||||
|
ws_send(cli, Stream.mkvHeader, Stream.mkvHeaderSz);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("New WS client\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
consume(cli, pret);
|
||||||
|
|
||||||
|
cli->prevlen = 0;
|
||||||
|
} else if(cli->state == ACTIVE) {
|
||||||
|
size_t rsize = cli->len;
|
||||||
|
int pret = phr_decode_chunked(&cli->chudec, cli->buf, &rsize);
|
||||||
|
|
||||||
|
if(pret == -1) return 0;
|
||||||
|
|
||||||
|
stream_step(cli->buf, rsize);
|
||||||
|
|
||||||
|
cli->len = 0;
|
||||||
|
|
||||||
|
if(pret == -2) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
} else if(cli->state == WEBSOCKET) {
|
||||||
|
if(cli->len < 2) return 1;
|
||||||
|
|
||||||
|
uint8_t framehdr = cli->buf[0];
|
||||||
|
|
||||||
|
bool fin = framehdr & 128;
|
||||||
|
int opcode = framehdr & 15;
|
||||||
|
|
||||||
|
if(cli->ws.opcode == 0 && opcode) {
|
||||||
|
cli->ws.opcode = opcode;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t payloadSz = 0;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
uint8_t payload0 = cli->buf[1] & 127;
|
||||||
|
if(payload0 < 126) {
|
||||||
|
payloadSz = payload0;
|
||||||
|
|
||||||
|
i = 2;
|
||||||
|
} else if(payload0 == 126) {
|
||||||
|
if(cli->len < 4) return 1;
|
||||||
|
|
||||||
|
payloadSz = (cli->buf[2] << 8) + cli->buf[3];
|
||||||
|
|
||||||
|
i = 4;
|
||||||
|
} else if(payload0 == 127) {
|
||||||
|
if(cli->len < 10) return 1;
|
||||||
|
|
||||||
|
payloadSz = ((uint64_t) cli->buf[2] << 56) + ((uint64_t) cli->buf[3] << 48) + ((uint64_t) cli->buf[4] << 40) + ((uint64_t) cli->buf[5] << 32) + ((uint64_t) cli->buf[6] << 24) + ((uint64_t) cli->buf[7] << 16) + ((uint64_t) cli->buf[8] << 8) + ((uint64_t) cli->buf[9] << 0);
|
||||||
|
|
||||||
|
i = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(payloadSz > 100) {
|
||||||
|
// Literally just kick
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(cli->len < i + 4 + payloadSz) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t mask[4] = {cli->buf[i], cli->buf[i + 1], cli->buf[i + 2], cli->buf[i + 3]};
|
||||||
|
|
||||||
|
for(size_t b = 0; b < payloadSz; b++) {
|
||||||
|
cli->buf[i + 4 + b] ^= mask[b % 4];
|
||||||
|
}
|
||||||
|
|
||||||
|
cli->ws.incoming = realloc(cli->ws.incoming, cli->ws.incomingSz + payloadSz);
|
||||||
|
memcpy(cli->ws.incoming + cli->ws.incomingSz, cli->buf + i + 4, payloadSz);
|
||||||
|
|
||||||
|
if(fin) {
|
||||||
|
receive_ws(cli);
|
||||||
|
|
||||||
|
if(cli->ws.opcode == WS_CLOSE) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
cli->ws.incomingSz = 0;
|
||||||
|
cli->ws.opcode = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rem_cli(Client *cli) {
|
||||||
|
for(size_t i = 0; i < clientsSz; i++) {
|
||||||
|
if(clients[i] == cli) {
|
||||||
|
memmove(clients + i, clients + i + 1, sizeof(*clients) * (clientsSz - i - 1));
|
||||||
|
clientsSz--;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Socket ServSock;
|
||||||
|
static EPoll EP;
|
||||||
|
|
||||||
|
static int Argc;
|
||||||
|
static char **Argv;
|
||||||
|
|
||||||
|
static const char *get_arg(const char *key, const char *def) {
|
||||||
|
int z = strlen(key);
|
||||||
|
|
||||||
|
for(size_t i = 1; i < Argc; i++) {
|
||||||
|
if(strlen(Argv[i]) > z && strstr(Argv[i], key) == Argv[i] && Argv[i][z] == '=') {
|
||||||
|
return Argv[i] + z + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool get_arg_bool(const char *key) {
|
||||||
|
const char *val = get_arg(key, "0");
|
||||||
|
return strtol(val, NULL, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
Argc = argc, Argv = argv;
|
||||||
|
|
||||||
|
ServSock = socket(AF_INET6, SOCK_STREAM | SOCK_NONBLOCK, 0);
|
||||||
|
EP = epoll_create1(0);
|
||||||
|
|
||||||
|
const char *streamkey = get_arg("key", NULL);
|
||||||
|
if(!streamkey) {
|
||||||
|
puts("Missing stream key parameter key=...");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ValidStreamPath = calloc(1, 6 + strlen(streamkey) + 1);
|
||||||
|
strcat(ValidStreamPath, "/push/");
|
||||||
|
strcat(ValidStreamPath, streamkey);
|
||||||
|
|
||||||
|
if(get_arg_bool("reuseaddr")) {
|
||||||
|
setsockopt(ServSock, SOL_SOCKET, SO_REUSEADDR, &(int) {1}, sizeof(int));
|
||||||
|
}
|
||||||
|
setsockopt(ServSock, IPPROTO_IPV6, IPV6_V6ONLY, &(int) {0}, sizeof(int));
|
||||||
|
|
||||||
|
assert(ServSock != -1);
|
||||||
|
|
||||||
|
struct addrinfo *res = NULL;
|
||||||
|
assert(getaddrinfo(NULL, get_arg("port", "25404"), &(struct addrinfo) {.ai_flags = AI_PASSIVE, .ai_family = AF_INET6}, &res) == 0);
|
||||||
|
|
||||||
|
assert(bind(ServSock, res->ai_addr, res->ai_addrlen) >= 0);
|
||||||
|
|
||||||
|
freeaddrinfo(res);
|
||||||
|
|
||||||
|
assert(listen(ServSock, 16) >= 0);
|
||||||
|
|
||||||
|
epoll_ctl(EP, EPOLL_CTL_ADD, ServSock, &(struct epoll_event) {.events = EPOLLIN | EPOLLOUT, .data = {.fd = ServSock}});
|
||||||
|
|
||||||
|
while(1) {
|
||||||
|
#define BUFSZ 8192
|
||||||
|
char buf[BUFSZ];
|
||||||
|
|
||||||
|
struct epoll_event events[EPOLL_EVS];
|
||||||
|
int nfds = epoll_wait(EP, events, EPOLL_EVS, -1);
|
||||||
|
for(int i = 0; i < nfds; i++) {
|
||||||
|
if(events[i].data.fd == ServSock) {
|
||||||
|
struct sockaddr_storage addr;
|
||||||
|
socklen_t addrlen;
|
||||||
|
|
||||||
|
Socket clisock = accept(ServSock, (struct sockaddr*) &addr, &addrlen);
|
||||||
|
|
||||||
|
if(fcntl(clisock, F_SETFL, fcntl(clisock, F_GETFL, 0) | O_NONBLOCK) == -1) {
|
||||||
|
close(clisock);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Client *cli = calloc(1, sizeof(*cli));
|
||||||
|
cli->fd = clisock;
|
||||||
|
cli->len = 0;
|
||||||
|
cli->buf = malloc(cli->cap = 8192);
|
||||||
|
|
||||||
|
epoll_ctl(EP, EPOLL_CTL_ADD, clisock, &(struct epoll_event) {.events = EPOLLIN | EPOLLRDHUP | EPOLLHUP, .data = {.ptr = cli}});
|
||||||
|
|
||||||
|
clients = realloc(clients, sizeof(*clients) * (clientsSz + 1));
|
||||||
|
clients[clientsSz++] = cli;
|
||||||
|
} else {
|
||||||
|
Client *cli = events[i].data.ptr;
|
||||||
|
|
||||||
|
bool forceclose = 0;
|
||||||
|
|
||||||
|
if(events[i].events & EPOLLIN) {
|
||||||
|
while(1) {
|
||||||
|
ssize_t readcount = recv(cli->fd, buf, sizeof(buf), 0);
|
||||||
|
|
||||||
|
if(readcount < 0) {
|
||||||
|
if(errno != EAGAIN && errno != EWOULDBLOCK) {
|
||||||
|
forceclose = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(cli->len + readcount > cli->cap) {
|
||||||
|
cli->buf = realloc(cli->buf, cli->cap = ((cli->len + readcount + 4095) & ~4095));
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(cli->buf + cli->len, buf, readcount);
|
||||||
|
|
||||||
|
cli->prevlen = cli->len;
|
||||||
|
cli->len += readcount;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(handle(cli) == 0) {
|
||||||
|
forceclose = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(forceclose || (events[i].events & (EPOLLRDHUP | EPOLLHUP))) {
|
||||||
|
epoll_ctl(EP, EPOLL_CTL_DEL, cli->fd, NULL);
|
||||||
|
close(cli->fd);
|
||||||
|
|
||||||
|
rem_cli(cli);
|
||||||
|
|
||||||
|
free(cli->buf);
|
||||||
|
|
||||||
|
free(cli->ws.incoming);
|
||||||
|
|
||||||
|
free(cli);
|
||||||
|
|
||||||
|
printf("Client left, now at %lu\n", clientsSz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19313
mongoose.c
19313
mongoose.c
File diff suppressed because it is too large
Load Diff
3220
mongoose.h
3220
mongoose.h
File diff suppressed because it is too large
Load Diff
685
picohttpparser.c
Normal file
685
picohttpparser.c
Normal file
@ -0,0 +1,685 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase,
|
||||||
|
* Shigeo Mitsunari
|
||||||
|
*
|
||||||
|
* The software is licensed under either the MIT License (below) or the Perl
|
||||||
|
* license.
|
||||||
|
*
|
||||||
|
* 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 <assert.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <string.h>
|
||||||
|
#ifdef __SSE4_2__
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#include <nmmintrin.h>
|
||||||
|
#else
|
||||||
|
#include <x86intrin.h>
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#include "picohttpparser.h"
|
||||||
|
|
||||||
|
#if __GNUC__ >= 3
|
||||||
|
#define likely(x) __builtin_expect(!!(x), 1)
|
||||||
|
#define unlikely(x) __builtin_expect(!!(x), 0)
|
||||||
|
#else
|
||||||
|
#define likely(x) (x)
|
||||||
|
#define unlikely(x) (x)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#define ALIGNED(n) _declspec(align(n))
|
||||||
|
#else
|
||||||
|
#define ALIGNED(n) __attribute__((aligned(n)))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define IS_PRINTABLE_ASCII(c) ((unsigned char)(c)-040u < 0137u)
|
||||||
|
|
||||||
|
#define CHECK_EOF() \
|
||||||
|
if (buf == buf_end) { \
|
||||||
|
*ret = -2; \
|
||||||
|
return NULL; \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define EXPECT_CHAR_NO_CHECK(ch) \
|
||||||
|
if (*buf++ != ch) { \
|
||||||
|
*ret = -1; \
|
||||||
|
return NULL; \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define EXPECT_CHAR(ch) \
|
||||||
|
CHECK_EOF(); \
|
||||||
|
EXPECT_CHAR_NO_CHECK(ch);
|
||||||
|
|
||||||
|
#define ADVANCE_TOKEN(tok, toklen) \
|
||||||
|
do { \
|
||||||
|
const char *tok_start = buf; \
|
||||||
|
static const char ALIGNED(16) ranges2[16] = "\000\040\177\177"; \
|
||||||
|
int found2; \
|
||||||
|
buf = findchar_fast(buf, buf_end, ranges2, 4, &found2); \
|
||||||
|
if (!found2) { \
|
||||||
|
CHECK_EOF(); \
|
||||||
|
} \
|
||||||
|
while (1) { \
|
||||||
|
if (*buf == ' ') { \
|
||||||
|
break; \
|
||||||
|
} else if (unlikely(!IS_PRINTABLE_ASCII(*buf))) { \
|
||||||
|
if ((unsigned char)*buf < '\040' || *buf == '\177') { \
|
||||||
|
*ret = -1; \
|
||||||
|
return NULL; \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
++buf; \
|
||||||
|
CHECK_EOF(); \
|
||||||
|
} \
|
||||||
|
tok = tok_start; \
|
||||||
|
toklen = buf - tok_start; \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
static const char *token_char_map = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||||
|
"\0\1\0\1\1\1\1\1\0\0\1\1\0\1\1\0\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0"
|
||||||
|
"\0\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\1\1"
|
||||||
|
"\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\1\0\1\0"
|
||||||
|
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||||
|
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||||
|
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
|
||||||
|
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
|
||||||
|
|
||||||
|
static const char *findchar_fast(const char *buf, const char *buf_end, const char *ranges, size_t ranges_size, int *found)
|
||||||
|
{
|
||||||
|
*found = 0;
|
||||||
|
#if __SSE4_2__
|
||||||
|
if (likely(buf_end - buf >= 16)) {
|
||||||
|
__m128i ranges16 = _mm_loadu_si128((const __m128i *)ranges);
|
||||||
|
|
||||||
|
size_t left = (buf_end - buf) & ~15;
|
||||||
|
do {
|
||||||
|
__m128i b16 = _mm_loadu_si128((const __m128i *)buf);
|
||||||
|
int r = _mm_cmpestri(ranges16, ranges_size, b16, 16, _SIDD_LEAST_SIGNIFICANT | _SIDD_CMP_RANGES | _SIDD_UBYTE_OPS);
|
||||||
|
if (unlikely(r != 16)) {
|
||||||
|
buf += r;
|
||||||
|
*found = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
buf += 16;
|
||||||
|
left -= 16;
|
||||||
|
} while (likely(left != 0));
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
/* suppress unused parameter warning */
|
||||||
|
(void)buf_end;
|
||||||
|
(void)ranges;
|
||||||
|
(void)ranges_size;
|
||||||
|
#endif
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *get_token_to_eol(const char *buf, const char *buf_end, const char **token, size_t *token_len, int *ret)
|
||||||
|
{
|
||||||
|
const char *token_start = buf;
|
||||||
|
|
||||||
|
#ifdef __SSE4_2__
|
||||||
|
static const char ALIGNED(16) ranges1[16] = "\0\010" /* allow HT */
|
||||||
|
"\012\037" /* allow SP and up to but not including DEL */
|
||||||
|
"\177\177"; /* allow chars w. MSB set */
|
||||||
|
int found;
|
||||||
|
buf = findchar_fast(buf, buf_end, ranges1, 6, &found);
|
||||||
|
if (found)
|
||||||
|
goto FOUND_CTL;
|
||||||
|
#else
|
||||||
|
/* find non-printable char within the next 8 bytes, this is the hottest code; manually inlined */
|
||||||
|
while (likely(buf_end - buf >= 8)) {
|
||||||
|
#define DOIT() \
|
||||||
|
do { \
|
||||||
|
if (unlikely(!IS_PRINTABLE_ASCII(*buf))) \
|
||||||
|
goto NonPrintable; \
|
||||||
|
++buf; \
|
||||||
|
} while (0)
|
||||||
|
DOIT();
|
||||||
|
DOIT();
|
||||||
|
DOIT();
|
||||||
|
DOIT();
|
||||||
|
DOIT();
|
||||||
|
DOIT();
|
||||||
|
DOIT();
|
||||||
|
DOIT();
|
||||||
|
#undef DOIT
|
||||||
|
continue;
|
||||||
|
NonPrintable:
|
||||||
|
if ((likely((unsigned char)*buf < '\040') && likely(*buf != '\011')) || unlikely(*buf == '\177')) {
|
||||||
|
goto FOUND_CTL;
|
||||||
|
}
|
||||||
|
++buf;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
for (;; ++buf) {
|
||||||
|
CHECK_EOF();
|
||||||
|
if (unlikely(!IS_PRINTABLE_ASCII(*buf))) {
|
||||||
|
if ((likely((unsigned char)*buf < '\040') && likely(*buf != '\011')) || unlikely(*buf == '\177')) {
|
||||||
|
goto FOUND_CTL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FOUND_CTL:
|
||||||
|
if (likely(*buf == '\015')) {
|
||||||
|
++buf;
|
||||||
|
EXPECT_CHAR('\012');
|
||||||
|
*token_len = buf - 2 - token_start;
|
||||||
|
} else if (*buf == '\012') {
|
||||||
|
*token_len = buf - token_start;
|
||||||
|
++buf;
|
||||||
|
} else {
|
||||||
|
*ret = -1;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
*token = token_start;
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *is_complete(const char *buf, const char *buf_end, size_t last_len, int *ret)
|
||||||
|
{
|
||||||
|
int ret_cnt = 0;
|
||||||
|
buf = last_len < 3 ? buf : buf + last_len - 3;
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
CHECK_EOF();
|
||||||
|
if (*buf == '\015') {
|
||||||
|
++buf;
|
||||||
|
CHECK_EOF();
|
||||||
|
EXPECT_CHAR('\012');
|
||||||
|
++ret_cnt;
|
||||||
|
} else if (*buf == '\012') {
|
||||||
|
++buf;
|
||||||
|
++ret_cnt;
|
||||||
|
} else {
|
||||||
|
++buf;
|
||||||
|
ret_cnt = 0;
|
||||||
|
}
|
||||||
|
if (ret_cnt == 2) {
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*ret = -2;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define PARSE_INT(valp_, mul_) \
|
||||||
|
if (*buf < '0' || '9' < *buf) { \
|
||||||
|
buf++; \
|
||||||
|
*ret = -1; \
|
||||||
|
return NULL; \
|
||||||
|
} \
|
||||||
|
*(valp_) = (mul_) * (*buf++ - '0');
|
||||||
|
|
||||||
|
#define PARSE_INT_3(valp_) \
|
||||||
|
do { \
|
||||||
|
int res_ = 0; \
|
||||||
|
PARSE_INT(&res_, 100) \
|
||||||
|
*valp_ = res_; \
|
||||||
|
PARSE_INT(&res_, 10) \
|
||||||
|
*valp_ += res_; \
|
||||||
|
PARSE_INT(&res_, 1) \
|
||||||
|
*valp_ += res_; \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/* returned pointer is always within [buf, buf_end), or null */
|
||||||
|
static const char *parse_token(const char *buf, const char *buf_end, const char **token, size_t *token_len, char next_char,
|
||||||
|
int *ret)
|
||||||
|
{
|
||||||
|
/* We use pcmpestri to detect non-token characters. This instruction can take no more than eight character ranges (8*2*8=128
|
||||||
|
* bits that is the size of a SSE register). Due to this restriction, characters `|` and `~` are handled in the slow loop. */
|
||||||
|
static const char ALIGNED(16) ranges[] = "\x00 " /* control chars and up to SP */
|
||||||
|
"\"\"" /* 0x22 */
|
||||||
|
"()" /* 0x28,0x29 */
|
||||||
|
",," /* 0x2c */
|
||||||
|
"//" /* 0x2f */
|
||||||
|
":@" /* 0x3a-0x40 */
|
||||||
|
"[]" /* 0x5b-0x5d */
|
||||||
|
"{\xff"; /* 0x7b-0xff */
|
||||||
|
const char *buf_start = buf;
|
||||||
|
int found;
|
||||||
|
buf = findchar_fast(buf, buf_end, ranges, sizeof(ranges) - 1, &found);
|
||||||
|
if (!found) {
|
||||||
|
CHECK_EOF();
|
||||||
|
}
|
||||||
|
while (1) {
|
||||||
|
if (*buf == next_char) {
|
||||||
|
break;
|
||||||
|
} else if (!token_char_map[(unsigned char)*buf]) {
|
||||||
|
*ret = -1;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
++buf;
|
||||||
|
CHECK_EOF();
|
||||||
|
}
|
||||||
|
*token = buf_start;
|
||||||
|
*token_len = buf - buf_start;
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* returned pointer is always within [buf, buf_end), or null */
|
||||||
|
static const char *parse_http_version(const char *buf, const char *buf_end, int *minor_version, int *ret)
|
||||||
|
{
|
||||||
|
/* we want at least [HTTP/1.<two chars>] to try to parse */
|
||||||
|
if (buf_end - buf < 9) {
|
||||||
|
*ret = -2;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
EXPECT_CHAR_NO_CHECK('H');
|
||||||
|
EXPECT_CHAR_NO_CHECK('T');
|
||||||
|
EXPECT_CHAR_NO_CHECK('T');
|
||||||
|
EXPECT_CHAR_NO_CHECK('P');
|
||||||
|
EXPECT_CHAR_NO_CHECK('/');
|
||||||
|
EXPECT_CHAR_NO_CHECK('1');
|
||||||
|
EXPECT_CHAR_NO_CHECK('.');
|
||||||
|
PARSE_INT(minor_version, 1);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *parse_headers(const char *buf, const char *buf_end, struct phr_header *headers, size_t *num_headers,
|
||||||
|
size_t max_headers, int *ret)
|
||||||
|
{
|
||||||
|
for (;; ++*num_headers) {
|
||||||
|
CHECK_EOF();
|
||||||
|
if (*buf == '\015') {
|
||||||
|
++buf;
|
||||||
|
EXPECT_CHAR('\012');
|
||||||
|
break;
|
||||||
|
} else if (*buf == '\012') {
|
||||||
|
++buf;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (*num_headers == max_headers) {
|
||||||
|
*ret = -1;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (!(*num_headers != 0 && (*buf == ' ' || *buf == '\t'))) {
|
||||||
|
/* parsing name, but do not discard SP before colon, see
|
||||||
|
* http://www.mozilla.org/security/announce/2006/mfsa2006-33.html */
|
||||||
|
if ((buf = parse_token(buf, buf_end, &headers[*num_headers].name, &headers[*num_headers].name_len, ':', ret)) == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (headers[*num_headers].name_len == 0) {
|
||||||
|
*ret = -1;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
++buf;
|
||||||
|
for (;; ++buf) {
|
||||||
|
CHECK_EOF();
|
||||||
|
if (!(*buf == ' ' || *buf == '\t')) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
headers[*num_headers].name = NULL;
|
||||||
|
headers[*num_headers].name_len = 0;
|
||||||
|
}
|
||||||
|
const char *value;
|
||||||
|
size_t value_len;
|
||||||
|
if ((buf = get_token_to_eol(buf, buf_end, &value, &value_len, ret)) == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
/* remove trailing SPs and HTABs */
|
||||||
|
const char *value_end = value + value_len;
|
||||||
|
for (; value_end != value; --value_end) {
|
||||||
|
const char c = *(value_end - 1);
|
||||||
|
if (!(c == ' ' || c == '\t')) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
headers[*num_headers].value = value;
|
||||||
|
headers[*num_headers].value_len = value_end - value;
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *parse_request(const char *buf, const char *buf_end, const char **method, size_t *method_len, const char **path,
|
||||||
|
size_t *path_len, int *minor_version, struct phr_header *headers, size_t *num_headers,
|
||||||
|
size_t max_headers, int *ret)
|
||||||
|
{
|
||||||
|
/* skip first empty line (some clients add CRLF after POST content) */
|
||||||
|
CHECK_EOF();
|
||||||
|
if (*buf == '\015') {
|
||||||
|
++buf;
|
||||||
|
EXPECT_CHAR('\012');
|
||||||
|
} else if (*buf == '\012') {
|
||||||
|
++buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* parse request line */
|
||||||
|
if ((buf = parse_token(buf, buf_end, method, method_len, ' ', ret)) == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
++buf;
|
||||||
|
CHECK_EOF();
|
||||||
|
} while (*buf == ' ');
|
||||||
|
ADVANCE_TOKEN(*path, *path_len);
|
||||||
|
do {
|
||||||
|
++buf;
|
||||||
|
CHECK_EOF();
|
||||||
|
} while (*buf == ' ');
|
||||||
|
if (*method_len == 0 || *path_len == 0) {
|
||||||
|
*ret = -1;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (*buf == '\015') {
|
||||||
|
++buf;
|
||||||
|
EXPECT_CHAR('\012');
|
||||||
|
} else if (*buf == '\012') {
|
||||||
|
++buf;
|
||||||
|
} else {
|
||||||
|
*ret = -1;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
int phr_parse_request(const char *buf_start, size_t len, const char **method, size_t *method_len, const char **path,
|
||||||
|
size_t *path_len, int *minor_version, struct phr_header *headers, size_t *num_headers, size_t last_len)
|
||||||
|
{
|
||||||
|
const char *buf = buf_start, *buf_end = buf_start + len;
|
||||||
|
size_t max_headers = *num_headers;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
*method = NULL;
|
||||||
|
*method_len = 0;
|
||||||
|
*path = NULL;
|
||||||
|
*path_len = 0;
|
||||||
|
*minor_version = -1;
|
||||||
|
*num_headers = 0;
|
||||||
|
|
||||||
|
/* if last_len != 0, check if the request is complete (a fast countermeasure
|
||||||
|
againt slowloris */
|
||||||
|
if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((buf = parse_request(buf, buf_end, method, method_len, path, path_len, minor_version, headers, num_headers, max_headers,
|
||||||
|
&r)) == NULL) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int)(buf - buf_start);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *parse_response(const char *buf, const char *buf_end, int *minor_version, int *status, const char **msg,
|
||||||
|
size_t *msg_len, struct phr_header *headers, size_t *num_headers, size_t max_headers, int *ret)
|
||||||
|
{
|
||||||
|
/* parse "HTTP/1.x" */
|
||||||
|
if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
/* skip space */
|
||||||
|
if (*buf != ' ') {
|
||||||
|
*ret = -1;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
++buf;
|
||||||
|
CHECK_EOF();
|
||||||
|
} while (*buf == ' ');
|
||||||
|
/* parse status code, we want at least [:digit:][:digit:][:digit:]<other char> to try to parse */
|
||||||
|
if (buf_end - buf < 4) {
|
||||||
|
*ret = -2;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
PARSE_INT_3(status);
|
||||||
|
|
||||||
|
/* get message including preceding space */
|
||||||
|
if ((buf = get_token_to_eol(buf, buf_end, msg, msg_len, ret)) == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (*msg_len == 0) {
|
||||||
|
/* ok */
|
||||||
|
} else if (**msg == ' ') {
|
||||||
|
/* Remove preceding space. Successful return from `get_token_to_eol` guarantees that we would hit something other than SP
|
||||||
|
* before running past the end of the given buffer. */
|
||||||
|
do {
|
||||||
|
++*msg;
|
||||||
|
--*msg_len;
|
||||||
|
} while (**msg == ' ');
|
||||||
|
} else {
|
||||||
|
/* garbage found after status code */
|
||||||
|
*ret = -1;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
int phr_parse_response(const char *buf_start, size_t len, int *minor_version, int *status, const char **msg, size_t *msg_len,
|
||||||
|
struct phr_header *headers, size_t *num_headers, size_t last_len)
|
||||||
|
{
|
||||||
|
const char *buf = buf_start, *buf_end = buf + len;
|
||||||
|
size_t max_headers = *num_headers;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
*minor_version = -1;
|
||||||
|
*status = 0;
|
||||||
|
*msg = NULL;
|
||||||
|
*msg_len = 0;
|
||||||
|
*num_headers = 0;
|
||||||
|
|
||||||
|
/* if last_len != 0, check if the response is complete (a fast countermeasure
|
||||||
|
against slowloris */
|
||||||
|
if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((buf = parse_response(buf, buf_end, minor_version, status, msg, msg_len, headers, num_headers, max_headers, &r)) == NULL) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int)(buf - buf_start);
|
||||||
|
}
|
||||||
|
|
||||||
|
int phr_parse_headers(const char *buf_start, size_t len, struct phr_header *headers, size_t *num_headers, size_t last_len)
|
||||||
|
{
|
||||||
|
const char *buf = buf_start, *buf_end = buf + len;
|
||||||
|
size_t max_headers = *num_headers;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
*num_headers = 0;
|
||||||
|
|
||||||
|
/* if last_len != 0, check if the response is complete (a fast countermeasure
|
||||||
|
against slowloris */
|
||||||
|
if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((buf = parse_headers(buf, buf_end, headers, num_headers, max_headers, &r)) == NULL) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int)(buf - buf_start);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum {
|
||||||
|
CHUNKED_IN_CHUNK_SIZE,
|
||||||
|
CHUNKED_IN_CHUNK_EXT,
|
||||||
|
CHUNKED_IN_CHUNK_DATA,
|
||||||
|
CHUNKED_IN_CHUNK_CRLF,
|
||||||
|
CHUNKED_IN_TRAILERS_LINE_HEAD,
|
||||||
|
CHUNKED_IN_TRAILERS_LINE_MIDDLE
|
||||||
|
};
|
||||||
|
|
||||||
|
static int decode_hex(int ch)
|
||||||
|
{
|
||||||
|
if ('0' <= ch && ch <= '9') {
|
||||||
|
return ch - '0';
|
||||||
|
} else if ('A' <= ch && ch <= 'F') {
|
||||||
|
return ch - 'A' + 0xa;
|
||||||
|
} else if ('a' <= ch && ch <= 'f') {
|
||||||
|
return ch - 'a' + 0xa;
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf, size_t *_bufsz)
|
||||||
|
{
|
||||||
|
size_t dst = 0, src = 0, bufsz = *_bufsz;
|
||||||
|
ssize_t ret = -2; /* incomplete */
|
||||||
|
|
||||||
|
decoder->_total_read += bufsz;
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
switch (decoder->_state) {
|
||||||
|
case CHUNKED_IN_CHUNK_SIZE:
|
||||||
|
for (;; ++src) {
|
||||||
|
int v;
|
||||||
|
if (src == bufsz)
|
||||||
|
goto Exit;
|
||||||
|
if ((v = decode_hex(buf[src])) == -1) {
|
||||||
|
if (decoder->_hex_count == 0) {
|
||||||
|
ret = -1;
|
||||||
|
goto Exit;
|
||||||
|
}
|
||||||
|
/* the only characters that may appear after the chunk size are BWS, semicolon, or CRLF */
|
||||||
|
switch (buf[src]) {
|
||||||
|
case ' ':
|
||||||
|
case '\011':
|
||||||
|
case ';':
|
||||||
|
case '\012':
|
||||||
|
case '\015':
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ret = -1;
|
||||||
|
goto Exit;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (decoder->_hex_count == sizeof(size_t) * 2) {
|
||||||
|
ret = -1;
|
||||||
|
goto Exit;
|
||||||
|
}
|
||||||
|
decoder->bytes_left_in_chunk = decoder->bytes_left_in_chunk * 16 + v;
|
||||||
|
++decoder->_hex_count;
|
||||||
|
}
|
||||||
|
decoder->_hex_count = 0;
|
||||||
|
decoder->_state = CHUNKED_IN_CHUNK_EXT;
|
||||||
|
/* fallthru */
|
||||||
|
case CHUNKED_IN_CHUNK_EXT:
|
||||||
|
/* RFC 7230 A.2 "Line folding in chunk extensions is disallowed" */
|
||||||
|
for (;; ++src) {
|
||||||
|
if (src == bufsz)
|
||||||
|
goto Exit;
|
||||||
|
if (buf[src] == '\012')
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++src;
|
||||||
|
if (decoder->bytes_left_in_chunk == 0) {
|
||||||
|
if (decoder->consume_trailer) {
|
||||||
|
decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
goto Complete;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
decoder->_state = CHUNKED_IN_CHUNK_DATA;
|
||||||
|
/* fallthru */
|
||||||
|
case CHUNKED_IN_CHUNK_DATA: {
|
||||||
|
size_t avail = bufsz - src;
|
||||||
|
if (avail < decoder->bytes_left_in_chunk) {
|
||||||
|
if (dst != src)
|
||||||
|
memmove(buf + dst, buf + src, avail);
|
||||||
|
src += avail;
|
||||||
|
dst += avail;
|
||||||
|
decoder->bytes_left_in_chunk -= avail;
|
||||||
|
goto Exit;
|
||||||
|
}
|
||||||
|
if (dst != src)
|
||||||
|
memmove(buf + dst, buf + src, decoder->bytes_left_in_chunk);
|
||||||
|
src += decoder->bytes_left_in_chunk;
|
||||||
|
dst += decoder->bytes_left_in_chunk;
|
||||||
|
decoder->bytes_left_in_chunk = 0;
|
||||||
|
decoder->_state = CHUNKED_IN_CHUNK_CRLF;
|
||||||
|
}
|
||||||
|
/* fallthru */
|
||||||
|
case CHUNKED_IN_CHUNK_CRLF:
|
||||||
|
for (;; ++src) {
|
||||||
|
if (src == bufsz)
|
||||||
|
goto Exit;
|
||||||
|
if (buf[src] != '\015')
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (buf[src] != '\012') {
|
||||||
|
ret = -1;
|
||||||
|
goto Exit;
|
||||||
|
}
|
||||||
|
++src;
|
||||||
|
decoder->_state = CHUNKED_IN_CHUNK_SIZE;
|
||||||
|
break;
|
||||||
|
case CHUNKED_IN_TRAILERS_LINE_HEAD:
|
||||||
|
for (;; ++src) {
|
||||||
|
if (src == bufsz)
|
||||||
|
goto Exit;
|
||||||
|
if (buf[src] != '\015')
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (buf[src++] == '\012')
|
||||||
|
goto Complete;
|
||||||
|
decoder->_state = CHUNKED_IN_TRAILERS_LINE_MIDDLE;
|
||||||
|
/* fallthru */
|
||||||
|
case CHUNKED_IN_TRAILERS_LINE_MIDDLE:
|
||||||
|
for (;; ++src) {
|
||||||
|
if (src == bufsz)
|
||||||
|
goto Exit;
|
||||||
|
if (buf[src] == '\012')
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++src;
|
||||||
|
decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert(!"decoder is corrupt");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Complete:
|
||||||
|
ret = bufsz - src;
|
||||||
|
Exit:
|
||||||
|
if (dst != src)
|
||||||
|
memmove(buf + dst, buf + src, bufsz - src);
|
||||||
|
*_bufsz = dst;
|
||||||
|
/* if incomplete but the overhead of the chunked encoding is >=100KB and >80%, signal an error */
|
||||||
|
if (ret == -2) {
|
||||||
|
decoder->_total_overhead += bufsz - dst;
|
||||||
|
if (decoder->_total_overhead >= 100 * 1024 && decoder->_total_read - decoder->_total_overhead < decoder->_total_read / 4)
|
||||||
|
ret = -1;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int phr_decode_chunked_is_in_data(struct phr_chunked_decoder *decoder)
|
||||||
|
{
|
||||||
|
return decoder->_state == CHUNKED_IN_CHUNK_DATA;
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef CHECK_EOF
|
||||||
|
#undef EXPECT_CHAR
|
||||||
|
#undef ADVANCE_TOKEN
|
90
picohttpparser.h
Normal file
90
picohttpparser.h
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase,
|
||||||
|
* Shigeo Mitsunari
|
||||||
|
*
|
||||||
|
* The software is licensed under either the MIT License (below) or the Perl
|
||||||
|
* license.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef picohttpparser_h
|
||||||
|
#define picohttpparser_h
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#define ssize_t intptr_t
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* contains name and value of a header (name == NULL if is a continuing line
|
||||||
|
* of a multiline header */
|
||||||
|
struct phr_header {
|
||||||
|
const char *name;
|
||||||
|
size_t name_len;
|
||||||
|
const char *value;
|
||||||
|
size_t value_len;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* returns number of bytes consumed if successful, -2 if request is partial,
|
||||||
|
* -1 if failed */
|
||||||
|
int phr_parse_request(const char *buf, size_t len, const char **method, size_t *method_len, const char **path, size_t *path_len,
|
||||||
|
int *minor_version, struct phr_header *headers, size_t *num_headers, size_t last_len);
|
||||||
|
|
||||||
|
/* ditto */
|
||||||
|
int phr_parse_response(const char *_buf, size_t len, int *minor_version, int *status, const char **msg, size_t *msg_len,
|
||||||
|
struct phr_header *headers, size_t *num_headers, size_t last_len);
|
||||||
|
|
||||||
|
/* ditto */
|
||||||
|
int phr_parse_headers(const char *buf, size_t len, struct phr_header *headers, size_t *num_headers, size_t last_len);
|
||||||
|
|
||||||
|
/* should be zero-filled before start */
|
||||||
|
struct phr_chunked_decoder {
|
||||||
|
size_t bytes_left_in_chunk; /* number of bytes left in current chunk */
|
||||||
|
char consume_trailer; /* if trailing headers should be consumed */
|
||||||
|
char _hex_count;
|
||||||
|
char _state;
|
||||||
|
uint64_t _total_read;
|
||||||
|
uint64_t _total_overhead;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* the function rewrites the buffer given as (buf, bufsz) removing the chunked-
|
||||||
|
* encoding headers. When the function returns without an error, bufsz is
|
||||||
|
* updated to the length of the decoded data available. Applications should
|
||||||
|
* repeatedly call the function while it returns -2 (incomplete) every time
|
||||||
|
* supplying newly arrived data. If the end of the chunked-encoded data is
|
||||||
|
* found, the function returns a non-negative number indicating the number of
|
||||||
|
* octets left undecoded, that starts from the offset returned by `*bufsz`.
|
||||||
|
* Returns -1 on error.
|
||||||
|
*/
|
||||||
|
ssize_t phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf, size_t *bufsz);
|
||||||
|
|
||||||
|
/* returns if the chunked decoder is in middle of chunked data */
|
||||||
|
int phr_decode_chunked_is_in_data(struct phr_chunked_decoder *decoder);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
201
teeny-sha1.c
Normal file
201
teeny-sha1.c
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* Teeny SHA-1
|
||||||
|
*
|
||||||
|
* The below sha1digest() calculates a SHA-1 hash value for a
|
||||||
|
* specified data buffer and generates a hex representation of the
|
||||||
|
* result. This implementation is a re-forming of the SHA-1 code at
|
||||||
|
* https://github.com/jinqiangshou/EncryptionLibrary.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2017 CTrabant
|
||||||
|
*
|
||||||
|
* License: MIT, see included LICENSE file for details.
|
||||||
|
*
|
||||||
|
* To use the sha1digest() function either copy it into an existing
|
||||||
|
* project source code file or include this file in a project and put
|
||||||
|
* the declaration (example below) in the sources files where needed.
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* Declaration:
|
||||||
|
extern int sha1digest(uint8_t *digest, char *hexdigest, const uint8_t *data, size_t databytes);
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* sha1digest: https://github.com/CTrabant/teeny-sha1
|
||||||
|
*
|
||||||
|
* Calculate the SHA-1 value for supplied data buffer and generate a
|
||||||
|
* text representation in hexadecimal.
|
||||||
|
*
|
||||||
|
* Based on https://github.com/jinqiangshou/EncryptionLibrary, credit
|
||||||
|
* goes to @jinqiangshou, all new bugs are mine.
|
||||||
|
*
|
||||||
|
* @input:
|
||||||
|
* data -- data to be hashed
|
||||||
|
* databytes -- bytes in data buffer to be hashed
|
||||||
|
*
|
||||||
|
* @output:
|
||||||
|
* digest -- the result, MUST be at least 20 bytes
|
||||||
|
* hexdigest -- the result in hex, MUST be at least 41 bytes
|
||||||
|
*
|
||||||
|
* At least one of the output buffers must be supplied. The other, if not
|
||||||
|
* desired, may be set to NULL.
|
||||||
|
*
|
||||||
|
* @return: 0 on success and non-zero on error.
|
||||||
|
******************************************************************************/
|
||||||
|
int
|
||||||
|
sha1digest(uint8_t *digest, char *hexdigest, const uint8_t *data, size_t databytes)
|
||||||
|
{
|
||||||
|
#define SHA1ROTATELEFT(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
|
||||||
|
|
||||||
|
uint32_t W[80];
|
||||||
|
uint32_t H[] = {0x67452301,
|
||||||
|
0xEFCDAB89,
|
||||||
|
0x98BADCFE,
|
||||||
|
0x10325476,
|
||||||
|
0xC3D2E1F0};
|
||||||
|
uint32_t a;
|
||||||
|
uint32_t b;
|
||||||
|
uint32_t c;
|
||||||
|
uint32_t d;
|
||||||
|
uint32_t e;
|
||||||
|
uint32_t f = 0;
|
||||||
|
uint32_t k = 0;
|
||||||
|
|
||||||
|
uint32_t idx;
|
||||||
|
uint32_t lidx;
|
||||||
|
uint32_t widx;
|
||||||
|
uint32_t didx = 0;
|
||||||
|
|
||||||
|
int32_t wcount;
|
||||||
|
uint32_t temp;
|
||||||
|
uint64_t databits = ((uint64_t)databytes) * 8;
|
||||||
|
uint32_t loopcount = (databytes + 8) / 64 + 1;
|
||||||
|
uint32_t tailbytes = 64 * loopcount - databytes;
|
||||||
|
uint8_t datatail[128] = {0};
|
||||||
|
|
||||||
|
if (!digest && !hexdigest)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (!data)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
/* Pre-processing of data tail (includes padding to fill out 512-bit chunk):
|
||||||
|
Add bit '1' to end of message (big-endian)
|
||||||
|
Add 64-bit message length in bits at very end (big-endian) */
|
||||||
|
datatail[0] = 0x80;
|
||||||
|
datatail[tailbytes - 8] = (uint8_t) (databits >> 56 & 0xFF);
|
||||||
|
datatail[tailbytes - 7] = (uint8_t) (databits >> 48 & 0xFF);
|
||||||
|
datatail[tailbytes - 6] = (uint8_t) (databits >> 40 & 0xFF);
|
||||||
|
datatail[tailbytes - 5] = (uint8_t) (databits >> 32 & 0xFF);
|
||||||
|
datatail[tailbytes - 4] = (uint8_t) (databits >> 24 & 0xFF);
|
||||||
|
datatail[tailbytes - 3] = (uint8_t) (databits >> 16 & 0xFF);
|
||||||
|
datatail[tailbytes - 2] = (uint8_t) (databits >> 8 & 0xFF);
|
||||||
|
datatail[tailbytes - 1] = (uint8_t) (databits >> 0 & 0xFF);
|
||||||
|
|
||||||
|
/* Process each 512-bit chunk */
|
||||||
|
for (lidx = 0; lidx < loopcount; lidx++)
|
||||||
|
{
|
||||||
|
/* Compute all elements in W */
|
||||||
|
memset (W, 0, 80 * sizeof (uint32_t));
|
||||||
|
|
||||||
|
/* Break 512-bit chunk into sixteen 32-bit, big endian words */
|
||||||
|
for (widx = 0; widx <= 15; widx++)
|
||||||
|
{
|
||||||
|
wcount = 24;
|
||||||
|
|
||||||
|
/* Copy byte-per byte from specified buffer */
|
||||||
|
while (didx < databytes && wcount >= 0)
|
||||||
|
{
|
||||||
|
W[widx] += (((uint32_t)data[didx]) << wcount);
|
||||||
|
didx++;
|
||||||
|
wcount -= 8;
|
||||||
|
}
|
||||||
|
/* Fill out W with padding as needed */
|
||||||
|
while (wcount >= 0)
|
||||||
|
{
|
||||||
|
W[widx] += (((uint32_t)datatail[didx - databytes]) << wcount);
|
||||||
|
didx++;
|
||||||
|
wcount -= 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Extend the sixteen 32-bit words into eighty 32-bit words, with potential optimization from:
|
||||||
|
"Improving the Performance of the Secure Hash Algorithm (SHA-1)" by Max Locktyukhin */
|
||||||
|
for (widx = 16; widx <= 31; widx++)
|
||||||
|
{
|
||||||
|
W[widx] = SHA1ROTATELEFT ((W[widx - 3] ^ W[widx - 8] ^ W[widx - 14] ^ W[widx - 16]), 1);
|
||||||
|
}
|
||||||
|
for (widx = 32; widx <= 79; widx++)
|
||||||
|
{
|
||||||
|
W[widx] = SHA1ROTATELEFT ((W[widx - 6] ^ W[widx - 16] ^ W[widx - 28] ^ W[widx - 32]), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Main loop */
|
||||||
|
a = H[0];
|
||||||
|
b = H[1];
|
||||||
|
c = H[2];
|
||||||
|
d = H[3];
|
||||||
|
e = H[4];
|
||||||
|
|
||||||
|
for (idx = 0; idx <= 79; idx++)
|
||||||
|
{
|
||||||
|
if (idx <= 19)
|
||||||
|
{
|
||||||
|
f = (b & c) | ((~b) & d);
|
||||||
|
k = 0x5A827999;
|
||||||
|
}
|
||||||
|
else if (idx >= 20 && idx <= 39)
|
||||||
|
{
|
||||||
|
f = b ^ c ^ d;
|
||||||
|
k = 0x6ED9EBA1;
|
||||||
|
}
|
||||||
|
else if (idx >= 40 && idx <= 59)
|
||||||
|
{
|
||||||
|
f = (b & c) | (b & d) | (c & d);
|
||||||
|
k = 0x8F1BBCDC;
|
||||||
|
}
|
||||||
|
else if (idx >= 60 && idx <= 79)
|
||||||
|
{
|
||||||
|
f = b ^ c ^ d;
|
||||||
|
k = 0xCA62C1D6;
|
||||||
|
}
|
||||||
|
temp = SHA1ROTATELEFT (a, 5) + f + e + k + W[idx];
|
||||||
|
e = d;
|
||||||
|
d = c;
|
||||||
|
c = SHA1ROTATELEFT (b, 30);
|
||||||
|
b = a;
|
||||||
|
a = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
H[0] += a;
|
||||||
|
H[1] += b;
|
||||||
|
H[2] += c;
|
||||||
|
H[3] += d;
|
||||||
|
H[4] += e;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Store binary digest in supplied buffer */
|
||||||
|
if (digest)
|
||||||
|
{
|
||||||
|
for (idx = 0; idx < 5; idx++)
|
||||||
|
{
|
||||||
|
digest[idx * 4 + 0] = (uint8_t) (H[idx] >> 24);
|
||||||
|
digest[idx * 4 + 1] = (uint8_t) (H[idx] >> 16);
|
||||||
|
digest[idx * 4 + 2] = (uint8_t) (H[idx] >> 8);
|
||||||
|
digest[idx * 4 + 3] = (uint8_t) (H[idx]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Store hex version of digest in supplied buffer */
|
||||||
|
if (hexdigest)
|
||||||
|
{
|
||||||
|
snprintf (hexdigest, 41, "%08x%08x%08x%08x%08x",
|
||||||
|
H[0],H[1],H[2],H[3],H[4]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
} /* End of sha1digest() */
|
Loading…
Reference in New Issue
Block a user