Basic test infrastructure

This commit is contained in:
2025-04-20 14:11:52 +02:00
parent a4aafb1ee8
commit e1dca84a44
9 changed files with 1407 additions and 260 deletions
+2 -4
View File
@@ -1,4 +1,2 @@
blog *.out
blog.exe *.exe
example
example.exe
+104 -59
View File
@@ -1,74 +1,119 @@
# Makefile for tinyhttp with cross-platform support CC = gcc
# Supports Windows and Linux with various build configurations RM = rm
MKDIR = mkdir
EXT_WINDOWS = exe
EXT_LINUX = out
# Can be either RELEASE or DEBUG
BUILD = RELEASE
SUFFIX_RELEASE =
SUFFIX_DEBUG = _debug
# ------------------------------------------------------ #
TEST_CFILES = tinyhttp.c tests/picohttpparser.c tests/main.c
TEST_HFILES = tinyhttp.h tests/picohttpparser.h
TEST_FLAGS = -Wall -Wextra
TEST_FLAGS_DEBUG = -ggdb
TEST_FLAGS_RELEASE = -O2 -DNDEBUG
TEST_FLAGS_WINDOWS = -lws2_32
TEST_FLAGS_WINDOWS_DEBUG =
TEST_FLAGS_WINDOWS_RELEASE =
TEST_FLAGS_LINUX =
TEST_FLAGS_LINUX_DEBUG =
TEST_FLAGS_LINUX_RELEASE =
# ------------------------------------------------------ #
DEMO0_CFILES = tinyhttp.c examples/server_api.c
DEMO0_HFILES = tinyhttp.h
DEMO0_FLAGS =
DEMO0_FLAGS_DEBUG = -ggdb
DEMO0_FLAGS_RELEASE = -O2 -DNDEBUG
DEMO0_FLAGS_WINDOWS = -lws2_32
DEMO0_FLAGS_WINDOWS_DEBUG =
DEMO0_FLAGS_WINDOWS_RELEASE =
DEMO0_FLAGS_LINUX =
DEMO0_FLAGS_LINUX_DEBUG =
DEMO0_FLAGS_LINUX_RELEASE =
# ------------------------------------------------------ #
DEMO1_CFILES = tinyhttp.c examples/stream_api_with_select.c
DEMO1_HFILES = tinyhttp.h
DEMO1_FLAGS =
DEMO1_FLAGS =
DEMO1_FLAGS_DEBUG = -ggdb
DEMO1_FLAGS_RELEASE = -O2 -DNDEBUG
DEMO1_FLAGS_WINDOWS = -lws2_32
DEMO1_FLAGS_WINDOWS_DEBUG =
DEMO1_FLAGS_WINDOWS_RELEASE =
DEMO1_FLAGS_LINUX =
DEMO1_FLAGS_LINUX_DEBUG =
DEMO1_FLAGS_LINUX_RELEASE =
# ------------------------------------------------------ #
# ------------------------------------------------------ #
# ------------------------------------------------------ #
# Detect operating system
ifeq ($(OS),Windows_NT) ifeq ($(OS),Windows_NT)
DETECTED_OS := Windows OSTAG = WINDOWS
# On Windows, we need to link with winsock library
LIBS := -lws2_32
RM := del /Q
# Windows executable extension
EXE := .exe
else else
DETECTED_OS := $(shell uname -s) UNAME_S := $(shell uname -s)
LIBS := ifeq ($(UNAME_S),Linux)
RM := rm -f OSTAG = LINUX
EXE := endif
ifeq ($(UNAME_S),Darwin)
OSTAG = OSX
endif
endif endif
# Compiler and flags EXT = ${EXT_$(OSTAG)}
CC := gcc
CFLAGS := -Wall -Wextra -I.
LDFLAGS :=
# Project files TEST_FLAGS += ${TEST_FLAGS_$(BUILD)}
SRC := tinyhttp.c example.c TEST_FLAGS += ${TEST_FLAGS_$(OSTAG)}
HEADERS := tinyhttp.h TEST_FLAGS += ${TEST_FLAGS_$(OSTAG)_$(BUILD)}
TARGET := example$(EXE)
# Default target DEMO0_FLAGS += ${DEMO0_FLAGS_$(BUILD)}
.PHONY: all clean release debug asan coverage DEMO0_FLAGS += ${DEMO0_FLAGS_$(OSTAG)}
DEMO0_FLAGS += ${DEMO0_FLAGS_$(OSTAG)_$(BUILD)}
# Default is release build DEMO1_FLAGS += ${DEMO1_FLAGS_$(BUILD)}
all: release DEMO1_FLAGS += ${DEMO1_FLAGS_$(OSTAG)}
DEMO1_FLAGS += ${DEMO1_FLAGS_$(OSTAG)_$(BUILD)}
# Release build SUFFIX = ${SUFFIX_$(BUILD)}
release: CFLAGS += -O2 -DNDEBUG
release: $(TARGET)
# Debug build # ------------------------------------------------------ #
debug: CFLAGS += -ggdb -DDEBUG # ------------------------------------------------------ #
debug: $(TARGET) # ------------------------------------------------------ #
# Address Sanitizer build .PHONY: all clean
asan: CFLAGS += -fsanitize=address -fno-omit-frame-pointer -O1
asan: LDFLAGS += -fsanitize=address
asan: $(TARGET)
# Coverage build all: out/test$(SUFFIX).$(EXT) out/demo0$(SUFFIX).$(EXT) out/demo1$(SUFFIX).$(EXT)
coverage: CFLAGS += -fprofile-arcs -ftest-coverage -O0
coverage: LDFLAGS += -fprofile-arcs -ftest-coverage
coverage: $(TARGET)
# Compile and link out:
$(TARGET): $(SRC) $(HEADERS) $(MKDIR) out
$(CC) $(CFLAGS) $(SRC) -o $@ $(LDFLAGS) $(LIBS)
out/test$(SUFFIX).$(EXT): out $(TEST_CFILES) $(TEST_HFILES)
$(CC) -o $@ $(TEST_CFILES) $(TEST_FLAGS)
out/demo0$(SUFFIX).$(EXT): out $(DEMO0_CFILES) $(DEMO0_HFILES)
$(CC) -o $@ $(DEMO0_CFILES) $(DEMO0_FLAGS)
out/demo1$(SUFFIX).$(EXT): out $(DEMO1_CFILES) $(DEMO1_HFILES)
$(CC) -o $@ $(DEMO1_CFILES) $(DEMO1_FLAGS)
# Clean build artifacts
clean: clean:
ifeq ($(DETECTED_OS),Windows) $(RM) -fr out
$(RM) $(TARGET) *.gcda *.gcno *.gcov
else
$(RM) $(TARGET) *.o *.gcda *.gcno *.gcov
endif
# Show help
help:
@echo "Available targets:"
@echo " all - Same as 'release'"
@echo " release - Optimized build (-O2, NDEBUG)"
@echo " debug - Debug build with symbols (-ggdb)"
@echo " asan - Address Sanitizer build"
@echo " coverage - Code coverage build"
@echo " clean - Remove build artifacts"
@echo " help - Show this help"
+7 -25
View File
@@ -1,7 +1,7 @@
#include <stdio.h> #include <stdio.h>
#include <signal.h> #include <signal.h>
#include <stdlib.h> #include <stdlib.h>
#include "tinyhttp.h" #include "../tinyhttp.h"
sig_atomic_t should_exit = 0; sig_atomic_t should_exit = 0;
@@ -12,22 +12,6 @@ signal_handler(int sig)
should_exit = 1; should_exit = 1;
} }
static void *memfunc(TinyHTTPMemoryFuncTag tag,
void *ptr, int len, void *data)
{
(void) data;
switch (tag) {
case TINYHTTP_MEM_MALLOC:
return malloc(len);
case TINYHTTP_MEM_FREE:
free(ptr);
return NULL;
}
return NULL;
}
int main(void) int main(void)
{ {
signal(SIGINT, signal_handler); signal(SIGINT, signal_handler);
@@ -39,27 +23,25 @@ int main(void)
.plain_backlog = 32, .plain_backlog = 32,
}; };
TinyHTTPServer *server = tinyhttp_server_init(config, memfunc, NULL); TinyHTTPServer *server = tinyhttp_server_init(config);
if (server == NULL) if (server == NULL)
return -1; return -1;
while (!should_exit) { while (!should_exit) {
int ret;
TinyHTTPRequest *req; TinyHTTPRequest *req;
TinyHTTPResponse res; TinyHTTPResponse res;
int ret = tinyhttp_server_wait(server, &req, &res, 1000); ret = tinyhttp_server_wait(server, &req, &res, 1000);
if (ret < 0) return -1; // Error if (ret < 0) return -1; // Error
if (ret > 0) continue; // Timeout if (ret > 0) continue; // Timeout
tinyhttp_response_status(res, 200); tinyhttp_response_status(res, 200);
tinyhttp_response_body(res, "Hello, world!", -1);
tinyhttp_response_body_setmincap(res, 1<<10); //tinyhttp_response_undo(res);
ptrdiff_t cap; //tinyhttp_response_status(res, 500);
char *buf = tinyhttp_response_body_buf(res, &cap);
int len = buf ? snprintf(buf, cap, "Hello, world!") : 0;
if (len < 0 || len > cap) abort();
tinyhttp_response_body_ack(res, len);
tinyhttp_response_send(res); tinyhttp_response_send(res);
} }
+107 -95
View File
@@ -1,95 +1,107 @@
// A program using the stream interface may look like this: #include <assert.h>
//
// void respond(TinyHTTPStream *stream) #if defined(_WIN32)
// { #include <winsock2.h>
// TinyHTTPRequest *req = tinyhttp_stream_request(stream); #define CLOSESOCKET closesocket
// if (req->method != TINYHTTP_METHOD_GET) #elif defined(__linux__)
// tinyhttp_stream_status(stream, 405); #include <unistd.h>
// else #include <sys/socket.h>
// tinyhttp_stream_status(stream, 200); #include <sys/select.h>
// tinyhttp_stream_send(stream); #include <arpa/inet.h>
// } #define CLOSESOCKET close
// #else
// int main(void) #error "Only Windows and Linux are supported"
// { #endif
// int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
// #include "../tinyhttp.h"
// struct sockaddr_in buf;
// buf.sin_family = AF_INET; #define PORT 8080
// buf.sin_port = htons(port); #define ADDR "127.0.0.1"
// buf.sin_addr.s_addr = htonl(INADDR_ANY);
// bind(listen_fd, (struct sockaddr*) &buf, sizeof(buf)); // TODO: Complete this
//
// listen(listen_fd, 32); void respond(TinyHTTPStream *stream)
// {
// int num_conns = 0; TinyHTTPRequest *req = tinyhttp_stream_request(stream);
// int fds[1000]; if (req->method != TINYHTTP_METHOD_GET)
// TinyHTTPStream streams[1000]; tinyhttp_stream_response_status(stream, 405);
// else
// for (int i = 0; i < 1000; i++) tinyhttp_stream_response_status(stream, 200);
// fds[i] = -1; tinyhttp_stream_response_send(stream);
// }
// for (;;) {
// // TODO: timeouts int main(void)
// {
// fd_set readset; int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
// fd_set writeset;
// FD_ZERO(&readset); struct sockaddr_in buf;
// FD_ZERO(&writeset); buf.sin_family = AF_INET;
// buf.sin_port = htons(PORT);
// FD_SET(&readset); buf.sin_addr.s_addr = inet_addr(ADDR);
// int max_fd = listen_fd; bind(listen_fd, (struct sockaddr*) &buf, sizeof(buf));
// for (int i = 0; i < 1000; i++) {
// if (fds[i] == -1) continue; listen(listen_fd, 32);
// int state = tinyhttp_stream_state(&streams[i]);
// if (state & TINYHTTP_STREAM_RECV) int num_conns = 0;
// FD_SET(fds[i], &readset); int fds[1000];
// if (state & TINYHTTP_STREAM_SEND) TinyHTTPStream streams[1000];
// FD_SET(fds[i], &writeset);
// if (state & (TINYHTTP_STREAM_RECV | TINYHTTP_STREAM_SEND)) for (int i = 0; i < 1000; i++)
// if (max_fd < fds[i]) max_fd = fds[i]; fds[i] = -1;
// }
// for (;;) {
// int num = select(max_fd+1, &readset, &writeset, NULL, NULL); // TODO: timeouts
//
// if (FD_ISSET(liste_fd, &readset)) { fd_set readset;
// // TODO fd_set writeset;
// } FD_ZERO(&readset);
// FD_ZERO(&writeset);
// int ready_queue[1000];
// int ready_head = 0; FD_SET(listen_fd, &readset);
// int ready_count = 0; int max_fd = listen_fd;
// for (int i = 0; i < 1000; i++) { for (int i = 0; i < 1000; i++) {
// // TODO if (fds[i] == -1) continue;
// } int state = tinyhttp_stream_state(&streams[i]);
// if (state & TINYHTTP_STREAM_RECV)
// while (ready_count > 0) { FD_SET(fds[i], &readset);
// if (state & TINYHTTP_STREAM_SEND)
// int idx = ready_queue[ready_head]; FD_SET(fds[i], &writeset);
// TinyHTTPStream *stream = &streams[idx]; if (state & (TINYHTTP_STREAM_RECV | TINYHTTP_STREAM_SEND))
// if (max_fd < fds[i]) max_fd = fds[i];
// TinyHTTPRequest *req = tinyhttp_stream_request(stream); }
// assert(req);
// int num = select(max_fd+1, &readset, &writeset, NULL, NULL);
// respond(stream);
// if (FD_ISSET(listen_fd, &readset)) {
// ready_head = (ready_head + 1) % 1000; // TODO
// ready_count--; }
// if (tinyhttp_stream_request(stream)) {
// ready_queue[(ready_head + ready_count) % 1000] = idx; int ready_queue[1000];
// ready_count++; int ready_head = 0;
// } int ready_count = 0;
// } for (int i = 0; i < 1000; i++) {
// } // TODO
// }
// close(listen_fd);
// return 0; while (ready_count > 0) {
// }
// int idx = ready_queue[ready_head];
// Note that this example does not keep track of timeouts. TinyHTTPStream *stream = &streams[idx];
//
// The recv_buf/recv_ack and send_buf/send_ack interface is very handy as it's TinyHTTPRequest *req = tinyhttp_stream_request(stream);
// compatible both with readyness-based event loops (epoll, poll, select) and assert(req);
// completion-based event loops (iocp, io_uring). Since the stream object does
// not read from the socket directly, you can easily implement HTTPS by providing respond(stream);
// it with TLS-encoded data instead of data directly from the socket.
ready_head = (ready_head + 1) % 1000;
ready_count--;
if (tinyhttp_stream_request(stream)) {
ready_queue[(ready_head + ready_count) % 1000] = idx;
ready_count++;
}
}
}
CLOSESOCKET(listen_fd);
return 0;
}
+290
View File
@@ -0,0 +1,290 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "picohttpparser.h"
#include "../tinyhttp.h"
//////////////////////////////////////////////////////////////////////////////////////
// TYPES, MACROS, PROTOTYPES
//////////////////////////////////////////////////////////////////////////////////////
typedef struct {
int minor;
int status_code;
TinyHTTPString status_text;
int num_headers;
TinyHTTPHeader headers[TINYHTTP_HEADER_LIMIT];
char *body;
int body_len;
} Response;
static void *memfunc(TinyHTTPMemoryFuncTag tag, void *ptr, int len, void *data);
static void send_request(TinyHTTPStream *stream, const char *str);
static void recv_response(TinyHTTPStream *stream, Response *res, char *dst, int cap);
#define TEST(X) {if (!(X)) { printf("Test failed at %s:%d\n", __FILE__, __LINE__); fflush(stdout); __builtin_trap(); }}
//////////////////////////////////////////////////////////////////////////////////////
// TEST CASES
//////////////////////////////////////////////////////////////////////////////////////
static void basic_request(void)
{
TinyHTTPStream stream;
tinyhttp_stream_init(&stream, memfunc, NULL);
// Send request
send_request(&stream,
"GET / HTTP/1.1\r\n"
"Host: 127.0.0.1:8080\r\n"
"User-Agent: curl/7.81.0\r\n"
"Accept: */*\r\n"
"\r\n");
// Build response
tinyhttp_stream_response_status(&stream, 200);
tinyhttp_stream_response_send(&stream);
// Receive response
char buf[1<<12];
Response res;
recv_response(&stream, &res, buf, sizeof(buf));
// TODO
tinyhttp_stream_free(&stream);
}
//////////////////////////////////////////////////////////////////////////////////////
// ENTRY POINT
//////////////////////////////////////////////////////////////////////////////////////
int main(void)
{
basic_request();
return 0;
}
//////////////////////////////////////////////////////////////////////////////////////
// Helper Functions
//////////////////////////////////////////////////////////////////////////////////////
static void*
memfunc(TinyHTTPMemoryFuncTag tag, void *ptr, int len, void *data)
{
(void) data;
switch (tag) {
case TINYHTTP_MEM_MALLOC:
return malloc(len);
case TINYHTTP_MEM_FREE:
free(ptr);
return NULL;
}
return NULL;
}
static int
buffer_into_stream(TinyHTTPStream *stream, const char *src, int len)
{
int state = tinyhttp_stream_state(stream);
ptrdiff_t copied = 0;
while (copied < len && (state & TINYHTTP_STREAM_RECV)) {
char *dst;
ptrdiff_t cap;
ptrdiff_t cpy;
dst = tinyhttp_stream_recv_buf(stream, &cap);
cpy = len - copied;
if (cpy > cap) cpy = cap;
memcpy(dst, src + copied, cpy);
copied += cpy;
tinyhttp_stream_recv_ack(stream, cpy);
state = tinyhttp_stream_state(stream);
}
return copied;
}
static int
stream_into_buffer(TinyHTTPStream *stream, char *dst, int cap)
{
int state = tinyhttp_stream_state(stream);
int copied = 0;
while (copied < cap && (state & TINYHTTP_STREAM_SEND)) {
char *src;
ptrdiff_t len;
ptrdiff_t cpy;
src = tinyhttp_stream_send_buf(stream, &len);
cpy = cap - copied;
if (cpy > len) cpy = len;
memcpy(dst + copied, src, cpy);
copied += cpy;
tinyhttp_stream_send_ack(stream, cpy);
state = tinyhttp_stream_state(stream);
}
return copied;
}
static void
expect_request(TinyHTTPStream *stream, TinyHTTPRequest expreq)
{
int state = tinyhttp_stream_state(stream);
TEST(state & TINYHTTP_STREAM_READY);
TinyHTTPRequest *req = tinyhttp_stream_request(stream);
TEST(req);
TEST(req->method == expreq.method);
TEST(req->minor == expreq.minor);
TEST(tinyhttp_streq(req->path, expreq.path));
TEST(req->num_headers == expreq.num_headers);
for (int i = 0; i < expreq.num_headers; i++) {
TEST(tinyhttp_streq(req->headers[i].name, expreq.headers[i].name));
TEST(tinyhttp_streq(req->headers[i].value, expreq.headers[i].value));
}
TEST(req->body_len == expreq.body_len);
if (expreq.body_len == 0) {
TEST(req->body == NULL);
} else {
TEST(!memcmp(req->body, expreq.body, expreq.body_len));
}
}
static int
parse_request(TinyHTTPString txt, TinyHTTPRequest *req)
{
const char *method;
size_t method_len;
const char *path;
size_t path_len;
int minor;
struct phr_header headers[TINYHTTP_HEADER_LIMIT];
size_t num_headers = TINYHTTP_HEADER_LIMIT;
int ret = phr_parse_request(
txt.ptr, txt.len,
&method, &method_len,
&path, &path_len,
&minor,
headers, &num_headers,
0);
TEST(ret == txt.len);
if (method_len == 3 && !memcmp("GET", method, 3)) {
req->method = TINYHTTP_METHOD_GET;
} else if (method_len == 4 && !memcmp("POST", method, 4)) {
req->method = TINYHTTP_METHOD_POST;
} else {
return -1;
}
req->minor = minor;
req->path = (TinyHTTPString) { path, path_len };
req->num_headers = num_headers;
for (int i = 0; i < (int) num_headers; i++) {
req->headers[i].name = (TinyHTTPString) {
headers[i].name,
headers[i].name_len,
};
req->headers[i].value = (TinyHTTPString) {
headers[i].value,
headers[i].value_len,
};
}
req->body = NULL; // TODO
req->body_len = 0; // TODO
return 0;
}
static void
parse_response(TinyHTTPString txt, Response *res)
{
int minor;
int status_code;
const char *status_text;
size_t status_text_len;
struct phr_header headers[TINYHTTP_HEADER_LIMIT];
size_t num_headers = TINYHTTP_HEADER_LIMIT;
int ret = phr_parse_response(
txt.ptr, txt.len,
&minor,
&status_code, &status_text, &status_text_len,
headers, &num_headers,
0);
TEST(ret == txt.len);
res->minor = minor;
res->status_code = status_code;
res->status_text = (TinyHTTPString) { status_text, status_text_len };
res->num_headers = num_headers;
for (int i = 0; i < (int) num_headers; i++) {
res->headers[i].name = (TinyHTTPString) {
headers[i].name,
headers[i].name_len
};
res->headers[i].value = (TinyHTTPString) {
headers[i].value,
headers[i].value_len
};
}
res->body = NULL; // TODO
res->body_len = 0; // TODO
}
static void
send_request(TinyHTTPStream *stream, const char *str)
{
int received = buffer_into_stream(stream, str, strlen(str));
TEST(received == (int) strlen(str));
TinyHTTPRequest req;
TEST(parse_request((TinyHTTPString) {str, strlen(str)}, &req) == 0);
expect_request(stream, req);
}
static void
recv_response(TinyHTTPStream *stream, Response *res, char *dst, int cap)
{
int len = stream_into_buffer(stream, dst, cap);
int state = tinyhttp_stream_state(stream);
TEST((state & TINYHTTP_STREAM_SEND) == 0);
parse_response((TinyHTTPString) { dst, len }, res);
}
//////////////////////////////////////////////////////////////////////////////////////
// THE END
//////////////////////////////////////////////////////////////////////////////////////
+685
View 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
View 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
+97 -60
View File
@@ -114,8 +114,8 @@ static void
dump_state(int state, const char *file, int line) dump_state(int state, const char *file, int line)
{ {
printf("state = "); printf("state = ");
if (state == TINYHTTP_STREAM_FREE) if (state & TINYHTTP_STREAM_DIED)
printf("FREE "); printf("DIED ");
if (state & TINYHTTP_STREAM_SEND) if (state & TINYHTTP_STREAM_SEND)
printf("SEND "); printf("SEND ");
@@ -145,6 +145,38 @@ dump_state(int state, const char *file, int line)
#define DUMP_STATE(...) #define DUMP_STATE(...)
#endif #endif
////////////////////////////////////////////////////////////////////////////////////
// UTILITIES //
////////////////////////////////////////////////////////////////////////////////////
static char
to_lower(char c)
{
if (c >= 'A' && c <= 'Z')
return c - 'A' + 'a';
return c;
}
int tinyhttp_streq(TinyHTTPString s1, TinyHTTPString s2)
{
if (s1.len != s2.len)
return 0;
for (int i = 0; i < s1.len; i++)
if (s1.ptr[i] != s2.ptr[i])
return 0;
return 1;
}
int tinyhttp_streqcase(TinyHTTPString s1, TinyHTTPString s2)
{
if (s1.len != s2.len)
return 0;
for (int i = 0; i < s1.len; i++)
if (to_lower(s1.ptr[i]) != to_lower(s2.ptr[i]))
return 0;
return 1;
}
//////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////
// HTTP REQUEST PARSER // // HTTP REQUEST PARSER //
//////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////
@@ -189,8 +221,7 @@ parse_request_head(char *src, ptrdiff_t len, TinyHTTPRequest *req)
off = cur; off = cur;
while (cur < len && src[cur] != ' ') // TODO: More robust while (cur < len && src[cur] != ' ') // TODO: More robust
cur++; cur++;
req->path = src + off; req->path = (TinyHTTPString) { src + off, cur - off };
req->path_len = cur - off;
if (len - cur <= 5 if (len - cur <= 5
|| src[cur+0] != ' ' || src[cur+0] != ' '
@@ -234,7 +265,7 @@ parse_request_head(char *src, ptrdiff_t len, TinyHTTPRequest *req)
ptrdiff_t name_off = cur; ptrdiff_t name_off = cur;
while (cur < len && src[cur] != ':') // TODO: robust while (cur < len && src[cur] != ':') // TODO: robust
cur++; cur++;
ptrdiff_t name_len = cur - name_off; TinyHTTPString name = { src + name_off, cur - name_off };
if (cur == len) if (cur == len)
return -400; return -400;
@@ -243,7 +274,7 @@ parse_request_head(char *src, ptrdiff_t len, TinyHTTPRequest *req)
ptrdiff_t value_off = cur; ptrdiff_t value_off = cur;
while (cur < len && src[cur] != '\r') while (cur < len && src[cur] != '\r')
cur++; cur++;
ptrdiff_t value_len = cur - value_off; TinyHTTPString value = { src + value_off, cur - value_off };
if (cur == len) if (cur == len)
return -400; return -400;
@@ -257,44 +288,20 @@ parse_request_head(char *src, ptrdiff_t len, TinyHTTPRequest *req)
// 1) No spaces are allowed after the name // 1) No spaces are allowed after the name
// 2) Spaces should be trimmed from the value // 2) Spaces should be trimmed from the value
if (req->num_headers < TINYHTTP_HEADER_LIMIT) { if (req->num_headers < TINYHTTP_HEADER_LIMIT)
TinyHTTPHeader *header = &req->headers[req->num_headers++]; req->headers[req->num_headers++] = (TinyHTTPHeader) { name, value };
header->name = src + name_off;
header->name_len = name_len;
header->value = src + value_off;
header->value_len = value_len;
}
} }
cur += 2; cur += 2;
return cur; return cur;
} }
static char
to_lower(char c)
{
if (c >= 'A' && c <= 'Z')
return c - 'A' + 'a';
return c;
}
static int static int
eqstrnocase(const char *s1, ptrdiff_t len1, const char *s2, ptrdiff_t len2) find_header(TinyHTTPRequest *req, TinyHTTPString name)
{
if (len1 != len2)
return 0;
for (int i = 0; i < len1; i++)
if (to_lower(s1[i]) != to_lower(s2[i]))
return 0;
return 1;
}
static int
find_header(TinyHTTPRequest *req, const char *name)
{ {
for (int i = 0; i < req->num_headers; i++) { for (int i = 0; i < req->num_headers; i++) {
TinyHTTPHeader *header = &req->headers[i]; TinyHTTPHeader *header = &req->headers[i];
if (eqstrnocase(header->name, header->name_len, name, strlen(name))) if (tinyhttp_streqcase(header->name, name))
return i; return i;
} }
return -1; return -1;
@@ -308,7 +315,7 @@ enum {
}; };
static int static int
parse_transfer_encoding(char *src, ptrdiff_t len, int *items, int max) parse_transfer_encoding(const char *src, ptrdiff_t len, int *items, int max)
{ {
int num = 0; int num = 0;
ptrdiff_t cur = 0; ptrdiff_t cur = 0;
@@ -387,7 +394,7 @@ static int is_digit(char c)
} }
static int static int
parse_content_length(char *src, ptrdiff_t len, unsigned long long *out) parse_content_length(const char *src, ptrdiff_t len, unsigned long long *out)
{ {
ptrdiff_t cur = 0; ptrdiff_t cur = 0;
while (cur < len && (src[cur] == ' ' || src[cur] == '\t')) while (cur < len && (src[cur] == ' ' || src[cur] == '\t'))
@@ -416,13 +423,13 @@ parse_request(char *src, ptrdiff_t len, unsigned long long body_limit, TinyHTTPR
return ret; return ret;
ptrdiff_t head_len = ret; ptrdiff_t head_len = ret;
int transfer_encoding_index = find_header(req, "Transfer-Encoding"); int transfer_encoding_index = find_header(req, TINYHTTP_STRING("Transfer-Encoding"));
if (transfer_encoding_index >= 0) { if (transfer_encoding_index >= 0) {
TinyHTTPHeader *header = &req->headers[transfer_encoding_index]; TinyHTTPHeader *header = &req->headers[transfer_encoding_index];
int items[8]; int items[8];
int num = parse_transfer_encoding(header->value, header->value_len, items, COUNTOF(items)); int num = parse_transfer_encoding(header->value.ptr, header->value.len, items, COUNTOF(items));
if (num < 0) if (num < 0)
return -400; return -400;
@@ -434,13 +441,13 @@ parse_request(char *src, ptrdiff_t len, unsigned long long body_limit, TinyHTTPR
return 1; return 1;
} }
int content_length_index = find_header(req, "Content-Length"); int content_length_index = find_header(req, TINYHTTP_STRING("Content-Length"));
if (content_length_index >= 0) { if (content_length_index >= 0) {
TinyHTTPHeader *header = &req->headers[content_length_index]; TinyHTTPHeader *header = &req->headers[content_length_index];
unsigned long long content_length; unsigned long long content_length;
if (parse_content_length(header->value, header->value_len, &content_length) < 0) if (parse_content_length(header->value.ptr, header->value.len, &content_length) < 0)
return -400; return -400;
if (content_length > body_limit || content_length > MAX_U32) if (content_length > body_limit || content_length > MAX_U32)
return -413; return -413;
@@ -1072,7 +1079,7 @@ process_next_request(TinyHTTPStream *stream)
// Try parsing the request from the buffered bytes. // Try parsing the request from the buffered bytes.
ptrdiff_t len; ptrdiff_t len;
char *src = byte_queue_read_buf(&stream->in, &len); char *src = byte_queue_read_buf(&stream->in, &len); // TODO: What if this returns NULL?
int ret = parse_request(src, len, stream->bodylimit, &stream->req); int ret = parse_request(src, len, stream->bodylimit, &stream->req);
// Request is incomplete // Request is incomplete
@@ -1270,6 +1277,19 @@ append_special_headers(TinyHTTPStream *stream)
stream->content_length_offset = byte_queue_offset(&stream->out); stream->content_length_offset = byte_queue_offset(&stream->out);
} }
void tinyhttp_stream_response_body(TinyHTTPStream *stream, const char *src, int len)
{
if (len < 0) len = strlen(src);
tinyhttp_stream_response_body_setmincap(stream, len);
ptrdiff_t cap;
char *dst = tinyhttp_stream_response_body_buf(stream, &cap);
if (dst)
memcpy(dst, src, len);
tinyhttp_stream_response_body_ack(stream, len);
}
// See tinyhttp.h // See tinyhttp.h
void tinyhttp_stream_response_body_setmincap(TinyHTTPStream *stream, ptrdiff_t mincap) void tinyhttp_stream_response_body_setmincap(TinyHTTPStream *stream, ptrdiff_t mincap)
{ {
@@ -1482,8 +1502,6 @@ struct TinyHTTPServer {
#elif defined(__linux__) #elif defined(__linux__)
int epoll_fd; int epoll_fd;
#endif #endif
TinyHTTPMemoryFunc memfunc;
void *memfuncdata;
SOCKET plain_listen_socket; SOCKET plain_listen_socket;
SOCKET secure_listen_socket; SOCKET secure_listen_socket;
int num_conns; int num_conns;
@@ -1618,6 +1636,22 @@ remove_from_ready_queue(TinyHTTPServer *server, int idx)
server->ready_count--; server->ready_count--;
} }
static void*
default_memfunc(TinyHTTPMemoryFuncTag tag, void *ptr, int len, void *data)
{
(void) data;
switch (tag) {
case TINYHTTP_MEM_MALLOC:
return malloc(len);
case TINYHTTP_MEM_FREE:
free(ptr);
return NULL;
}
return NULL;
}
#if defined(__linux__) #if defined(__linux__)
static void static void
@@ -1699,7 +1733,7 @@ accept_from_listen_socket(TinyHTTPServer *server, SOCKET listen_socket, int secu
server->stream_sockets[idx] = accepted_socket; server->stream_sockets[idx] = accepted_socket;
TinyHTTPStream *stream = &server->stream_state[idx]; TinyHTTPStream *stream = &server->stream_state[idx];
tinyhttp_stream_init(stream, server->memfunc, server->memfuncdata); tinyhttp_stream_init(stream, default_memfunc, NULL);
if (server->num_conns < TINYHTTP_SERVER_CONN_LIMIT * 0.7) if (server->num_conns < TINYHTTP_SERVER_CONN_LIMIT * 0.7)
tinyhttp_stream_setreuse(stream, 1); tinyhttp_stream_setreuse(stream, 1);
@@ -2025,13 +2059,11 @@ intern_accepted_socket(TinyHTTPServer *server, SOCKET accepted_socket, int secur
server->stream_sockets[idx] = accepted_socket; server->stream_sockets[idx] = accepted_socket;
TinyHTTPStream *stream = &server->stream_state[idx]; TinyHTTPStream *stream = &server->stream_state[idx];
tinyhttp_stream_init(stream, server->memfunc, server->memfuncdata); tinyhttp_stream_init(stream, default_memfunc, NULL);
if (server->num_conns < TINYHTTP_SERVER_CONN_LIMIT * 0.7) if (server->num_conns < TINYHTTP_SERVER_CONN_LIMIT * 0.7)
tinyhttp_stream_setreuse(stream, 1); tinyhttp_stream_setreuse(stream, 1);
int state = tinyhttp_stream_state(stream);
DUMP_STATE(tinyhttp_stream_state(stream)); DUMP_STATE(tinyhttp_stream_state(stream));
if (CreateIoCompletionPort((HANDLE) accepted_socket, server->iocp, 0, 0) == NULL) { if (CreateIoCompletionPort((HANDLE) accepted_socket, server->iocp, 0, 0) == NULL) {
@@ -2209,21 +2241,17 @@ process_network_events(TinyHTTPServer *server, int timeout)
} }
#endif #endif
TinyHTTPServer* tinyhttp_server_init(TinyHTTPServerConfig config, TinyHTTPServer* tinyhttp_server_init(TinyHTTPServerConfig config)
TinyHTTPMemoryFunc memfunc, void *memfuncdata)
{ {
#if !TINYHTTP_HTTPS_ENABLE #if !TINYHTTP_HTTPS_ENABLE
if (config.secure) if (config.secure)
return NULL; return NULL;
#endif #endif
TinyHTTPServer *server = memfunc(TINYHTTP_MEM_MALLOC, NULL, sizeof(TinyHTTPServer), memfuncdata); TinyHTTPServer *server = malloc(sizeof(TinyHTTPServer));
if (server == NULL) if (server == NULL)
return NULL; return NULL;
server->memfunc = memfunc;
server->memfuncdata = memfuncdata;
server->num_conns = 0; server->num_conns = 0;
server->ready_head = 0; server->ready_head = 0;
server->ready_count = 0; server->ready_count = 0;
@@ -2249,7 +2277,7 @@ TinyHTTPServer* tinyhttp_server_init(TinyHTTPServerConfig config,
#endif #endif
if (server->plain_listen_socket == INVALID_SOCKET) { if (server->plain_listen_socket == INVALID_SOCKET) {
memfunc(TINYHTTP_MEM_FREE, server, sizeof(TinyHTTPServer), NULL); free(server);
return NULL; return NULL;
} }
@@ -2263,7 +2291,7 @@ TinyHTTPServer* tinyhttp_server_init(TinyHTTPServerConfig config,
CLOSESOCKET(server->plain_listen_socket); CLOSESOCKET(server->plain_listen_socket);
memfunc(TINYHTTP_MEM_FREE, server, sizeof(TinyHTTPServer), NULL); free(server);
return NULL; return NULL;
} }
} }
@@ -2276,7 +2304,7 @@ TinyHTTPServer* tinyhttp_server_init(TinyHTTPServerConfig config,
if (server->secure_listen_socket != INVALID_SOCKET) if (server->secure_listen_socket != INVALID_SOCKET)
CLOSESOCKET(server->secure_listen_socket); CLOSESOCKET(server->secure_listen_socket);
memfunc(TINYHTTP_MEM_FREE, server, sizeof(TinyHTTPServer), NULL); free(server);
return NULL; return NULL;
} }
@@ -2298,10 +2326,7 @@ void tinyhttp_server_free(TinyHTTPServer *server)
CLOSESOCKET(server->secure_listen_socket); CLOSESOCKET(server->secure_listen_socket);
server_free_platform(server); server_free_platform(server);
free(server);
TinyHTTPMemoryFunc memfunc = server->memfunc;
void *memfuncdata = server->memfuncdata;
memfunc(TINYHTTP_MEM_FREE, server, sizeof(TinyHTTPServer), memfuncdata);
} }
int tinyhttp_server_wait(TinyHTTPServer *server, TinyHTTPRequest **req, int tinyhttp_server_wait(TinyHTTPServer *server, TinyHTTPRequest **req,
@@ -2408,6 +2433,18 @@ void tinyhttp_response_body_ack(TinyHTTPResponse res, ptrdiff_t num)
tinyhttp_stream_response_body_ack(stream, num); tinyhttp_stream_response_body_ack(stream, num);
} }
void tinyhttp_response_body(TinyHTTPResponse res, char *src, int len)
{
if (len < 0) len = strlen(src);
tinyhttp_response_body_setmincap(res, len);
ptrdiff_t cap;
char *dst = tinyhttp_response_body_buf(res, &cap);
if (dst)
memcpy(dst, src, len);
tinyhttp_response_body_ack(res, len);
}
void tinyhttp_response_send(TinyHTTPResponse res) void tinyhttp_response_send(TinyHTTPResponse res)
{ {
TinyHTTPStream *stream = response_to_stream(res); TinyHTTPStream *stream = response_to_stream(res);
+18 -10
View File
@@ -43,23 +43,27 @@
typedef struct TinyHTTPServer TinyHTTPServer; typedef struct TinyHTTPServer TinyHTTPServer;
typedef struct TinyHTTPRouter TinyHTTPRouter; typedef struct TinyHTTPRouter TinyHTTPRouter;
typedef struct {
const char *ptr;
ptrdiff_t len;
} TinyHTTPString;
#define TINYHTTP_STRING(X) ((TinyHTTPString) {(X), sizeof(X)-1})
typedef enum { typedef enum {
TINYHTTP_METHOD_GET, TINYHTTP_METHOD_GET,
TINYHTTP_METHOD_POST, TINYHTTP_METHOD_POST,
} TinyHTTPMethod; } TinyHTTPMethod;
typedef struct { typedef struct {
char *name; TinyHTTPString name;
ptrdiff_t name_len; TinyHTTPString value;
char *value;
ptrdiff_t value_len;
} TinyHTTPHeader; } TinyHTTPHeader;
typedef struct { typedef struct {
TinyHTTPMethod method; TinyHTTPMethod method;
int minor; int minor;
char* path; TinyHTTPString path;
ptrdiff_t path_len;
int num_headers; int num_headers;
TinyHTTPHeader headers[TINYHTTP_HEADER_LIMIT]; TinyHTTPHeader headers[TINYHTTP_HEADER_LIMIT];
char* body; char* body;
@@ -167,6 +171,9 @@ typedef struct {
TinyHTTPRequest req; TinyHTTPRequest req;
} TinyHTTPStream; } TinyHTTPStream;
int tinyhttp_streq(TinyHTTPString s1, TinyHTTPString s2);
int tinyhttp_streqcase(TinyHTTPString s1, TinyHTTPString s2);
// Initializes an HTTP stream // Initializes an HTTP stream
// //
// TODO: Comment on memfunc // TODO: Comment on memfunc
@@ -262,8 +269,7 @@ void tinyhttp_stream_response_header(TinyHTTPStream *stream, const char *fmt, ..
void tinyhttp_stream_response_header_fmt(TinyHTTPStream *stream, const char *fmt, va_list args); void tinyhttp_stream_response_header_fmt(TinyHTTPStream *stream, const char *fmt, va_list args);
// TODO: Comment // TODO: Comment
// TODO: Implement void tinyhttp_stream_response_body(TinyHTTPStream *stream, const char *src, int len);
void tinyhttp_stream_response_body(TinyHTTPStream *stream, char *src, int len);
// TODO: Comment // TODO: Comment
void tinyhttp_stream_response_body_setmincap(TinyHTTPStream *stream, ptrdiff_t mincap); void tinyhttp_stream_response_body_setmincap(TinyHTTPStream *stream, ptrdiff_t mincap);
@@ -281,8 +287,7 @@ void tinyhttp_stream_response_send(TinyHTTPStream *stream);
void tinyhttp_stream_response_undo(TinyHTTPStream *stream); void tinyhttp_stream_response_undo(TinyHTTPStream *stream);
// TODO: Comment // TODO: Comment
TinyHTTPServer *tinyhttp_server_init(TinyHTTPServerConfig config, TinyHTTPServer *tinyhttp_server_init(TinyHTTPServerConfig config);
TinyHTTPMemoryFunc memfunc, void *memfuncdata);
// TODO: Comment // TODO: Comment
void tinyhttp_server_free(TinyHTTPServer *server); void tinyhttp_server_free(TinyHTTPServer *server);
@@ -312,6 +317,9 @@ char *tinyhttp_response_body_buf(TinyHTTPResponse res, ptrdiff_t *cap);
// TODO: Comment // TODO: Comment
void tinyhttp_response_body_ack(TinyHTTPResponse res, ptrdiff_t num); void tinyhttp_response_body_ack(TinyHTTPResponse res, ptrdiff_t num);
// TODO: Comment
void tinyhttp_response_body(TinyHTTPResponse res, char *src, int len);
// TODO: Comment // TODO: Comment
void tinyhttp_response_send(TinyHTTPResponse res); void tinyhttp_response_send(TinyHTTPResponse res);