First version of the refactor
This commit is contained in:
-13
@@ -1,13 +0,0 @@
|
|||||||
coverage_report
|
|
||||||
|
|
||||||
# Coverage stuff
|
|
||||||
*.info
|
|
||||||
*.gcda
|
|
||||||
*.gcno
|
|
||||||
|
|
||||||
*.out
|
|
||||||
*.exe
|
|
||||||
*.o
|
|
||||||
*.a
|
|
||||||
*.so
|
|
||||||
*.pem
|
|
||||||
@@ -4,4 +4,4 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
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.
|
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.
|
||||||
|
|||||||
@@ -1,76 +1,12 @@
|
|||||||
|
.PHONY: all clean example
|
||||||
|
|
||||||
CC = gcc
|
all: chttp.c chttp.h example
|
||||||
CFLAGS = -I. -Wall -Wextra -O0 -g3
|
|
||||||
LFLAGS =
|
|
||||||
AR = ar
|
|
||||||
|
|
||||||
CFILES = $(shell find src -name "*.c")
|
chttp.c chttp.h: $(wildcard src/*.c src/*.h) misc/amalg.py Makefile
|
||||||
HFILES = $(shell find src -name "*.h")
|
|
||||||
OFILES = $(patsubst %.c,%.o,$(CFILES))
|
|
||||||
|
|
||||||
# Library names
|
|
||||||
LIBNAME = chttp
|
|
||||||
STATIC_LIB = lib$(LIBNAME).a
|
|
||||||
|
|
||||||
# Detect OS and set executable extension
|
|
||||||
ifeq ($(OS),Windows_NT)
|
|
||||||
EXT = .exe
|
|
||||||
LFLAGS = -lws2_32
|
|
||||||
else
|
|
||||||
EXT = .out
|
|
||||||
LFLAGS += -lssl -lcrypto
|
|
||||||
endif
|
|
||||||
|
|
||||||
HTTPS ?= 0
|
|
||||||
ifneq ($(HTTPS),0)
|
|
||||||
CFLAGS += -DHTTPS_ENABLED
|
|
||||||
LFLAGS += -lssl -lcrypto
|
|
||||||
endif
|
|
||||||
|
|
||||||
# Installation directories
|
|
||||||
PREFIX ?= /usr/local
|
|
||||||
LIBDIR = $(PREFIX)/lib
|
|
||||||
INCDIR = $(PREFIX)/include
|
|
||||||
|
|
||||||
EXAMPLES_CLIENT_SRC := $(shell ls examples/client/*.c 2>/dev/null)
|
|
||||||
EXAMPLES_SERVER_SRC := $(shell ls examples/server/*.c 2>/dev/null)
|
|
||||||
EXAMPLES_ENGINE_SRC := $(shell ls examples/engine/*.c 2>/dev/null)
|
|
||||||
|
|
||||||
EXAMPLES_CLIENT := $(patsubst %.c,%$(EXT),$(EXAMPLES_CLIENT_SRC))
|
|
||||||
EXAMPLES_SERVER := $(patsubst %.c,%$(EXT),$(EXAMPLES_SERVER_SRC))
|
|
||||||
EXAMPLES_ENGINE := $(patsubst %.c,%$(EXT),$(EXAMPLES_ENGINE_SRC))
|
|
||||||
|
|
||||||
all: chttp.c chttp.h examples lib
|
|
||||||
|
|
||||||
lib: $(STATIC_LIB)
|
|
||||||
|
|
||||||
chttp.c chttp.h: $(HFILES) $(CFILES)
|
|
||||||
python misc/amalg.py
|
python misc/amalg.py
|
||||||
|
|
||||||
# Object files from source files
|
example: main.c chttp.c chttp.h
|
||||||
%.o: %.c $(HFILES)
|
gcc main.c chttp.c -o example.exe
|
||||||
$(CC) $(CFLAGS) -c $< -o $@
|
|
||||||
|
|
||||||
# Static library
|
|
||||||
$(STATIC_LIB): $(OFILES)
|
|
||||||
$(AR) rcs $@ $^
|
|
||||||
|
|
||||||
examples: $(EXAMPLES_CLIENT) $(EXAMPLES_SERVER) $(EXAMPLES_ENGINE)
|
|
||||||
|
|
||||||
examples/client/%$(EXT): examples/client/%.c chttp.c chttp.h
|
|
||||||
$(CC) $(CFLAGS) $< chttp.c -o $@ $(LFLAGS)
|
|
||||||
|
|
||||||
examples/server/%$(EXT): examples/server/%.c chttp.c chttp.h
|
|
||||||
$(CC) $(CFLAGS) $< chttp.c -o $@ $(LFLAGS)
|
|
||||||
|
|
||||||
examples/engine/%$(EXT): examples/engine/%.c chttp.c chttp.h
|
|
||||||
$(CC) $(CFLAGS) $< chttp.c -o $@ $(LFLAGS)
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f client_example server_example
|
rm chttp.c chttp.h
|
||||||
rm -f examples/client/*$(EXT) examples/server/*$(EXT) examples/engine/*$(EXT)
|
|
||||||
rm -f $(OFILES)
|
|
||||||
rm -f $(STATIC_LIB)
|
|
||||||
rm -f chttp.c chttp.h
|
|
||||||
|
|
||||||
.PHONY: all lib examples install install-lib install-headers uninstall clean
|
|
||||||
|
|||||||
@@ -1,114 +1,12 @@
|
|||||||
# cHTTP
|
# cHTTP
|
||||||
cHTTP is an HTTP **client and server** library for C with minimal dependencies and **distributed as a single chttp.c** file.
|
This is an HTTP library for C, featuring an HTTP(S) server, HTTP(S) client, and much more!
|
||||||
|
|
||||||
Here is a list of features:
|
## Contributing
|
||||||
|
|
||||||
* HTTP 1.1 client and server
|
Contributions are welcome! The following are some notes on how to work with the codebase. Don't worry if you get something wrong. I will remind you.
|
||||||
* Non-blocking
|
|
||||||
* Cross-Platform
|
|
||||||
* Minimal dependencies
|
|
||||||
* HTTPS support
|
|
||||||
* Virtual Hosts
|
|
||||||
* Single-threaded
|
|
||||||
* Ergonomic API
|
|
||||||
|
|
||||||
Note: This library is beta-quality software as some essential features are still being implemented.
|
The source code in the `src/` directory is intended to be be amalgamated into a single file before compilation. The amalgamation is not only intended as a distribution method, but also as easy-access documentation, and therefore need to be readable. For this reasons:
|
||||||
|
1. You never need need to include other cHTTP source files
|
||||||
## Getting Started
|
2. All inclusions of third-party headers are to be placed inside `src/includes.h`
|
||||||
|
3. All files must start with a single empty line, unless they start with an overview comment of the file, in which case they must have no empty lines at the beginning of the file.
|
||||||
The library is distributed as a single amalgamated `chttp.c` file or as a static library, so you can:
|
4. All files must end with a single empty line.
|
||||||
|
|
||||||
1) Download `chttp.c` and `chttp.h` from the repository (no need to clone the project) and include them in your soource tree
|
|
||||||
|
|
||||||
2) or clone the project and build the static library by running:
|
|
||||||
|
|
||||||
```
|
|
||||||
make libchttp.a
|
|
||||||
```
|
|
||||||
|
|
||||||
If you used the amalgamated files, these are the flags required to build a project:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Linux
|
|
||||||
gcc your_app.c chttp.c
|
|
||||||
|
|
||||||
# Windows
|
|
||||||
gcc your_app.c chttp.c -lws2_32
|
|
||||||
```
|
|
||||||
|
|
||||||
If you are using the static library, instead of adding `chttp.c`, you will need to add the `-lchttp` flag.
|
|
||||||
|
|
||||||
By default, the library is built without HTTPS. To enable it, add the flags `-DHTTPS_ENABLED -lssl -lcrypto`.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Linux + HTTPS
|
|
||||||
gcc your_app.c chttp.c -DHTTPS_ENABLED -lssl -lcrypto
|
|
||||||
|
|
||||||
# Windows + HTTPS
|
|
||||||
gcc your_app.c chttp.c -lws2_32 -DHTTPS_ENABLED -lssl -lcrypto
|
|
||||||
```
|
|
||||||
|
|
||||||
## Example
|
|
||||||
|
|
||||||
To give you a feel of the library, here are some examples of programs using cHTTP. To learn more, you can look at the files in `examples/` (they are intended to be skimmed in order).
|
|
||||||
|
|
||||||
Here is a client performing a GET request:
|
|
||||||
```c
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <chttp.h>
|
|
||||||
|
|
||||||
int main(void)
|
|
||||||
{
|
|
||||||
http_global_init();
|
|
||||||
|
|
||||||
HTTP_String url = HTTP_STR("http://example.com/index.html");
|
|
||||||
|
|
||||||
HTTP_String headers[] = {
|
|
||||||
HTTP_STR("User-Agent: cHTTP"),
|
|
||||||
};
|
|
||||||
|
|
||||||
HTTP_Response *res = http_get(url, headers, 1);
|
|
||||||
|
|
||||||
fwrite(res->body.ptr, 1, res->body.len, stdout);
|
|
||||||
|
|
||||||
http_response_free(res);
|
|
||||||
http_global_free();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
And this is a server:
|
|
||||||
```c
|
|
||||||
#include <chttp.h>
|
|
||||||
|
|
||||||
int main(void)
|
|
||||||
{
|
|
||||||
http_global_init();
|
|
||||||
HTTP_Server *server = http_server_init(HTTP_STR("127.0.0.1"), 8080);
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
|
|
||||||
HTTP_Request *req;
|
|
||||||
HTTP_ResponseBuilder builder;
|
|
||||||
http_server_wait(server, &req, &builder);
|
|
||||||
|
|
||||||
http_response_builder_status(builder, 200);
|
|
||||||
http_response_builder_header(builder, "Content-Type: text/plain");
|
|
||||||
http_response_builder_body(builder, HTTP_STR("Hello, world!"));
|
|
||||||
http_response_builder_done(builder);
|
|
||||||
}
|
|
||||||
|
|
||||||
http_server_free(server);
|
|
||||||
http_global_free();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Platform Support
|
|
||||||
cHTTP officially supports Linux and Windows.
|
|
||||||
|
|
||||||
## HTTPS support
|
|
||||||
Currently, HTTPS is implemented using OpenSSL. If you are on Windows, you'll need to install it manually or disabling it by not defining `HTTPS_ENABLED`.
|
|
||||||
|
|
||||||
## Scalability
|
|
||||||
cHTTP is designed to reach moderate scale to allow a compact and easy to work with implementation. The non-blocking I/O is based on `poll()` which I would say works up to about 500 concurrent connections. If you have more than that, you should consider APIs like epoll, io_uring, and I/O completion ports. If you do go that route, you can still reuse the cHTTP I/O independent core (see HTTP_Engine) to handle the HTTP protocol for you, both for client and server.
|
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
#include <stdio.h>
|
|
||||||
#include <chttp.h>
|
|
||||||
|
|
||||||
// This is an example of how to use cHTTP to perform
|
|
||||||
// a basic GET request.
|
|
||||||
|
|
||||||
int main(void)
|
|
||||||
{
|
|
||||||
http_global_init();
|
|
||||||
|
|
||||||
// List any headers the request should hold
|
|
||||||
HTTP_String headers[] = {
|
|
||||||
HTTP_STR("User-Agent: cHTTP"),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Perform the request. This will block the thread
|
|
||||||
// until an error occurs or the request completes.
|
|
||||||
HTTP_Response *res = http_get(
|
|
||||||
HTTP_STR("http://example.com/index.html"),
|
|
||||||
headers, HTTP_COUNT(headers)
|
|
||||||
);
|
|
||||||
|
|
||||||
// The http_get function returns NULL if the request
|
|
||||||
// couldn't be performed.
|
|
||||||
if (res == NULL) return -1;
|
|
||||||
|
|
||||||
// If the request succeded (note that responses with
|
|
||||||
// status 4xx and 5xx are not considered as errors in
|
|
||||||
// this context) the returned value holds the parsed
|
|
||||||
// version of the response and the output handle is set.
|
|
||||||
|
|
||||||
printf("status code: %d\n", res->status);
|
|
||||||
|
|
||||||
for (int i = 0; i < res->num_headers; i++) {
|
|
||||||
HTTP_Header header = res->headers[i];
|
|
||||||
printf(
|
|
||||||
"header %d: [%.*s] [%.*s]\n",
|
|
||||||
i,
|
|
||||||
HTTP_UNPACK(header.name),
|
|
||||||
HTTP_UNPACK(header.value)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("body: %.*s\n", HTTP_UNPACK(res->body));
|
|
||||||
|
|
||||||
// When we are done reading from the response object
|
|
||||||
// we must free the request's resources.
|
|
||||||
http_response_free(res);
|
|
||||||
|
|
||||||
// All done. Deinitialize the library.
|
|
||||||
http_global_free();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
#include <stddef.h>
|
|
||||||
#include <chttp.h>
|
|
||||||
|
|
||||||
int main(void)
|
|
||||||
{
|
|
||||||
http_global_init();
|
|
||||||
HTTP_Client *client = http_client_init();
|
|
||||||
if (client == NULL)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
HTTP_Response *responses[2];
|
|
||||||
|
|
||||||
HTTP_String urls[] = {
|
|
||||||
HTTP_STR("http://coz.is"),
|
|
||||||
HTTP_STR("http://coz.is"),
|
|
||||||
};
|
|
||||||
|
|
||||||
bool trace = false;
|
|
||||||
|
|
||||||
HTTP_RequestBuilder builder;
|
|
||||||
if (http_client_get_builder(client, &builder) < 0) return -1;
|
|
||||||
http_request_builder_trace(builder, trace);
|
|
||||||
http_request_builder_user_data(builder, &responses[0]);
|
|
||||||
http_request_builder_line(builder, HTTP_METHOD_GET, urls[0]);
|
|
||||||
http_request_builder_submit(builder);
|
|
||||||
|
|
||||||
if (http_client_get_builder(client, &builder) < 0) return -1;
|
|
||||||
http_request_builder_trace(builder, trace);
|
|
||||||
http_request_builder_user_data(builder, &responses[1]);
|
|
||||||
http_request_builder_line(builder, HTTP_METHOD_GET, urls[1]);
|
|
||||||
http_request_builder_submit(builder);
|
|
||||||
|
|
||||||
for (int i = 0; i < 2; i++) {
|
|
||||||
void **dst;
|
|
||||||
HTTP_Response *output;
|
|
||||||
if (http_client_wait(client, &output, (void*) &dst) < 0)
|
|
||||||
return -1;
|
|
||||||
*dst = output;
|
|
||||||
}
|
|
||||||
|
|
||||||
HTTP_Response *responseA = responses[0];
|
|
||||||
HTTP_Response *responseB = responses[1];
|
|
||||||
|
|
||||||
// ... process responses ...
|
|
||||||
|
|
||||||
http_response_free(responseA);
|
|
||||||
http_response_free(responseB);
|
|
||||||
http_client_free(client);
|
|
||||||
http_global_free();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <chttp.h>
|
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
|
||||||
{
|
|
||||||
if (argc < 2) {
|
|
||||||
printf("Usage: %s <url1> [url2 ...]\n", argv[0]);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
http_global_init();
|
|
||||||
HTTP_Client *client = http_client_init();
|
|
||||||
|
|
||||||
for (int i = 1; i < argc; i++) {
|
|
||||||
HTTP_RequestBuilder builder;
|
|
||||||
if (http_client_get_builder(client, &builder) < 0) {
|
|
||||||
printf("request creation error\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
http_request_builder_line(builder, HTTP_METHOD_GET, (HTTP_String) { argv[i], strlen(argv[i]) });
|
|
||||||
http_request_builder_submit(builder);
|
|
||||||
printf("request submitted\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 1; i < argc; i++) {
|
|
||||||
|
|
||||||
HTTP_Response *res;
|
|
||||||
if (http_client_wait(client, &res, NULL) < 0) {
|
|
||||||
printf("request wait error\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("Status: %d\n", res->status);
|
|
||||||
printf("Body: %.*s\n", HTTP_UNPACK(res->body));
|
|
||||||
|
|
||||||
http_response_free(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
http_client_free(client);
|
|
||||||
http_global_free();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,194 +0,0 @@
|
|||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <chttp.h>
|
|
||||||
|
|
||||||
#define BLK "\e[0;30m"
|
|
||||||
#define RED "\e[0;31m"
|
|
||||||
#define GRN "\e[0;32m"
|
|
||||||
#define YEL "\e[0;33m"
|
|
||||||
#define BLU "\e[0;34m"
|
|
||||||
#define MAG "\e[0;35m"
|
|
||||||
#define CYN "\e[0;36m"
|
|
||||||
#define WHT "\e[0;37m"
|
|
||||||
#define RST "\e[0m"
|
|
||||||
|
|
||||||
HTTP_String *already_crawled = NULL;
|
|
||||||
int num_already_crawled = 0;
|
|
||||||
int cap_already_crawled = 0;
|
|
||||||
|
|
||||||
HTTP_String copystr(HTTP_String str)
|
|
||||||
{
|
|
||||||
char *copy = malloc(str.len);
|
|
||||||
if (copy == NULL)
|
|
||||||
abort();
|
|
||||||
memcpy(copy, str.ptr, str.len);
|
|
||||||
return (HTTP_String) { copy, str.len };
|
|
||||||
}
|
|
||||||
|
|
||||||
int normalize_url(HTTP_String url, char *dst, int max)
|
|
||||||
{
|
|
||||||
HTTP_URL parsed_url;
|
|
||||||
if (http_parse_url(url.ptr, url.len, &parsed_url) <= 0)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
int len = snprintf(dst, max, "http://%.*s%.*s",
|
|
||||||
(int) parsed_url.authority.host.text.len,
|
|
||||||
parsed_url.authority.host.text.ptr,
|
|
||||||
(int) parsed_url.path.len,
|
|
||||||
parsed_url.path.ptr
|
|
||||||
);
|
|
||||||
if (len < 0 || len >= max)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
|
|
||||||
void add_to_crawled_list(HTTP_String url)
|
|
||||||
{
|
|
||||||
if (num_already_crawled == cap_already_crawled) {
|
|
||||||
|
|
||||||
int new_cap = 2 * cap_already_crawled;
|
|
||||||
if (new_cap == 0)
|
|
||||||
new_cap = 8;
|
|
||||||
|
|
||||||
HTTP_String *new_ptr = malloc(new_cap * sizeof(HTTP_String));
|
|
||||||
if (new_ptr == NULL)
|
|
||||||
abort();
|
|
||||||
|
|
||||||
if (cap_already_crawled > 0) {
|
|
||||||
for (int i = 0; i < num_already_crawled; i++)
|
|
||||||
new_ptr[i] = already_crawled[i];
|
|
||||||
free(already_crawled);
|
|
||||||
}
|
|
||||||
|
|
||||||
already_crawled = new_ptr;
|
|
||||||
cap_already_crawled = new_cap;
|
|
||||||
}
|
|
||||||
|
|
||||||
char buf[1<<10];
|
|
||||||
int len = normalize_url(url, buf, (int) sizeof(buf));
|
|
||||||
if (len < 0) return;
|
|
||||||
|
|
||||||
already_crawled[num_already_crawled++] = copystr((HTTP_String) { buf, len });
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_already_crawled(HTTP_String url)
|
|
||||||
{
|
|
||||||
char buf[1<<10];
|
|
||||||
int len = normalize_url(url, buf, (int) sizeof(buf));
|
|
||||||
if (len < 0 || len >= (int) sizeof(buf))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
for (int i = 0; i < num_already_crawled; i++)
|
|
||||||
if (http_streq(already_crawled[i], (HTTP_String) { buf, len }))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
HTTP_String next_link(HTTP_String src, int *pcur)
|
|
||||||
{
|
|
||||||
int cur = *pcur;
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
|
|
||||||
while (cur < src.len && src.ptr[cur] != 'h')
|
|
||||||
cur++;
|
|
||||||
|
|
||||||
if (cur == src.len)
|
|
||||||
break;
|
|
||||||
|
|
||||||
int off = cur;
|
|
||||||
|
|
||||||
HTTP_URL parsed_url;
|
|
||||||
int len = http_parse_url(src.ptr + cur, src.len - cur, &parsed_url);
|
|
||||||
if (len <= 0) {
|
|
||||||
cur++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
cur += len;
|
|
||||||
|
|
||||||
if (!http_streq(parsed_url.scheme, HTTP_STR("http")) &&
|
|
||||||
!http_streq(parsed_url.scheme, HTTP_STR("https")))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
*pcur = cur;
|
|
||||||
return (HTTP_String) { src.ptr + off, len };
|
|
||||||
}
|
|
||||||
|
|
||||||
*pcur = cur;
|
|
||||||
return (HTTP_String) { NULL, 0 };
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
|
||||||
{
|
|
||||||
http_global_init();
|
|
||||||
|
|
||||||
if (argc < 2) {
|
|
||||||
printf("Usage: %s <URL>\n", argv[0]);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
HTTP_String start_url = { argv[1], strlen(argv[1]) };
|
|
||||||
|
|
||||||
HTTP_Client *client = http_client_init();
|
|
||||||
if (client == NULL) {
|
|
||||||
printf("Couldn't initialize HTTP client object\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
HTTP_RequestBuilder builder;
|
|
||||||
int ret = http_client_get_builder(client, &builder);
|
|
||||||
if (ret < 0) {
|
|
||||||
printf("Couldn't start request\n");
|
|
||||||
http_client_free(client);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
http_request_builder_line(builder, HTTP_METHOD_GET, start_url);
|
|
||||||
http_request_builder_header(builder, HTTP_STR("User-Agent: Simple crawler"));
|
|
||||||
http_request_builder_submit(builder);
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
|
|
||||||
HTTP_Response *res;
|
|
||||||
ret = http_client_wait(client, &res, NULL);
|
|
||||||
if (ret < 0) {
|
|
||||||
// TODO
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (res == NULL)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
HTTP_String body = res->body;
|
|
||||||
|
|
||||||
int cursor = 0;
|
|
||||||
for (;;) {
|
|
||||||
|
|
||||||
HTTP_String url = next_link(body, &cursor);
|
|
||||||
if (url.len == 0) break;
|
|
||||||
|
|
||||||
if (is_already_crawled(url)) {
|
|
||||||
printf("Ignoring " RED "%.*s" RST "\n", HTTP_UNPACK(url));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("Fetching " GRN "%.*s" RST "\n", HTTP_UNPACK(url));
|
|
||||||
add_to_crawled_list(url);
|
|
||||||
|
|
||||||
HTTP_RequestBuilder builder;
|
|
||||||
ret = http_client_get_builder(client, &builder);
|
|
||||||
if (ret < 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
http_request_builder_line(builder, HTTP_METHOD_GET, url);
|
|
||||||
http_request_builder_header(builder, HTTP_STR("User-Agent: Simple crawler"));
|
|
||||||
http_request_builder_submit(builder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
http_client_free(client);
|
|
||||||
http_global_free();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,244 +0,0 @@
|
|||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include <winsock2.h>
|
|
||||||
#include <ws2ipdef.h>
|
|
||||||
#include <ws2tcpip.h>
|
|
||||||
#define CLOSE_SOCKET closesocket
|
|
||||||
#else
|
|
||||||
#include <errno.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <netdb.h>
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <arpa/inet.h>
|
|
||||||
#define SOCKET int
|
|
||||||
#define INVALID_SOCKET -1
|
|
||||||
#define CLOSE_SOCKET close
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <chttp.h>
|
|
||||||
|
|
||||||
// This example showcases how to use the engine interface
|
|
||||||
// to build a blocking HTTP server that works on Windows
|
|
||||||
// and Linux.
|
|
||||||
|
|
||||||
// Callback used by the engine to manage dynamic memory
|
|
||||||
static void *memfunc(HTTP_MemoryFuncTag tag,
|
|
||||||
void *ptr, int len, void *data)
|
|
||||||
{
|
|
||||||
(void) data;
|
|
||||||
switch (tag) {
|
|
||||||
|
|
||||||
case HTTP_MEMFUNC_MALLOC:
|
|
||||||
return malloc(len);
|
|
||||||
|
|
||||||
case HTTP_MEMFUNC_FREE:
|
|
||||||
free(ptr);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void produce_response(HTTP_Engine *eng)
|
|
||||||
{
|
|
||||||
// All considerations in simple_server.c for how responses
|
|
||||||
// are built also applies here, where http_engine_XXX functions
|
|
||||||
// are used instead of http_response_XXX functions.
|
|
||||||
|
|
||||||
// Set the response status
|
|
||||||
http_engine_status(eng, 200);
|
|
||||||
|
|
||||||
// Set zero or more headers
|
|
||||||
http_engine_header(eng, HTTP_STR("Server: tinyhttp"));
|
|
||||||
|
|
||||||
// Set some bytes in the body
|
|
||||||
http_engine_body(eng, HTTP_STR("Hello, world!"));
|
|
||||||
|
|
||||||
// This is one difference from the http_response_XXX API.
|
|
||||||
// It's possible to write response content directly into
|
|
||||||
// the engine's output buffer. This avoids copies in some
|
|
||||||
// circumstances.
|
|
||||||
|
|
||||||
char msg[] = " What's up??";
|
|
||||||
|
|
||||||
// First, set how many bytes the output buffer will need
|
|
||||||
// to hold at least:
|
|
||||||
http_engine_bodycap(eng, sizeof(msg)-1);
|
|
||||||
|
|
||||||
// Now get the location for the write. The returned pointer
|
|
||||||
// points to a region of size "cap", which equal or greater
|
|
||||||
// to the previously set minimum capacity.
|
|
||||||
int cap;
|
|
||||||
char *dst = http_engine_bodybuf(eng, &cap);
|
|
||||||
|
|
||||||
// If an error occurred internally, the returned pointer will
|
|
||||||
// be NULL and the capacity 0. In this case, you can just skip
|
|
||||||
// this write. The engine will automatically be closed when the
|
|
||||||
// "http_engine_done" function is called.
|
|
||||||
if (dst) {
|
|
||||||
memcpy(dst, msg, sizeof(msg)-1);
|
|
||||||
|
|
||||||
// Tell the engine how many bytes the application wrote to the
|
|
||||||
// provided buffer.
|
|
||||||
http_engine_bodyack(eng, sizeof(msg)-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If an error occurs, you can undo all progress and start
|
|
||||||
// from scratch
|
|
||||||
int error = rand() & 1;
|
|
||||||
if (error) {
|
|
||||||
http_engine_undo(eng);
|
|
||||||
http_engine_status(eng, 500);
|
|
||||||
http_engine_done(eng);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
http_engine_done(eng);
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(void)
|
|
||||||
{
|
|
||||||
// Interface and port the server will be listening on.
|
|
||||||
char *addr = "127.0.0.1";
|
|
||||||
int port = 8080;
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
WSADATA wd;
|
|
||||||
if (WSAStartup(MAKEWORD(2, 2), &wd))
|
|
||||||
return -1;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Create the listening socket
|
|
||||||
SOCKET listen_fd = socket(AF_INET, SOCK_STREAM, 0);
|
|
||||||
if (listen_fd == INVALID_SOCKET)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
// Ignore the cooldown time for the bound interface to
|
|
||||||
// avoid that annoying "address in use" error
|
|
||||||
int reuse = 1;
|
|
||||||
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, (void*) &reuse, sizeof(reuse));
|
|
||||||
|
|
||||||
// Fill out the address struct
|
|
||||||
struct sockaddr_in bind_buf;
|
|
||||||
{
|
|
||||||
struct in_addr addr_buf;
|
|
||||||
if (inet_pton(AF_INET, addr, &addr_buf) != 1)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
bind_buf.sin_family = AF_INET;
|
|
||||||
bind_buf.sin_port = htons(port);
|
|
||||||
bind_buf.sin_addr = addr_buf;
|
|
||||||
memset(&bind_buf.sin_zero, 0, sizeof(bind_buf.sin_zero));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Associate the listening socket to the interface
|
|
||||||
if (bind(listen_fd, (struct sockaddr*) &bind_buf, sizeof(bind_buf)) < 0)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
// Allow incoming connections
|
|
||||||
if (listen(listen_fd, 32) < 0)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
|
|
||||||
printf("Waiting for a connection\n");
|
|
||||||
|
|
||||||
// Get an incoming connection from the kernel
|
|
||||||
SOCKET client_fd = accept(listen_fd, NULL, NULL);
|
|
||||||
if (client_fd == INVALID_SOCKET)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
printf("New connection\n");
|
|
||||||
|
|
||||||
// Initialize the HTTP state machine
|
|
||||||
HTTP_Engine eng;
|
|
||||||
http_engine_init(&eng, 0, memfunc, NULL);
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
|
|
||||||
// At this point, the engine can be in one
|
|
||||||
// of four states:
|
|
||||||
// 1) RECV_BUF: The engine is waiting for bytes from the network
|
|
||||||
// 2) SEND_BUF: The engine wants to write bytes from the network
|
|
||||||
// 3) CLOSED: The connection shut down at the HTTP layer
|
|
||||||
// 4) PREP_STATUS: A request was received and the associated response
|
|
||||||
// needs to be generated.
|
|
||||||
HTTP_EngineState state = http_engine_state(&eng);
|
|
||||||
|
|
||||||
if (state == HTTP_ENGINE_STATE_SERVER_PREP_STATUS) {
|
|
||||||
|
|
||||||
produce_response(&eng);
|
|
||||||
|
|
||||||
} else if (state == HTTP_ENGINE_STATE_SERVER_RECV_BUF) {
|
|
||||||
|
|
||||||
printf("Receiving bytes\n");
|
|
||||||
|
|
||||||
// Get a pointer to the engine's input buffer
|
|
||||||
int cap;
|
|
||||||
char *dst = http_engine_recvbuf(&eng, &cap);
|
|
||||||
|
|
||||||
// The application can write up to "cap" bytes
|
|
||||||
// to the "dst" buffer.
|
|
||||||
|
|
||||||
int ret = recv(client_fd, dst, cap, 0);
|
|
||||||
if (ret <= 0) {
|
|
||||||
// If the peer disconnected or an error occurred,
|
|
||||||
// we can "close" the engine. This makes it so any
|
|
||||||
// further operation on the engine will be a no-op
|
|
||||||
// and the next time we query the state we will get
|
|
||||||
// CLOSED.
|
|
||||||
http_engine_close(&eng);
|
|
||||||
ret = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("Received %d bytes\n", ret);
|
|
||||||
|
|
||||||
// Tell the engine how many bytes we wrote to
|
|
||||||
// the buffer.
|
|
||||||
http_engine_recvack(&eng, ret);
|
|
||||||
|
|
||||||
} else if (state == HTTP_ENGINE_STATE_SERVER_SEND_BUF) {
|
|
||||||
|
|
||||||
// This code is the same as the recv case except
|
|
||||||
// we read from the buffer instead of writing.
|
|
||||||
|
|
||||||
printf("Sending bytes\n");
|
|
||||||
|
|
||||||
int len;
|
|
||||||
char *src = http_engine_sendbuf(&eng, &len);
|
|
||||||
|
|
||||||
// Here "src" points to "len" bytes that need to
|
|
||||||
// be sent over the network.
|
|
||||||
int ret = send(client_fd, src, len, 0);
|
|
||||||
if (ret < 0) {
|
|
||||||
http_engine_close(&eng);
|
|
||||||
ret = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("Sent %d bytes\n", ret);
|
|
||||||
|
|
||||||
http_engine_sendack(&eng, ret);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// HTTP_ENGINE_STATE_SERVER_CLOSED
|
|
||||||
printf("HTTP close\n");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("Closing connection\n");
|
|
||||||
|
|
||||||
http_engine_free(&eng);
|
|
||||||
CLOSE_SOCKET(client_fd);
|
|
||||||
}
|
|
||||||
|
|
||||||
CLOSE_SOCKET(listen_fd);
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
WSACleanup();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,239 +0,0 @@
|
|||||||
#ifdef _WIN32
|
|
||||||
|
|
||||||
#include <winsock2.h>
|
|
||||||
#include <ws2tcpip.h>
|
|
||||||
#include <mswsock.h>
|
|
||||||
#include <chttp.h>
|
|
||||||
|
|
||||||
#define MAX_CLIENTS (1<<10)
|
|
||||||
|
|
||||||
// Callback used by the engine to manage dynamic memory
|
|
||||||
static void *memfunc(HTTP_MemoryFuncTag tag,
|
|
||||||
void *ptr, int len, void *data)
|
|
||||||
{
|
|
||||||
(void) data;
|
|
||||||
switch (tag) {
|
|
||||||
|
|
||||||
case HTTP_MEMFUNC_MALLOC:
|
|
||||||
return malloc(len);
|
|
||||||
|
|
||||||
case HTTP_MEMFUNC_FREE:
|
|
||||||
free(ptr);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static SOCKET start_accept(LPFN_ACCEPTEX AcceptEx, SOCKET listen_fd, OVERLAPPED *accept_ov, char *buf, int bufsize)
|
|
||||||
{
|
|
||||||
SOCKET accept_target = socket(AF_INET, SOCK_STREAM, 0);
|
|
||||||
if (accept_target == INVALID_SOCKET)
|
|
||||||
return INVALID_SOCKET;
|
|
||||||
|
|
||||||
unsigned long num;
|
|
||||||
memset(accept_ov, 0, sizeof(OVERLAPPED));
|
|
||||||
BOOL ok = AcceptEx(listen_fd, accept_target, buf, bufsize - ((sizeof(struct sockaddr_in) + 16) * 2),
|
|
||||||
sizeof(struct sockaddr_in) + 16,
|
|
||||||
sizeof(struct sockaddr_in) + 16,
|
|
||||||
&num, accept_ov);
|
|
||||||
if (!ok && WSAGetLastError() != ERROR_IO_PENDING) {
|
|
||||||
closesocket(accept_target);
|
|
||||||
return INVALID_SOCKET;
|
|
||||||
}
|
|
||||||
|
|
||||||
return accept_target;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void start_recv_or_send(SOCKET sock, OVERLAPPED *recv_ov, OVERLAPPED *send_ov, HTTP_Engine *eng)
|
|
||||||
{
|
|
||||||
HTTP_EngineState state = http_engine_state(eng);
|
|
||||||
|
|
||||||
if (state == HTTP_ENGINE_STATE_SERVER_RECV_BUF) {
|
|
||||||
|
|
||||||
int cap;
|
|
||||||
char *dst = http_engine_recvbuf(eng, &cap);
|
|
||||||
|
|
||||||
memset(recv_ov, 0, sizeof(OVERLAPPED));
|
|
||||||
int ok = ReadFile((HANDLE) sock, dst, cap, NULL, recv_ov);
|
|
||||||
if (!ok && GetLastError() != ERROR_IO_PENDING)
|
|
||||||
http_engine_close(eng);
|
|
||||||
|
|
||||||
} else if (state == HTTP_ENGINE_STATE_SERVER_SEND_BUF) {
|
|
||||||
|
|
||||||
int len;
|
|
||||||
char *src = http_engine_sendbuf(eng, &len);
|
|
||||||
|
|
||||||
memset(send_ov, 0, sizeof(OVERLAPPED));
|
|
||||||
int ok = WriteFile((HANDLE) sock, src, len, NULL, send_ov);
|
|
||||||
if (!ok && GetLastError() != ERROR_IO_PENDING)
|
|
||||||
http_engine_close(eng);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(void)
|
|
||||||
{
|
|
||||||
// Interface and port the server will be listening on.
|
|
||||||
char *addr = "127.0.0.1";
|
|
||||||
int port = 8080;
|
|
||||||
|
|
||||||
// Initialize the winsock subsystem
|
|
||||||
WSADATA wd;
|
|
||||||
if (WSAStartup(MAKEWORD(2, 2), &wd))
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
// Create the I/O completion port object
|
|
||||||
HANDLE iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 1);
|
|
||||||
if (iocp == INVALID_HANDLE_VALUE)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
// Create the listening socket
|
|
||||||
SOCKET listen_fd = socket(AF_INET, SOCK_STREAM, 0);
|
|
||||||
if (listen_fd == INVALID_SOCKET)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
// Register the socket into the IOCP
|
|
||||||
if (CreateIoCompletionPort((HANDLE) listen_fd, iocp, 0, 0) == NULL)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
// Ignore the cooldown time for the bound interface to
|
|
||||||
// avoid that annoying "address in use" error
|
|
||||||
int reuse = 1;
|
|
||||||
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, (void*) &reuse, sizeof(reuse));
|
|
||||||
|
|
||||||
// Fill out the address struct
|
|
||||||
struct sockaddr_in bind_buf;
|
|
||||||
{
|
|
||||||
struct in_addr addr_buf;
|
|
||||||
if (inet_pton(AF_INET, addr, &addr_buf) != 1)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
bind_buf.sin_family = AF_INET;
|
|
||||||
bind_buf.sin_port = htons(port);
|
|
||||||
bind_buf.sin_addr = addr_buf;
|
|
||||||
memset(&bind_buf.sin_zero, 0, sizeof(bind_buf.sin_zero));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Associate the listening socket to the interface
|
|
||||||
if (bind(listen_fd, (struct sockaddr*) &bind_buf, sizeof(bind_buf)) < 0)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
// Allow incoming connections
|
|
||||||
if (listen(listen_fd, 32) < 0)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
// Get the AcceptEx function pointer
|
|
||||||
LPFN_ACCEPTEX AcceptEx = NULL;
|
|
||||||
GUID GuidAcceptEx = WSAID_ACCEPTEX;
|
|
||||||
unsigned long num;
|
|
||||||
int ret = WSAIoctl(listen_fd,
|
|
||||||
SIO_GET_EXTENSION_FUNCTION_POINTER,
|
|
||||||
&GuidAcceptEx, sizeof(GuidAcceptEx),
|
|
||||||
&AcceptEx, sizeof(AcceptEx),
|
|
||||||
&num, NULL, NULL);
|
|
||||||
if (ret == SOCKET_ERROR)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
OVERLAPPED accept_ov;
|
|
||||||
char accept_buf[2 * (sizeof(struct sockaddr_in) + 16)];
|
|
||||||
SOCKET accept_target = start_accept(AcceptEx, listen_fd, &accept_ov, accept_buf, (int) sizeof(accept_buf));
|
|
||||||
if (accept_target == INVALID_SOCKET)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
OVERLAPPED recv_overlapped[MAX_CLIENTS];
|
|
||||||
OVERLAPPED send_overlapped[MAX_CLIENTS];
|
|
||||||
SOCKET sockets[MAX_CLIENTS];
|
|
||||||
HTTP_Engine engs[MAX_CLIENTS];
|
|
||||||
|
|
||||||
for (int i = 0; i < MAX_CLIENTS; i++)
|
|
||||||
sockets[i] = INVALID_SOCKET;
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
DWORD timeout = INFINITE;
|
|
||||||
|
|
||||||
DWORD transferred;
|
|
||||||
ULONG_PTR key;
|
|
||||||
OVERLAPPED *overlapped;
|
|
||||||
BOOL result = GetQueuedCompletionStatus(iocp, &transferred, &key, &overlapped, timeout);
|
|
||||||
if (!result && overlapped == NULL) {
|
|
||||||
if (GetLastError() == WAIT_TIMEOUT)
|
|
||||||
continue; // Go back to waiting
|
|
||||||
return -1; // Error
|
|
||||||
}
|
|
||||||
|
|
||||||
if (overlapped == &accept_ov) {
|
|
||||||
// A new client connected
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
while (i < MAX_CLIENTS && sockets[i] != INVALID_SOCKET)
|
|
||||||
i++;
|
|
||||||
|
|
||||||
if (i == MAX_CLIENTS)
|
|
||||||
closesocket(accept_target); // Server limit reached
|
|
||||||
else {
|
|
||||||
|
|
||||||
sockets[i] = accept_target;
|
|
||||||
http_engine_init(&engs[i], 0, memfunc, NULL);
|
|
||||||
|
|
||||||
// Register the socket into the IOCP
|
|
||||||
if (CreateIoCompletionPort((HANDLE) sockets[i], iocp, i, 0) == NULL)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
start_recv_or_send(sockets[i], &recv_overlapped[i], &send_overlapped[i], &engs[i]);
|
|
||||||
|
|
||||||
// Check that the recv or send operation was started.
|
|
||||||
// If now, remove the connection
|
|
||||||
if (http_engine_state(&engs[i]) == HTTP_ENGINE_STATE_CLIENT_CLOSED) {
|
|
||||||
closesocket(sockets[i]);
|
|
||||||
sockets[i] = INVALID_SOCKET;
|
|
||||||
http_engine_free(&engs[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
accept_target = start_accept(AcceptEx, listen_fd, &accept_ov, accept_buf, (int) sizeof(accept_buf));
|
|
||||||
if (accept_target == INVALID_SOCKET)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
// Go back to waiting
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Complete the current operation
|
|
||||||
if (0) {}
|
|
||||||
else if (overlapped == &recv_overlapped[key]) http_engine_recvack(&engs[key], transferred);
|
|
||||||
else if (overlapped == &send_overlapped[key]) http_engine_sendack(&engs[key], transferred);
|
|
||||||
|
|
||||||
if (http_engine_state(&engs[key]) == HTTP_ENGINE_STATE_SERVER_PREP_STATUS) {
|
|
||||||
|
|
||||||
// See blocking_server_with_engine.c to learn about how to
|
|
||||||
// build a response
|
|
||||||
http_engine_status(&engs[key], 200);
|
|
||||||
http_engine_body(&engs[key], HTTP_STR("Hello, world!"));
|
|
||||||
http_engine_done(&engs[key]);
|
|
||||||
}
|
|
||||||
|
|
||||||
start_recv_or_send(sockets[key],
|
|
||||||
&recv_overlapped[key],
|
|
||||||
&send_overlapped[key],
|
|
||||||
&engs[key]);
|
|
||||||
|
|
||||||
if (http_engine_state(&engs[key]) == HTTP_ENGINE_STATE_SERVER_CLOSED) {
|
|
||||||
http_engine_free(&engs[key]);
|
|
||||||
closesocket(sockets[key]);
|
|
||||||
sockets[key] = INVALID_SOCKET;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#else
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
int main(void)
|
|
||||||
{
|
|
||||||
printf("This example only works on windows!\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
#include <stdio.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <chttp.h>
|
|
||||||
|
|
||||||
// This example shows how to set up a basic HTTP server
|
|
||||||
|
|
||||||
int main(void)
|
|
||||||
{
|
|
||||||
http_global_init();
|
|
||||||
|
|
||||||
// Choose the interface to listen on and the port.
|
|
||||||
// Currently, servers can only bind to IPv4 addresses.
|
|
||||||
HTTP_String addr = HTTP_STR("127.0.0.1");
|
|
||||||
uint16_t port = 8080;
|
|
||||||
|
|
||||||
bool all_interfaces = false;
|
|
||||||
|
|
||||||
// If you want to bind to all interfaces, you can
|
|
||||||
// set the address to an empty string.
|
|
||||||
if (all_interfaces)
|
|
||||||
addr = HTTP_STR("");
|
|
||||||
|
|
||||||
// Instanciate the HTTP server object
|
|
||||||
HTTP_Server *server = http_server_init(addr, port);
|
|
||||||
if (server == NULL)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
// Now we loop forever. Every iteration will serve
|
|
||||||
// a single HTTP request
|
|
||||||
for (;;) {
|
|
||||||
|
|
||||||
HTTP_Request *req;
|
|
||||||
HTTP_ResponseBuilder builder;
|
|
||||||
|
|
||||||
// Block until a request is available
|
|
||||||
int ret = http_server_wait(server, &req, &builder);
|
|
||||||
|
|
||||||
// The wait functions returns 0 on success and -1
|
|
||||||
// on error. By "error" I mean an unrecoverable
|
|
||||||
// condition. There is no other option than kill
|
|
||||||
// the process.
|
|
||||||
if (ret < 0)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
// The request information is accessible from
|
|
||||||
// the [req] variable. Most fields in the request
|
|
||||||
// struct are reference to the original request
|
|
||||||
// string. They use type HTTP_String and are not
|
|
||||||
// null-terminated. This means you'll have to make
|
|
||||||
// sure to express the length when interacting with
|
|
||||||
// libc:
|
|
||||||
HTTP_String path = req->url.path;
|
|
||||||
printf("requested path [%.*s]\n", HTTP_UNPACK(path));
|
|
||||||
|
|
||||||
// To find a specific header value, you can either
|
|
||||||
// iterate over the [req->headers] array or use
|
|
||||||
// a helper function. Note that this compares header
|
|
||||||
// names case-insensitively.
|
|
||||||
int idx = http_find_header(req->headers, req->num_headers, HTTP_STR("Some-Header-Name"));
|
|
||||||
if (idx == -1) {
|
|
||||||
// Header wasn't found
|
|
||||||
} else {
|
|
||||||
// Found
|
|
||||||
HTTP_String value = req->headers[idx].value;
|
|
||||||
printf("Header has value [%.*s]\n", HTTP_UNPACK(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
// To create a response, you will need to specify
|
|
||||||
// status code, headers, and content in the proper
|
|
||||||
// order.
|
|
||||||
|
|
||||||
// First the status code
|
|
||||||
http_response_builder_status(builder, 200);
|
|
||||||
|
|
||||||
// Then zero or more headers
|
|
||||||
http_response_builder_header(builder, HTTP_STR("Content-Type: text/plain"));
|
|
||||||
|
|
||||||
// Then you can write zero or more chunks of the response body
|
|
||||||
http_response_builder_body(builder, HTTP_STR("Hello"));
|
|
||||||
http_response_builder_body(builder, HTTP_STR(", world!"));
|
|
||||||
|
|
||||||
// Then, mark the request as complete (Very important or the server will hang!)
|
|
||||||
http_response_builder_done(builder);
|
|
||||||
|
|
||||||
// Note that none of the http_response_builder_* functions return errors.
|
|
||||||
// This is by design to simplify user endpoint code. If at any point
|
|
||||||
// something goes wrong, the server will send a code 4xx or 5xx to
|
|
||||||
// the client or abort the TCP connection entirely.
|
|
||||||
}
|
|
||||||
|
|
||||||
// This program will loop forever, but if you write
|
|
||||||
// your server in a way to exit gracefully, this is
|
|
||||||
// you the server object is freed:
|
|
||||||
http_server_free(server);
|
|
||||||
http_global_free();
|
|
||||||
|
|
||||||
// Have fun. Bye!
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
#include <string.h>
|
|
||||||
#include <chttp.h>
|
|
||||||
|
|
||||||
// This example shows how to generate response bodies
|
|
||||||
// using the zero-copy API.
|
|
||||||
|
|
||||||
int main(void)
|
|
||||||
{
|
|
||||||
http_global_init();
|
|
||||||
|
|
||||||
// All the setup is identical to the previous example.
|
|
||||||
// The only thing that changes where "http_response_builder_body"
|
|
||||||
// is called.
|
|
||||||
|
|
||||||
HTTP_Server *server = http_server_init(HTTP_STR("127.0.0.1"), 8080);
|
|
||||||
if (server == NULL)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
|
|
||||||
HTTP_Request *req;
|
|
||||||
HTTP_ResponseBuilder builder;
|
|
||||||
|
|
||||||
int ret = http_server_wait(server, &req, &builder);
|
|
||||||
if (ret < 0) return -1;
|
|
||||||
|
|
||||||
http_response_builder_status(builder, 200);
|
|
||||||
http_response_builder_header(builder, HTTP_STR("Content-Type: text/plain"));
|
|
||||||
|
|
||||||
// The previous example used the *_body function to
|
|
||||||
// write the response body in chunks:
|
|
||||||
//
|
|
||||||
// http_response_builder_body(builder, HTTP_STR("Hello"));
|
|
||||||
// http_response_builder_body(builder, HTTP_STR(", world!"));
|
|
||||||
//
|
|
||||||
// This function reads from an user buffer and copies
|
|
||||||
// the data in the connection's output buffer. If the
|
|
||||||
// data is not in a contiguous region that's fine as
|
|
||||||
// the function can be called repeatedly on separate
|
|
||||||
// chunks.
|
|
||||||
//
|
|
||||||
// This function assumes the user is holding in memory
|
|
||||||
// the data to be sent beforehand, but this may not
|
|
||||||
// be true. If for instance the data comes from a file,
|
|
||||||
// the user will need to read from the file, copy in
|
|
||||||
// memory and then write to the response body.
|
|
||||||
//
|
|
||||||
// The zero-copy API allows copying directly from the
|
|
||||||
// source of the data (such as the read() system call
|
|
||||||
// on a file descriptor) to the server's output buffer
|
|
||||||
|
|
||||||
char example_data[] = "I'm some example data!";
|
|
||||||
int example_data_len = sizeof(example_data)-1;
|
|
||||||
|
|
||||||
// Tell the server how much data we are going to write
|
|
||||||
http_response_builder_bodycap(builder, example_data_len);
|
|
||||||
|
|
||||||
int cap;
|
|
||||||
char *dst;
|
|
||||||
|
|
||||||
// Get a pointer to the server's output buffer. The
|
|
||||||
// output parameter [cap] is the capacity of the region
|
|
||||||
// and is equal or larger than the data we requested
|
|
||||||
// with *_bodycap
|
|
||||||
dst = http_response_builder_bodybuf(builder, &cap);
|
|
||||||
|
|
||||||
// Write the data directly into the output buffer. In
|
|
||||||
// this example we are copying from memory, but you could
|
|
||||||
// read from a file or a socket
|
|
||||||
if (dst) {
|
|
||||||
memcpy(dst, example_data, example_data_len);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tell the server how much bytes we have written to
|
|
||||||
// the provided region.
|
|
||||||
http_response_builder_bodyack(builder, example_data_len);
|
|
||||||
|
|
||||||
// The reason we had to guard the [memcpy] by checking the
|
|
||||||
// [dst] pointer is that if an error occurred internally
|
|
||||||
// then *_bodybuf will return NULL. This will cause the
|
|
||||||
// server to either return an internally generated error
|
|
||||||
// response or drop the connection. The correct thing to
|
|
||||||
// do in that situation is not access the pointer and do
|
|
||||||
// as nothing bad happened.
|
|
||||||
|
|
||||||
// As usual, mark the response as complete
|
|
||||||
http_response_builder_done(builder);
|
|
||||||
|
|
||||||
// If we're being being honest, this is not a zero-copy
|
|
||||||
// interface. It's more like an N-1 copy interface as in
|
|
||||||
// it just avoids one copy from userspace to userspace!
|
|
||||||
}
|
|
||||||
|
|
||||||
http_server_free(server);
|
|
||||||
http_global_free();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
#include <stddef.h>
|
|
||||||
#include <chttp.h>
|
|
||||||
|
|
||||||
// This example shows how undo a response that is being built
|
|
||||||
// when an error occurs.
|
|
||||||
|
|
||||||
int main(void)
|
|
||||||
{
|
|
||||||
http_global_init();
|
|
||||||
|
|
||||||
HTTP_Server *server = http_server_init(HTTP_STR("127.0.0.1"), 8080);
|
|
||||||
if (server == NULL)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
|
|
||||||
HTTP_Request *req;
|
|
||||||
HTTP_ResponseBuilder builder;
|
|
||||||
|
|
||||||
int ret = http_server_wait(server, &req, &builder);
|
|
||||||
if (ret < 0) return -1;
|
|
||||||
|
|
||||||
// Say we are building a request..
|
|
||||||
|
|
||||||
http_response_builder_status(builder, 200);
|
|
||||||
http_response_builder_header(builder, HTTP_STR("Content-Type: text/plain"));
|
|
||||||
|
|
||||||
// .. and in the middle of building an error condition
|
|
||||||
// occurs. Maybe a file was missing or an allocation fails.
|
|
||||||
// The proper response in this case would be a code 500
|
|
||||||
// with an error message, but we already wrote the first
|
|
||||||
// part of the response assuming the operation would succede.
|
|
||||||
//
|
|
||||||
// You can use the *_undo function to reset the response
|
|
||||||
// building process
|
|
||||||
|
|
||||||
bool error_occurred = true;
|
|
||||||
if (error_occurred) {
|
|
||||||
|
|
||||||
http_response_builder_undo(builder);
|
|
||||||
|
|
||||||
// Now we are back to setting the status code
|
|
||||||
http_response_builder_status(builder, 500);
|
|
||||||
http_response_builder_header(builder, HTTP_STR("Content-Type: text/plain"));
|
|
||||||
http_response_builder_body(builder, HTTP_STR("An error occurred!"));
|
|
||||||
http_response_builder_done(builder);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
// If no error occures, we finish as planned
|
|
||||||
http_response_builder_body(builder, HTTP_STR("Hello, world!"));
|
|
||||||
http_response_builder_done(builder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
http_server_free(server);
|
|
||||||
http_global_free();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,386 +0,0 @@
|
|||||||
#include <stdlib.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <chttp.h>
|
|
||||||
|
|
||||||
// !!! WARNING !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! //
|
|
||||||
// //
|
|
||||||
// This example is just a proof of concept for now as the library //
|
|
||||||
// still isn't thread-safe. //
|
|
||||||
// //
|
|
||||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! //
|
|
||||||
|
|
||||||
// This example shows how to delegate the response creation
|
|
||||||
// process to other threads.
|
|
||||||
//
|
|
||||||
// Your server may have some endpoints that require considerable
|
|
||||||
// computation or may be waiting for some external system to
|
|
||||||
// complete. If we used the current pattern we've been using for
|
|
||||||
// generating requests, following request will have to wait until
|
|
||||||
// this processing has concluded.
|
|
||||||
//
|
|
||||||
// One solution for this situation is to create a separate thread
|
|
||||||
// to do the waiting or processing. When a request is received
|
|
||||||
// that requires processing, it is passed to the second thread.
|
|
||||||
// In the mean time, the main thread can process the next request.
|
|
||||||
// When the thread has finished, it can just call the usual
|
|
||||||
// functions to produce a response.
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#define WIN32_LEAN_AND_MEAN
|
|
||||||
#include <windows.h>
|
|
||||||
typedef void* Thread;
|
|
||||||
typedef unsigned long ThreadReturn;
|
|
||||||
typedef CRITICAL_SECTION Mutex;
|
|
||||||
typedef CONDITION_VARIABLE Condvar;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef __linux__
|
|
||||||
#include <pthread.h>
|
|
||||||
typedef pthread_t Thread;
|
|
||||||
typedef void* ThreadReturn;
|
|
||||||
typedef pthread_mutex_t Mutex;
|
|
||||||
typedef pthread_cond_t Condvar;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// The following types are used to describe a job the worker
|
|
||||||
// needs to work on.
|
|
||||||
typedef enum {
|
|
||||||
|
|
||||||
// Special value used to tell the worker the program is terminating
|
|
||||||
NO_JOB,
|
|
||||||
|
|
||||||
// We assume jobs may be of two different types we call A and B
|
|
||||||
JOB_A,
|
|
||||||
JOB_B,
|
|
||||||
|
|
||||||
} JobType;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
JobType type;
|
|
||||||
HTTP_ResponseBuilder builder;
|
|
||||||
} Job;
|
|
||||||
|
|
||||||
// Maximum number of jobs that can be buffered at once
|
|
||||||
#define MAX_JOBS 100
|
|
||||||
|
|
||||||
void init_job_queue(void);
|
|
||||||
void free_job_queue(void);
|
|
||||||
|
|
||||||
// This function pops an item from the job queue. If the
|
|
||||||
// queue is empty, the thread will block until one is
|
|
||||||
// available.
|
|
||||||
Job pop_job(void);
|
|
||||||
|
|
||||||
// This function adds a job to the queue. The block argument
|
|
||||||
// changes the behavior when the queue is full and there is
|
|
||||||
// no space for a new job. If the block argument is true and
|
|
||||||
// there is no space, the thread waits. If the argument is
|
|
||||||
// false the function exits immediately by returning false
|
|
||||||
// with no new job pushed.
|
|
||||||
bool push_job(Job job, bool block);
|
|
||||||
|
|
||||||
void thread_create(Thread *thread, void *arg, ThreadReturn (*func)(void*));
|
|
||||||
ThreadReturn thread_join(Thread thread);
|
|
||||||
|
|
||||||
void mutex_init(Mutex *mutex);
|
|
||||||
void mutex_free(Mutex *mutex);
|
|
||||||
void mutex_lock(Mutex *mutex);
|
|
||||||
void mutex_unlock(Mutex *mutex);
|
|
||||||
|
|
||||||
void condvar_init(Condvar *condvar);
|
|
||||||
void condvar_free(Condvar *condvar);
|
|
||||||
void condvar_wait(Condvar *condvar, Mutex *mutex);
|
|
||||||
void condvar_signal(Condvar *condvar);
|
|
||||||
|
|
||||||
ThreadReturn worker(void*)
|
|
||||||
{
|
|
||||||
for (bool exit = false; !exit; ) {
|
|
||||||
|
|
||||||
Job job = pop_job();
|
|
||||||
|
|
||||||
switch (job.type) {
|
|
||||||
|
|
||||||
case NO_JOB:
|
|
||||||
exit = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case JOB_A:
|
|
||||||
http_response_builder_status(job.builder, 200);
|
|
||||||
http_response_builder_body(job.builder, HTTP_STR("Job A completed"));
|
|
||||||
http_response_builder_done(job.builder);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case JOB_B:
|
|
||||||
http_response_builder_status(job.builder, 200);
|
|
||||||
http_response_builder_body(job.builder, HTTP_STR("Job B completed"));
|
|
||||||
http_response_builder_done(job.builder);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(void)
|
|
||||||
{
|
|
||||||
http_global_free();
|
|
||||||
init_job_queue();
|
|
||||||
|
|
||||||
HTTP_Server *server = http_server_init(HTTP_STR("127.0.0.1"), 8080);
|
|
||||||
if (server == NULL)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
Thread worker_id;
|
|
||||||
thread_create(&worker_id, NULL, worker);
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
|
|
||||||
HTTP_Request *req;
|
|
||||||
HTTP_ResponseBuilder builder;
|
|
||||||
|
|
||||||
int ret = http_server_wait(server, &req, &builder);
|
|
||||||
if (ret < 0) return -1;
|
|
||||||
|
|
||||||
if (http_streq(req->url.path, HTTP_STR("/endpoint_A"))) {
|
|
||||||
|
|
||||||
// Endpoint A sends the job to the worker.
|
|
||||||
// If too many jobs are queued, it blocks
|
|
||||||
|
|
||||||
Job job;
|
|
||||||
job.type = JOB_A;
|
|
||||||
job.builder = builder;
|
|
||||||
push_job(job, true);
|
|
||||||
|
|
||||||
} else if (http_streq(req->url.path, HTTP_STR("/endpoint_B"))) {
|
|
||||||
|
|
||||||
// Endpoint B sends the job to the worker
|
|
||||||
// but fails if the queue is full, in which
|
|
||||||
// case the "503 Service Unavailable" response
|
|
||||||
// is generated.
|
|
||||||
|
|
||||||
Job job;
|
|
||||||
job.type = JOB_B;
|
|
||||||
job.builder = builder;
|
|
||||||
if (!push_job(job, false)) {
|
|
||||||
http_response_builder_status(builder, 503);
|
|
||||||
http_response_builder_done(builder);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
// Other endpoints may resolve immediately
|
|
||||||
|
|
||||||
http_response_builder_status(builder, 404);
|
|
||||||
http_response_builder_done(builder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop the worker by sending an empty job
|
|
||||||
Job job;
|
|
||||||
job.type = NO_JOB;
|
|
||||||
push_job(job, true);
|
|
||||||
thread_join(worker_id);
|
|
||||||
|
|
||||||
http_server_free(server);
|
|
||||||
free_job_queue();
|
|
||||||
http_global_free();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
//////////////////////////////////////////////
|
|
||||||
|
|
||||||
// This is a pretty standard condition variable-based
|
|
||||||
// producer-consumer queue. In this example we are using
|
|
||||||
// one worker, but we could easily have more than that.
|
|
||||||
|
|
||||||
Job queue[MAX_JOBS];
|
|
||||||
int queue_head = 0;
|
|
||||||
int queue_count = 0;
|
|
||||||
Mutex queue_lock;
|
|
||||||
Condvar queue_consume_event;
|
|
||||||
Condvar queue_produce_event;
|
|
||||||
|
|
||||||
void init_job_queue(void)
|
|
||||||
{
|
|
||||||
mutex_init(&queue_lock);
|
|
||||||
condvar_init(&queue_consume_event);
|
|
||||||
condvar_init(&queue_produce_event);
|
|
||||||
}
|
|
||||||
|
|
||||||
void free_job_queue(void)
|
|
||||||
{
|
|
||||||
condvar_free(&queue_produce_event);
|
|
||||||
condvar_free(&queue_consume_event);
|
|
||||||
mutex_free(&queue_lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
Job pop_job(void)
|
|
||||||
{
|
|
||||||
mutex_lock(&queue_lock);
|
|
||||||
|
|
||||||
while (queue_count == 0)
|
|
||||||
condvar_wait(&queue_produce_event, &queue_lock);
|
|
||||||
|
|
||||||
Job job = queue[queue_head];
|
|
||||||
queue_head = (queue_head + 1) % MAX_JOBS;
|
|
||||||
queue_count--;
|
|
||||||
|
|
||||||
condvar_signal(&queue_consume_event);
|
|
||||||
mutex_unlock(&queue_lock);
|
|
||||||
return job;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool push_job(Job job, bool block)
|
|
||||||
{
|
|
||||||
mutex_lock(&queue_lock);
|
|
||||||
if (queue_count == 0) {
|
|
||||||
|
|
||||||
if (!block) {
|
|
||||||
mutex_unlock(&queue_lock);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
do
|
|
||||||
condvar_wait(&queue_consume_event, &queue_lock);
|
|
||||||
while (queue_count == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
int tail = (queue_head + queue_count) % MAX_JOBS;
|
|
||||||
queue[tail] = job;
|
|
||||||
queue_count++;
|
|
||||||
|
|
||||||
condvar_signal(&queue_produce_event);
|
|
||||||
mutex_unlock(&queue_lock);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//////////////////////////////////////////////
|
|
||||||
|
|
||||||
void thread_create(Thread *thread, void *arg, ThreadReturn (*func)(void*))
|
|
||||||
{
|
|
||||||
#ifdef _WIN32
|
|
||||||
Thread thread_ = CreateThread(NULL, 0, func, arg, 0, NULL);
|
|
||||||
if (thread_ == INVALID_HANDLE_VALUE)
|
|
||||||
abort();
|
|
||||||
*thread = thread_;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef __linux__
|
|
||||||
int ret = pthread_create(thread, NULL, func, arg);
|
|
||||||
if (ret) abort();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
ThreadReturn thread_join(Thread thread)
|
|
||||||
{
|
|
||||||
#ifdef _WIN32
|
|
||||||
ThreadReturn result;
|
|
||||||
WaitForSingleObject(thread, INFINITE);
|
|
||||||
if (!GetExitCodeThread(thread, &result))
|
|
||||||
abort();
|
|
||||||
CloseHandle(thread);
|
|
||||||
return result;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef __linux__
|
|
||||||
ThreadReturn result;
|
|
||||||
int ret = pthread_join(thread, &result);
|
|
||||||
if (ret) abort();
|
|
||||||
return result;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void mutex_init(Mutex *mutex)
|
|
||||||
{
|
|
||||||
#ifdef _WIN32
|
|
||||||
InitializeCriticalSection(mutex);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef __linux__
|
|
||||||
if (pthread_mutex_init(mutex, NULL))
|
|
||||||
abort();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void mutex_free(Mutex *mutex)
|
|
||||||
{
|
|
||||||
#ifdef _WIN32
|
|
||||||
DeleteCriticalSection(mutex);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef __linux__
|
|
||||||
if (pthread_mutex_destroy(mutex))
|
|
||||||
abort();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void mutex_lock(Mutex *mutex)
|
|
||||||
{
|
|
||||||
#ifdef _WIN32
|
|
||||||
EnterCriticalSection(mutex);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef __linux__
|
|
||||||
if (pthread_mutex_lock(mutex))
|
|
||||||
abort();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void mutex_unlock(Mutex *mutex)
|
|
||||||
{
|
|
||||||
#ifdef _WIN32
|
|
||||||
LeaveCriticalSection(mutex);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef __linux__
|
|
||||||
if (pthread_mutex_unlock(mutex))
|
|
||||||
abort();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void condvar_init(Condvar *condvar)
|
|
||||||
{
|
|
||||||
#ifdef _WIN32
|
|
||||||
InitializeConditionVariable(condvar);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef __linux__
|
|
||||||
if (pthread_cond_init(condvar, NULL))
|
|
||||||
abort();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void condvar_free(Condvar *condvar)
|
|
||||||
{
|
|
||||||
#ifdef __linux__
|
|
||||||
if (pthread_cond_destroy(condvar))
|
|
||||||
abort();
|
|
||||||
#else
|
|
||||||
(void) condvar;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void condvar_wait(Condvar *condvar, Mutex *mutex)
|
|
||||||
{
|
|
||||||
#ifdef _WIN32
|
|
||||||
if (!SleepConditionVariableCS(condvar, mutex, INFINITE))
|
|
||||||
abort();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef __linux__
|
|
||||||
int err = pthread_cond_wait(condvar, mutex);
|
|
||||||
if (err) abort();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void condvar_signal(Condvar *condvar)
|
|
||||||
{
|
|
||||||
#ifdef _WIN32
|
|
||||||
WakeConditionVariable(condvar);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef __linux__
|
|
||||||
if (pthread_cond_signal(condvar))
|
|
||||||
abort();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
|
|
||||||
int main(void) {}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
#include <stddef.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <chttp.h>
|
|
||||||
|
|
||||||
// This example shows how to set up an HTTPS (HTTP over TLS)
|
|
||||||
// server.
|
|
||||||
|
|
||||||
int main(void)
|
|
||||||
{
|
|
||||||
http_global_init();
|
|
||||||
|
|
||||||
// To setup an HTTPS server, we need to use the *_ex variant
|
|
||||||
// of the server initialization function as it offers more
|
|
||||||
// control. Server objects can serve HTTP traffic, HTTPS
|
|
||||||
// traffic, or both at the same time. The init_ex function
|
|
||||||
// allows us to control this behavior.
|
|
||||||
|
|
||||||
// The first argument is the local interface address. It
|
|
||||||
// works just as the other examples but is shared between
|
|
||||||
// HTTP and HTTPS. Then come the HTTP port and HTTPS port
|
|
||||||
// arguments. If you want to disable HTTP or HTTPS you can
|
|
||||||
// pass zero to its port argument. If the HTTPS port is
|
|
||||||
// not zero, you need to pass the file names of the server's
|
|
||||||
// certificate and private key.
|
|
||||||
HTTP_Server *server = http_server_init_ex(
|
|
||||||
HTTP_STR("127.0.0.1"), // HTTP and HTTPS port
|
|
||||||
8080, // HTTP port
|
|
||||||
8443, // HTTPS port
|
|
||||||
HTTP_STR("cert.pem"),
|
|
||||||
HTTP_STR("privkey.pem")
|
|
||||||
);
|
|
||||||
if (server == NULL)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
// Just to be clear, to initialize a plain HTTP server
|
|
||||||
// using the *_ex function we would do this:
|
|
||||||
//
|
|
||||||
// HTTP_Server *server = http_server_init_ex(
|
|
||||||
// HTTP_STR("127.0.0.1"), // HTTP and HTTPS port
|
|
||||||
// 8080, // HTTP port
|
|
||||||
// 0, // HTTPS disabled
|
|
||||||
// HTTP_STR(""), // ignore
|
|
||||||
// HTTP_STR("") // ignore
|
|
||||||
// );
|
|
||||||
//
|
|
||||||
// and if we wanted and HTTPS-only server we would
|
|
||||||
// do this:
|
|
||||||
//
|
|
||||||
// HTTP_Server *server = http_server_init_ex(
|
|
||||||
// HTTP_STR("127.0.0.1"), // HTTP and HTTPS port
|
|
||||||
// 0, // HTTP disabled
|
|
||||||
// 8443, // HTTPS port
|
|
||||||
// HTTP_STR("cert.pem"),
|
|
||||||
// HTTP_STR("privkey.pem")
|
|
||||||
// );
|
|
||||||
|
|
||||||
// Everything else is identical to the simple HTTP server
|
|
||||||
// example.
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
|
|
||||||
HTTP_Request *req;
|
|
||||||
HTTP_ResponseBuilder builder;
|
|
||||||
|
|
||||||
int ret = http_server_wait(server, &req, &builder);
|
|
||||||
if (ret < 0) return -1;
|
|
||||||
|
|
||||||
http_response_builder_status(builder, 200);
|
|
||||||
http_response_builder_header(builder, HTTP_STR("Content-Type: text/plain"));
|
|
||||||
http_response_builder_body(builder, HTTP_STR("Hello"));
|
|
||||||
http_response_builder_body(builder, HTTP_STR(", world!"));
|
|
||||||
http_response_builder_done(builder);
|
|
||||||
}
|
|
||||||
|
|
||||||
http_server_free(server);
|
|
||||||
http_global_free();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,168 +0,0 @@
|
|||||||
#include <stddef.h>
|
|
||||||
#include <chttp.h>
|
|
||||||
|
|
||||||
// This is an example of how to serve different websites
|
|
||||||
// over a single HTTPS server instance.
|
|
||||||
|
|
||||||
int setup_test_certificates(void);
|
|
||||||
|
|
||||||
int main(void)
|
|
||||||
{
|
|
||||||
http_global_init();
|
|
||||||
|
|
||||||
// To test this program you need to add the following
|
|
||||||
// lines to your hosts file:
|
|
||||||
//
|
|
||||||
// 127.0.0.1 websiteA.com
|
|
||||||
// 127.0.0.1 websiteB.com
|
|
||||||
// 127.0.0.1 websiteC.com
|
|
||||||
//
|
|
||||||
// That you can find at /etc/hosts on Linux and
|
|
||||||
// C:\Windows\System32\drivers\etc\hosts on Windows
|
|
||||||
|
|
||||||
// First, create three certificates for the domains:
|
|
||||||
//
|
|
||||||
// websiteA.com
|
|
||||||
// websiteB.com
|
|
||||||
// websiteC.com
|
|
||||||
//
|
|
||||||
// This will create a number of certificate files
|
|
||||||
// and private key files
|
|
||||||
//
|
|
||||||
// websiteA_cert.pem websiteA_key.pem
|
|
||||||
// websiteB_cert.pem websiteB_key.pem
|
|
||||||
// websiteC_cert.pem websiteC_key.pem
|
|
||||||
//
|
|
||||||
// Of course this is just for testing. It is expected
|
|
||||||
// you have your own.
|
|
||||||
int ret = setup_test_certificates();
|
|
||||||
if (ret < 0)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
// First, set up an HTTPS server instance with one
|
|
||||||
// of the certificate. This will act as default certificate
|
|
||||||
// when ecrypted connections don't target a specific domain.
|
|
||||||
HTTP_Server *server = http_server_init_ex(
|
|
||||||
HTTP_STR("127.0.0.1"), 8080, 8443,
|
|
||||||
HTTP_STR("websiteA_cert.pem"),
|
|
||||||
HTTP_STR("websiteA_key.pem")
|
|
||||||
);
|
|
||||||
if (server == NULL)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
// Then we can add an arbitrary number of additional
|
|
||||||
// certificates using the add_website function
|
|
||||||
|
|
||||||
ret = http_server_add_website(server,
|
|
||||||
HTTP_STR("websiteB.com"),
|
|
||||||
HTTP_STR("websiteB_cert.pem"),
|
|
||||||
HTTP_STR("websiteB_key.pem")
|
|
||||||
);
|
|
||||||
if (ret < 0)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
ret = http_server_add_website(server,
|
|
||||||
HTTP_STR("websiteC.com"),
|
|
||||||
HTTP_STR("websiteC_cert.pem"),
|
|
||||||
HTTP_STR("websiteC_key.pem")
|
|
||||||
);
|
|
||||||
if (ret < 0)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
// Now the server is ready to accept incoming HTTP
|
|
||||||
// or HTTPS connections.
|
|
||||||
//
|
|
||||||
// Note that the add_website function is only used
|
|
||||||
// to serve the correct certificate to the client.
|
|
||||||
// The HTTP request itself may very well hold a
|
|
||||||
// different domain name in the host header:
|
|
||||||
//
|
|
||||||
// [client] [server]
|
|
||||||
// | |
|
|
||||||
// | TLS hanshake to domain1.com |
|
|
||||||
// | -------------------------------> |
|
|
||||||
// | |
|
|
||||||
// | cert for domain1.com |
|
|
||||||
// | <------------------------------- |
|
|
||||||
// | |
|
|
||||||
// | HTTP request to domain2.com |
|
|
||||||
// | over the encrypted connection |
|
|
||||||
// | established with domain1.com |
|
|
||||||
// | -------------------------------> |
|
|
||||||
// | |
|
|
||||||
// | response as domain2.com |
|
|
||||||
// | <------------------------------- |
|
|
||||||
// | |
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
|
|
||||||
HTTP_Request *req;
|
|
||||||
HTTP_ResponseBuilder builder;
|
|
||||||
ret = http_server_wait(server, &req, &builder);
|
|
||||||
if (ret < 0)
|
|
||||||
break;
|
|
||||||
|
|
||||||
if (http_match_host(req, HTTP_STR("websiteB.com"), 8080) ||
|
|
||||||
http_match_host(req, HTTP_STR("websiteB.com"), 8443)) {
|
|
||||||
|
|
||||||
http_response_builder_status(builder, 200);
|
|
||||||
http_response_builder_body(builder, HTTP_STR("Hello from websiteB.com!"));
|
|
||||||
http_response_builder_done(builder);
|
|
||||||
|
|
||||||
} else if (http_match_host(req, HTTP_STR("websiteC.com"), 8080) ||
|
|
||||||
http_match_host(req, HTTP_STR("websiteC.com"), 8443)) {
|
|
||||||
|
|
||||||
http_response_builder_status(builder, 200);
|
|
||||||
http_response_builder_body(builder, HTTP_STR("Hello from websiteC.com!"));
|
|
||||||
http_response_builder_done(builder);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
// Serve websiteA.com by default to be consistent
|
|
||||||
// with the certificate setup
|
|
||||||
|
|
||||||
http_response_builder_status(builder, 200);
|
|
||||||
http_response_builder_body(builder, HTTP_STR("Hello from websiteA.com!"));
|
|
||||||
http_response_builder_done(builder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
http_server_free(server);
|
|
||||||
http_global_free();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int setup_test_certificates(void)
|
|
||||||
{
|
|
||||||
int ret = http_create_test_certificate(
|
|
||||||
HTTP_STR("IT"),
|
|
||||||
HTTP_STR("Organization A"),
|
|
||||||
HTTP_STR("websiteA.com"),
|
|
||||||
HTTP_STR("websiteA_cert.pem"),
|
|
||||||
HTTP_STR("websiteA_key.pem")
|
|
||||||
);
|
|
||||||
if (ret < 0)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
ret = http_create_test_certificate(
|
|
||||||
HTTP_STR("IT"),
|
|
||||||
HTTP_STR("Organization B"),
|
|
||||||
HTTP_STR("websiteB.com"),
|
|
||||||
HTTP_STR("websiteB_cert.pem"),
|
|
||||||
HTTP_STR("websiteB_key.pem")
|
|
||||||
);
|
|
||||||
if (ret < 0)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
ret = http_create_test_certificate(
|
|
||||||
HTTP_STR("IT"),
|
|
||||||
HTTP_STR("Organization C"),
|
|
||||||
HTTP_STR("websiteC.com"),
|
|
||||||
HTTP_STR("websiteC_cert.pem"),
|
|
||||||
HTTP_STR("websiteC_key.pem")
|
|
||||||
);
|
|
||||||
if (ret < 0)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
(The following are thoughts that came to mind that I didn't have the time to add to docs but don't want to forget.)
|
|
||||||
|
|
||||||
The immediate mode API allows writing directly to the library's output buffer avoiding intermediate allocations
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
Allow resolving requests from different threads
|
|
||||||
Find a way to make sure chttp.h and chttp.c are always up to date
|
|
||||||
Find a way to compile OpenSSL on windows
|
|
||||||
handle 3xx client redirections
|
|
||||||
add discussion on string management
|
|
||||||
add discussion on error management
|
|
||||||
add timers
|
|
||||||
add debug and release builds
|
|
||||||
+51
-26
@@ -6,56 +6,81 @@ class Amalgamator:
|
|||||||
self.out += text
|
self.out += text
|
||||||
|
|
||||||
def append_file(self, file):
|
def append_file(self, file):
|
||||||
|
|
||||||
self.out += "\n"
|
self.out += "\n"
|
||||||
self.out += "////////////////////////////////////////////////////////////////////////////////////////\n"
|
self.out += "////////////////////////////////////////////////////////////////////////////////////////\n"
|
||||||
self.out += "// " + file + "\n"
|
self.out += "// " + file + "\n"
|
||||||
self.out += "////////////////////////////////////////////////////////////////////////////////////////\n"
|
self.out += "////////////////////////////////////////////////////////////////////////////////////////\n"
|
||||||
self.out += "\n"
|
|
||||||
self.out += "#line 1 \"" + file + "\"\n"
|
|
||||||
self.out += open(file).read()
|
self.out += open(file).read()
|
||||||
|
|
||||||
if len(self.out) > 0 and self.out[len(self.out)-1] != '\n':
|
if len(self.out) > 0 and self.out[len(self.out) - 1] != "\n":
|
||||||
self.out += "\n"
|
self.out += "\n"
|
||||||
|
|
||||||
def save(self, file):
|
def save(self, file):
|
||||||
open(file, 'w').write(self.out)
|
open(file, "w").write(self.out)
|
||||||
|
|
||||||
desc = """
|
|
||||||
// This file was generated automatically. Do not modify directly!
|
desc = """// cHTTP, an HTTP client and server library!
|
||||||
|
//
|
||||||
|
// This file was generated automatically. Do not modify directly.
|
||||||
|
//
|
||||||
|
// Refer to the end of this file for the license"""
|
||||||
|
|
||||||
|
license = """
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Copyright 2025 Francesco Cozzuto
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////
|
||||||
"""
|
"""
|
||||||
|
|
||||||
header = Amalgamator()
|
header = Amalgamator()
|
||||||
header.append_text("#ifndef HTTP_AMALGAMATION\n")
|
|
||||||
header.append_text("#define HTTP_AMALGAMATION\n")
|
|
||||||
header.append_text(desc)
|
header.append_text(desc)
|
||||||
|
header.append_file("src/includes.h")
|
||||||
header.append_file("src/basic.h")
|
header.append_file("src/basic.h")
|
||||||
header.append_file("src/parse.h")
|
header.append_file("src/secure_context.h")
|
||||||
header.append_file("src/engine.h")
|
header.append_file("src/socket.h")
|
||||||
|
header.append_file("src/thread.h")
|
||||||
|
header.append_file("src/byte_queue.h")
|
||||||
header.append_file("src/cert.h")
|
header.append_file("src/cert.h")
|
||||||
header.append_file("src/client.h")
|
header.append_file("src/client.h")
|
||||||
|
header.append_file("src/parse.h")
|
||||||
header.append_file("src/server.h")
|
header.append_file("src/server.h")
|
||||||
header.append_text("#endif // HTTP_AMALGAMATION\n")
|
header.append_text(license)
|
||||||
header.save("chttp.h")
|
header.save("chttp.h")
|
||||||
|
|
||||||
source = Amalgamator()
|
source = Amalgamator()
|
||||||
|
source.append_text(desc)
|
||||||
source.append_text("#ifndef HTTP_NOINCLUDE\n")
|
source.append_text("\n")
|
||||||
source.append_text("#include \"chttp.h\"\n")
|
source.append_text("#ifndef HTTP_DONT_INCLUDE\n")
|
||||||
|
source.append_text('#include "chttp.h"\n')
|
||||||
source.append_text("#endif\n")
|
source.append_text("#endif\n")
|
||||||
|
|
||||||
source.append_file("src/sec.h")
|
|
||||||
source.append_file("src/socket_raw.h")
|
|
||||||
source.append_file("src/socket.h")
|
|
||||||
source.append_file("src/socket_pool.h")
|
|
||||||
source.append_file("src/basic.c")
|
source.append_file("src/basic.c")
|
||||||
source.append_file("src/parse.c")
|
source.append_file("src/secure_context.c")
|
||||||
source.append_file("src/engine.c")
|
|
||||||
source.append_file("src/cert.c")
|
|
||||||
source.append_file("src/sec.c")
|
|
||||||
source.append_file("src/socket_raw.c")
|
|
||||||
source.append_file("src/socket.c")
|
source.append_file("src/socket.c")
|
||||||
source.append_file("src/socket_pool.c")
|
source.append_file("src/thread.c")
|
||||||
|
source.append_file("src/byte_queue.c")
|
||||||
|
source.append_file("src/cert.c")
|
||||||
source.append_file("src/client.c")
|
source.append_file("src/client.c")
|
||||||
|
source.append_file("src/parse.c")
|
||||||
source.append_file("src/server.c")
|
source.append_file("src/server.c")
|
||||||
|
header.append_text(license)
|
||||||
source.save("chttp.c")
|
source.save("chttp.c")
|
||||||
|
|||||||
+1
-7
@@ -1,9 +1,3 @@
|
|||||||
#include <stddef.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#ifndef HTTP_AMALGAMATION
|
|
||||||
#include "basic.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
bool http_streq(HTTP_String s1, HTTP_String s2)
|
bool http_streq(HTTP_String s1, HTTP_String s2)
|
||||||
{
|
{
|
||||||
@@ -100,4 +94,4 @@ void print_bytes(HTTP_String prefix, HTTP_String src)
|
|||||||
cur++;
|
cur++;
|
||||||
}
|
}
|
||||||
putc('\n', stream);
|
putc('\n', stream);
|
||||||
}
|
}
|
||||||
|
|||||||
-31
@@ -1,7 +1,3 @@
|
|||||||
#ifndef CHTTP_BASIC_INCLUDED
|
|
||||||
#define CHTTP_BASIC_INCLUDED
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
// String type used throughout cHTTP.
|
// String type used throughout cHTTP.
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@@ -52,30 +48,3 @@ void print_bytes(HTTP_String prefix, HTTP_String src);
|
|||||||
|
|
||||||
// TODO: comment
|
// TODO: comment
|
||||||
#define HTTP_UNPACK(X) (X).len, (X).ptr
|
#define HTTP_UNPACK(X) (X).len, (X).ptr
|
||||||
|
|
||||||
// Macro used to make invariants of the code more explicit.
|
|
||||||
//
|
|
||||||
// Say you have some function that operates on two integers
|
|
||||||
// and that by design their sum is always 100. This macro is
|
|
||||||
// useful to make that explicit:
|
|
||||||
//
|
|
||||||
// void func(int a, int b)
|
|
||||||
// {
|
|
||||||
// HTTP_ASSERT(a + b == 100);
|
|
||||||
// ...
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Assertions are about documentation, *not* error management.
|
|
||||||
//
|
|
||||||
// In non-release builds (where NDEBUG is not defined) asserted
|
|
||||||
// expressions are evaluated and if not true, the program is halted.
|
|
||||||
// This is quite nice as they offer a way to document code in
|
|
||||||
// a way that can be checked at runtime, unlike regular comments
|
|
||||||
// like this one.
|
|
||||||
#ifdef NDEBUG
|
|
||||||
#define HTTP_ASSERT(X) ((void) 0)
|
|
||||||
#else
|
|
||||||
#define HTTP_ASSERT(X) {if (!(X)) { __builtin_trap(); }}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif // CHTTP_BASIC_INCLUDED
|
|
||||||
@@ -0,0 +1,258 @@
|
|||||||
|
|
||||||
|
void byte_queue_init(ByteQueue *queue, uint32_t limit)
|
||||||
|
{
|
||||||
|
queue->flags = 0;
|
||||||
|
queue->head = 0;
|
||||||
|
queue->size = 0;
|
||||||
|
queue->used = 0;
|
||||||
|
queue->curs = 0;
|
||||||
|
queue->limit = limit;
|
||||||
|
queue->data = NULL;
|
||||||
|
queue->read_target = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deinitialize the queue
|
||||||
|
void byte_queue_free(ByteQueue *queue)
|
||||||
|
{
|
||||||
|
if (queue->read_target) {
|
||||||
|
if (queue->read_target != queue->data)
|
||||||
|
free(queue->read_target);
|
||||||
|
queue->read_target = NULL;
|
||||||
|
queue->read_target_size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(queue->data);
|
||||||
|
queue->data = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int byte_queue_error(ByteQueue *queue)
|
||||||
|
{
|
||||||
|
return queue->flags & BYTE_QUEUE_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
int byte_queue_empty(ByteQueue *queue)
|
||||||
|
{
|
||||||
|
return queue->used == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int byte_queue_full(ByteQueue *queue)
|
||||||
|
{
|
||||||
|
return queue->used == queue->limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteView byte_queue_read_buf(ByteQueue *queue)
|
||||||
|
{
|
||||||
|
if (queue->flags & BYTE_QUEUE_ERROR)
|
||||||
|
return (ByteView) {NULL, 0};
|
||||||
|
|
||||||
|
assert((queue->flags & BYTE_QUEUE_READ) == 0);
|
||||||
|
queue->flags |= BYTE_QUEUE_READ;
|
||||||
|
queue->read_target = queue->data;
|
||||||
|
queue->read_target_size = queue->size;
|
||||||
|
|
||||||
|
if (queue->data == NULL)
|
||||||
|
return (ByteView) {NULL, 0};
|
||||||
|
|
||||||
|
return (ByteView) { queue->data + queue->head, queue->used };
|
||||||
|
}
|
||||||
|
|
||||||
|
void byte_queue_read_ack(ByteQueue *queue, uint32_t num)
|
||||||
|
{
|
||||||
|
if (queue->flags & BYTE_QUEUE_ERROR)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if ((queue->flags & BYTE_QUEUE_READ) == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
queue->flags &= ~BYTE_QUEUE_READ;
|
||||||
|
|
||||||
|
assert((uint32_t) num <= queue->used);
|
||||||
|
queue->head += (uint32_t) num;
|
||||||
|
queue->used -= (uint32_t) num;
|
||||||
|
queue->curs += (uint32_t) num;
|
||||||
|
|
||||||
|
if (queue->read_target) {
|
||||||
|
if (queue->read_target != queue->data)
|
||||||
|
free(queue->read_target);
|
||||||
|
queue->read_target = NULL;
|
||||||
|
queue->read_target_size = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteView byte_queue_write_buf(ByteQueue *queue)
|
||||||
|
{
|
||||||
|
if ((queue->flags & BYTE_QUEUE_ERROR) || queue->data == NULL)
|
||||||
|
return (ByteView) {NULL, 0};
|
||||||
|
|
||||||
|
assert((queue->flags & BYTE_QUEUE_WRITE) == 0);
|
||||||
|
queue->flags |= BYTE_QUEUE_WRITE;
|
||||||
|
|
||||||
|
return (ByteView) {
|
||||||
|
queue->data + (queue->head + queue->used),
|
||||||
|
queue->size - (queue->head + queue->used),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void byte_queue_write_ack(ByteQueue *queue, uint32_t num)
|
||||||
|
{
|
||||||
|
if (queue->flags & BYTE_QUEUE_ERROR)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if ((queue->flags & BYTE_QUEUE_WRITE) == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
queue->flags &= ~BYTE_QUEUE_WRITE;
|
||||||
|
queue->used += num;
|
||||||
|
}
|
||||||
|
|
||||||
|
int byte_queue_write_setmincap(ByteQueue *queue, uint32_t mincap)
|
||||||
|
{
|
||||||
|
// Sticky error
|
||||||
|
if (queue->flags & BYTE_QUEUE_ERROR)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
// In general, the queue's contents look like this:
|
||||||
|
//
|
||||||
|
// size
|
||||||
|
// v
|
||||||
|
// [___xxxxxxxxxxxx________]
|
||||||
|
// ^ ^ ^
|
||||||
|
// 0 head head + used
|
||||||
|
//
|
||||||
|
// This function needs to make sure that at least [mincap]
|
||||||
|
// bytes are available on the right side of the content.
|
||||||
|
//
|
||||||
|
// We have 3 cases:
|
||||||
|
//
|
||||||
|
// 1) If there is enough memory already, this function doesn't
|
||||||
|
// need to do anything.
|
||||||
|
//
|
||||||
|
// 2) If there isn't enough memory on the right but there is
|
||||||
|
// enough free memory if we cound the left unused region,
|
||||||
|
// then the content is moved back to the
|
||||||
|
// start of the buffer.
|
||||||
|
//
|
||||||
|
// 3) If there isn't enough memory considering both sides, this
|
||||||
|
// function needs to allocate a new buffer.
|
||||||
|
//
|
||||||
|
// If there are pending read or write operations, the application
|
||||||
|
// is holding pointers to the buffer, so we need to make sure
|
||||||
|
// to not invalidate them. The only real problem is pending reads
|
||||||
|
// since this function can only be called before starting a write
|
||||||
|
// opearation.
|
||||||
|
//
|
||||||
|
// To avoid invalidating the read pointer when we allocate a new
|
||||||
|
// buffer, we don't free the old buffer. Instead, we store the
|
||||||
|
// pointer in the "old" field so that the read ack function can
|
||||||
|
// free it.
|
||||||
|
//
|
||||||
|
// To avoid invalidating the pointer when we are moving back the
|
||||||
|
// content since there is enough memory at the start of the buffer,
|
||||||
|
// we just avoid that. Even if there is enough memory considering
|
||||||
|
// left and right free regions, we allocate a new buffer.
|
||||||
|
|
||||||
|
assert((queue->flags & BYTE_QUEUE_WRITE) == 0);
|
||||||
|
|
||||||
|
uint32_t total_free_space = queue->size - queue->used;
|
||||||
|
uint32_t free_space_after_data = queue->size - queue->used - queue->head;
|
||||||
|
|
||||||
|
int moved = 0;
|
||||||
|
if (free_space_after_data < mincap) {
|
||||||
|
|
||||||
|
if (total_free_space < mincap || (queue->read_target == queue->data)) {
|
||||||
|
// Resize required
|
||||||
|
|
||||||
|
if (queue->used + mincap > queue->limit) {
|
||||||
|
queue->flags |= BYTE_QUEUE_ERROR;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t size;
|
||||||
|
if (queue->size > UINT32_MAX / 2)
|
||||||
|
size = UINT32_MAX;
|
||||||
|
else
|
||||||
|
size = 2 * queue->size;
|
||||||
|
|
||||||
|
if (size < queue->used + mincap)
|
||||||
|
size = queue->used + mincap;
|
||||||
|
|
||||||
|
if (size > queue->limit)
|
||||||
|
size = queue->limit;
|
||||||
|
|
||||||
|
uint8_t *data = malloc(size);
|
||||||
|
if (!data) {
|
||||||
|
queue->flags |= BYTE_QUEUE_ERROR;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (queue->used > 0)
|
||||||
|
memcpy(data, queue->data + queue->head, queue->used);
|
||||||
|
|
||||||
|
if (queue->read_target != queue->data)
|
||||||
|
free(queue->data);
|
||||||
|
|
||||||
|
queue->data = data;
|
||||||
|
queue->head = 0;
|
||||||
|
queue->size = size;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Move required
|
||||||
|
memmove(queue->data, queue->data + queue->head, queue->used);
|
||||||
|
queue->head = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
moved = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return moved;
|
||||||
|
}
|
||||||
|
|
||||||
|
void byte_queue_write(ByteQueue *queue, void *ptr, uint32_t len)
|
||||||
|
{
|
||||||
|
byte_queue_write_setmincap(queue, len);
|
||||||
|
ByteView dst = byte_queue_write_buf(queue);
|
||||||
|
if (dst.ptr) {
|
||||||
|
memcpy(dst.ptr, ptr, len);
|
||||||
|
byte_queue_write_ack(queue, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteQueueOffset byte_queue_offset(ByteQueue *queue)
|
||||||
|
{
|
||||||
|
if (queue->flags & BYTE_QUEUE_ERROR)
|
||||||
|
return (ByteQueueOffset) { 0 };
|
||||||
|
return (ByteQueueOffset) { queue->curs + queue->used };
|
||||||
|
}
|
||||||
|
|
||||||
|
void byte_queue_patch(ByteQueue *queue, ByteQueueOffset off,
|
||||||
|
void *src, uint32_t len)
|
||||||
|
{
|
||||||
|
if (queue->flags & BYTE_QUEUE_ERROR)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Check that the offset is in range
|
||||||
|
assert(off >= queue->curs && off - queue->curs < queue->used);
|
||||||
|
|
||||||
|
// Check that the length is in range
|
||||||
|
assert(len <= queue->used - (off - queue->curs));
|
||||||
|
|
||||||
|
// Perform the patch
|
||||||
|
uint8_t *dst = queue->data + queue->head + (off - queue->curs);
|
||||||
|
memcpy(dst, src, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t byte_queue_size_from_offset(ByteQueue *queue, ByteQueueOffset off)
|
||||||
|
{
|
||||||
|
return queue->curs + queue->used - off;
|
||||||
|
}
|
||||||
|
|
||||||
|
void byte_queue_remove_from_offset(ByteQueue *queue, ByteQueueOffset offset)
|
||||||
|
{
|
||||||
|
if (queue->flags & BYTE_QUEUE_ERROR)
|
||||||
|
return;
|
||||||
|
|
||||||
|
uint64_t num = (queue->curs + queue->used) - offset;
|
||||||
|
assert(num <= queue->used);
|
||||||
|
|
||||||
|
queue->used -= num;
|
||||||
|
}
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
// This is the implementation of a byte queue useful
|
||||||
|
// for systems that need to process engs of bytes.
|
||||||
|
//
|
||||||
|
// It features sticky errors, a zero-copy interface,
|
||||||
|
// and a safe mechanism to patch previously written
|
||||||
|
// bytes.
|
||||||
|
//
|
||||||
|
// Only up to 4GB of data can be stored at once.
|
||||||
|
|
||||||
|
// Internal use only
|
||||||
|
enum {
|
||||||
|
BYTE_QUEUE_ERROR = 1 << 0,
|
||||||
|
BYTE_QUEUE_READ = 1 << 1,
|
||||||
|
BYTE_QUEUE_WRITE = 1 << 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t *ptr;
|
||||||
|
size_t len;
|
||||||
|
} ByteView;
|
||||||
|
|
||||||
|
// Fields are for internal use only
|
||||||
|
typedef struct {
|
||||||
|
uint64_t curs;
|
||||||
|
uint8_t* data;
|
||||||
|
uint32_t head;
|
||||||
|
uint32_t size;
|
||||||
|
uint32_t used;
|
||||||
|
uint32_t limit;
|
||||||
|
uint8_t* read_target;
|
||||||
|
uint32_t read_target_size;
|
||||||
|
int flags;
|
||||||
|
} ByteQueue;
|
||||||
|
|
||||||
|
// Represents an offset inside the queue relative
|
||||||
|
// to the first byte ever appended to the queue,
|
||||||
|
// therefore consuming bytes from the queue does
|
||||||
|
// not invalidate this type of offset.
|
||||||
|
typedef uint64_t ByteQueueOffset;
|
||||||
|
|
||||||
|
// Initialize the queue with a given capacity limit.
|
||||||
|
// This is just a soft limit. The queue will allocate
|
||||||
|
// dynamically as needed up to this limit and won't
|
||||||
|
// grow further. When the limit is reached, http_queue_full
|
||||||
|
// returns true.
|
||||||
|
void byte_queue_init(ByteQueue *queue, uint32_t limit);
|
||||||
|
|
||||||
|
// Free resources associated to this queue
|
||||||
|
void byte_queue_free(ByteQueue *queue);
|
||||||
|
|
||||||
|
// Check whether an error occurred inside the queue
|
||||||
|
int byte_queue_error(ByteQueue *queue);
|
||||||
|
|
||||||
|
// Returns 1 if the queue has no bytes inside it,
|
||||||
|
// or 0 otherwise.
|
||||||
|
int byte_queue_empty(ByteQueue *queue);
|
||||||
|
|
||||||
|
// Returns 1 if the queue reached its limit, or 0
|
||||||
|
// otherwise.
|
||||||
|
int byte_queue_full(ByteQueue *queue);
|
||||||
|
|
||||||
|
// These two functions are to be used together.
|
||||||
|
// read_buf returns a view into the queue of the
|
||||||
|
// bytes that can be read from it. The caller can
|
||||||
|
// decide how many of those bytes can be removed
|
||||||
|
// by passing the count to the read_ack function.
|
||||||
|
// If an error occurred inside the queue, this
|
||||||
|
// function returns an empty view.
|
||||||
|
//
|
||||||
|
// Note that the calls to read_buf and read_ack
|
||||||
|
// may be far apart. Other operations won't interfere
|
||||||
|
// with the read. The only rule is you can't call
|
||||||
|
// read_buf multiple times before calling read_ack.
|
||||||
|
ByteView byte_queue_read_buf(ByteQueue *queue);
|
||||||
|
void byte_queue_read_ack(ByteQueue *queue, uint32_t num);
|
||||||
|
|
||||||
|
// Similar to the read_buf/read_ack functions,
|
||||||
|
// but write_buf returns a view of the unused
|
||||||
|
// memory inside the queue, and write_ack is
|
||||||
|
// used to tell the queue how many bytes were
|
||||||
|
// written into it. Note that to ensure there
|
||||||
|
// is a minimum amount of free space in the queue,
|
||||||
|
// the user needs to call byte_queue_setmincap.
|
||||||
|
// If an error occurred inside the queue, this
|
||||||
|
// function returns an empty view.
|
||||||
|
//
|
||||||
|
// Note that the calls to write_buf and write_ack
|
||||||
|
// may be far apart. Other operations won't interfere
|
||||||
|
// with the write (except for other byte_queue_write_*
|
||||||
|
// functions). The only rule is you can't call
|
||||||
|
// write_buf multiple times before calling write_ack.
|
||||||
|
ByteView byte_queue_write_buf(ByteQueue *queue);
|
||||||
|
void byte_queue_write_ack(ByteQueue *queue, uint32_t num);
|
||||||
|
|
||||||
|
// Sets the minimum capacity for the next write
|
||||||
|
// operation and returns 1 if the content of the
|
||||||
|
// queue was moved, else 0 is returned.
|
||||||
|
//
|
||||||
|
// You must not call this function while a write
|
||||||
|
// is pending. In other words, you must do this:
|
||||||
|
//
|
||||||
|
// byte_queue_write_setmincap(queue, mincap);
|
||||||
|
// dst = byte_queue_write_buf(queue, &cap);
|
||||||
|
// ...
|
||||||
|
// byte_queue_write_ack(num);
|
||||||
|
//
|
||||||
|
// And NOT this:
|
||||||
|
//
|
||||||
|
// dst = byte_queue_write_buf(queue);
|
||||||
|
// byte_queue_write_setmincap(queue, mincap); <-- BAD
|
||||||
|
// ...
|
||||||
|
// byte_queue_write_ack(num);
|
||||||
|
//
|
||||||
|
int byte_queue_write_setmincap(ByteQueue *queue, uint32_t mincap);
|
||||||
|
|
||||||
|
// Write some bytes to the queue. This is a
|
||||||
|
// short hand for write_buf/memcpy/write_ack
|
||||||
|
void byte_queue_write(ByteQueue *queue, void *ptr, uint32_t len);
|
||||||
|
|
||||||
|
// Returns the current offset inside the queue
|
||||||
|
ByteQueueOffset byte_queue_offset(ByteQueue *queue);
|
||||||
|
|
||||||
|
// Writes some bytes at the specified offset. It's
|
||||||
|
// the responsibility of the user to make sure that
|
||||||
|
// the offset still refers to content inside the queue.
|
||||||
|
void byte_queue_patch(ByteQueue *queue, ByteQueueOffset off, void *src, uint32_t len);
|
||||||
|
|
||||||
|
// Returns the number of bytes from the given offset
|
||||||
|
// to the end of the queue.
|
||||||
|
uint32_t byte_queue_size_from_offset(ByteQueue *queue, ByteQueueOffset off);
|
||||||
|
|
||||||
|
// Removes all bytes from the given offset to the the
|
||||||
|
// end of the queue.
|
||||||
|
void byte_queue_remove_from_offset(ByteQueue *queue, ByteQueueOffset offset);
|
||||||
+3
-19
@@ -1,19 +1,3 @@
|
|||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#ifdef HTTPS_ENABLED
|
|
||||||
#include <openssl/pem.h>
|
|
||||||
#include <openssl/conf.h>
|
|
||||||
#include <openssl/x509v3.h>
|
|
||||||
#include <openssl/rsa.h>
|
|
||||||
#include <openssl/evp.h>
|
|
||||||
#include <openssl/bn.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef HTTP_AMALGAMATION
|
|
||||||
#include "cert.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef HTTPS_ENABLED
|
#ifdef HTTPS_ENABLED
|
||||||
|
|
||||||
@@ -51,10 +35,10 @@ static X509 *create_certificate(EVP_PKEY *pkey, HTTP_String C, HTTP_String O, HT
|
|||||||
|
|
||||||
// Set version (version 3)
|
// Set version (version 3)
|
||||||
X509_set_version(x509, 2);
|
X509_set_version(x509, 2);
|
||||||
|
|
||||||
// Set serial number
|
// Set serial number
|
||||||
ASN1_INTEGER_set(X509_get_serialNumber(x509), 1);
|
ASN1_INTEGER_set(X509_get_serialNumber(x509), 1);
|
||||||
|
|
||||||
// Set validity period
|
// Set validity period
|
||||||
X509_gmtime_adj(X509_get_notBefore(x509), 0);
|
X509_gmtime_adj(X509_get_notBefore(x509), 0);
|
||||||
X509_gmtime_adj(X509_get_notAfter(x509), 31536000L * days); // days * seconds_per_year
|
X509_gmtime_adj(X509_get_notAfter(x509), 31536000L * days); // days * seconds_per_year
|
||||||
@@ -166,4 +150,4 @@ int http_create_test_certificate(HTTP_String C, HTTP_String O, HTTP_String CN,
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,10 +1,3 @@
|
|||||||
#ifndef CERT_INCLUDED
|
|
||||||
#define CERT_INCLUDED
|
|
||||||
|
|
||||||
#ifndef HTTP_AMALGAMATION
|
|
||||||
#include "basic.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// This is an utility to create self-signed certificates
|
// This is an utility to create self-signed certificates
|
||||||
// useful when testing HTTPS servers locally. This is only
|
// useful when testing HTTPS servers locally. This is only
|
||||||
// meant to be used by people starting out with a library
|
// meant to be used by people starting out with a library
|
||||||
@@ -21,5 +14,3 @@
|
|||||||
// key file with the key used to sign the certificate.
|
// key file with the key used to sign the certificate.
|
||||||
int http_create_test_certificate(HTTP_String C, HTTP_String O, HTTP_String CN,
|
int http_create_test_certificate(HTTP_String C, HTTP_String O, HTTP_String CN,
|
||||||
HTTP_String cert_file, HTTP_String key_file);
|
HTTP_String cert_file, HTTP_String key_file);
|
||||||
|
|
||||||
#endif // CERT_INCLUDED
|
|
||||||
+67
-408
@@ -1,453 +1,112 @@
|
|||||||
#include <stdint.h>
|
|
||||||
#include <assert.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
int http_client_init(HTTP_Client *client)
|
||||||
#define WIN32_LEAN_AND_MEAN
|
|
||||||
#include <winsock2.h>
|
|
||||||
#define POLL WSAPoll
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef __linux__
|
|
||||||
#include <poll.h>
|
|
||||||
#define POLL poll
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef HTTP_AMALGAMATION
|
|
||||||
#include "client.h"
|
|
||||||
#include "engine.h"
|
|
||||||
#include "socket_pool.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define CLIENT_MAX_CONNS 256
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
CLIENT_CONNECTION_FREE,
|
|
||||||
CLIENT_CONNECTION_INIT,
|
|
||||||
CLIENT_CONNECTION_INIT_ERROR,
|
|
||||||
CLIENT_CONNECTION_WAIT,
|
|
||||||
CLIENT_CONNECTION_DONE,
|
|
||||||
} ClientConnectionState;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
ClientConnectionState state;
|
|
||||||
uint16_t gen;
|
|
||||||
SocketHandle sock;
|
|
||||||
HTTP_Engine eng;
|
|
||||||
bool trace;
|
|
||||||
void* user_data;
|
|
||||||
} ClientConnection;
|
|
||||||
|
|
||||||
struct HTTP_Client {
|
|
||||||
|
|
||||||
SocketPool *socket_pool;
|
|
||||||
|
|
||||||
int num_conns;
|
|
||||||
ClientConnection conns[CLIENT_MAX_CONNS];
|
|
||||||
|
|
||||||
int ready_head;
|
|
||||||
int ready_count;
|
|
||||||
int ready[CLIENT_MAX_CONNS];
|
|
||||||
};
|
|
||||||
|
|
||||||
int http_global_init(void)
|
|
||||||
{
|
{
|
||||||
int ret = socket_pool_global_init();
|
client->num_conns = 0;
|
||||||
if (ret < 0)
|
for (int i = 0; i < HTTP_CLIENT_CAPACITY; i++)
|
||||||
|
client->conns[i].state = HTTP_CLIENT_CONN_FREE;
|
||||||
|
|
||||||
|
client->num_ready = 0;
|
||||||
|
client->ready_head = 0;
|
||||||
|
|
||||||
|
if (socket_manager_init(&client->sockets,
|
||||||
|
client->socket_pool, HTTP_CLIENT_CAPACITY) < 0)
|
||||||
return -1;
|
return -1;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void http_global_free(void)
|
|
||||||
{
|
|
||||||
socket_pool_global_free();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rename the memory function
|
|
||||||
static void* client_memfunc(HTTP_MemoryFuncTag tag, void *ptr, int len, void *data) {
|
|
||||||
(void)data;
|
|
||||||
switch (tag) {
|
|
||||||
case HTTP_MEMFUNC_MALLOC:
|
|
||||||
return malloc(len);
|
|
||||||
case HTTP_MEMFUNC_FREE:
|
|
||||||
free(ptr);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
HTTP_Client *http_client_init(void)
|
|
||||||
{
|
|
||||||
HTTP_Client *client = malloc(sizeof(HTTP_Client));
|
|
||||||
if (client == NULL)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
int max_socks = 100;
|
|
||||||
SocketPool *socket_pool = socket_pool_init(HTTP_STR(""), 0, 0, max_socks, false, 0, HTTP_STR(""), HTTP_STR(""));
|
|
||||||
if (socket_pool == NULL) {
|
|
||||||
free(client);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
client->socket_pool = socket_pool;
|
|
||||||
|
|
||||||
for (int i = 0; i < CLIENT_MAX_CONNS; i++) {
|
|
||||||
client->conns[i].state = CLIENT_CONNECTION_FREE;
|
|
||||||
client->conns[i].gen = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
client->num_conns = 0;
|
|
||||||
client->ready_head = 0;
|
|
||||||
client->ready_count = 0;
|
|
||||||
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
void http_client_free(HTTP_Client *client)
|
void http_client_free(HTTP_Client *client)
|
||||||
{
|
{
|
||||||
for (int i = 0, j = 0; j < client->num_conns; i++) {
|
socket_manager_free(&client->sockets);
|
||||||
|
|
||||||
if (client->conns[i].state == CLIENT_CONNECTION_FREE)
|
for (int i = 0, j = 0; j < client->num_conns; i++) {
|
||||||
|
HTTP_ClientConn *conn = &client->conns[i];
|
||||||
|
if (conn->state == HTTP_CLIENT_CONN_FREE)
|
||||||
continue;
|
continue;
|
||||||
j++;
|
j++;
|
||||||
|
|
||||||
// TODO
|
http_client_conn_free(conn);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
socket_pool_free(client->socket_pool);
|
int http_client_wakeup(HTTP_Client *client)
|
||||||
free(client);
|
{
|
||||||
|
if (socket_manager_wakeup(&client->sockets) < 0)
|
||||||
|
return -1;
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int http_client_get_builder(HTTP_Client *client, HTTP_RequestBuilder *builder)
|
int http_client_get_builder(HTTP_Client *client, HTTP_RequestBuilder *builder)
|
||||||
{
|
{
|
||||||
if (client->num_conns == CLIENT_MAX_CONNS)
|
// TODO
|
||||||
return -1;
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
while (client->conns[i].state != CLIENT_CONNECTION_FREE)
|
|
||||||
i++;
|
|
||||||
|
|
||||||
client->conns[i].sock = -1;
|
|
||||||
client->conns[i].user_data = NULL;
|
|
||||||
client->conns[i].trace = false;
|
|
||||||
client->conns[i].state = CLIENT_CONNECTION_INIT;
|
|
||||||
http_engine_init(&client->conns[i].eng, 1, client_memfunc, NULL);
|
|
||||||
|
|
||||||
client->num_conns++;
|
|
||||||
|
|
||||||
*builder = (HTTP_RequestBuilder) { client, i, client->conns[i].gen };
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int http_client_wait(HTTP_Client *client, HTTP_Response **result, void **user_data)
|
void http_request_builder_line(HTTP_RequestBuilder builder, xxxx)
|
||||||
{
|
{
|
||||||
while (client->ready_count == 0) {
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
SocketEvent event = socket_pool_wait(client->socket_pool);
|
void http_request_builder_header(HTTP_RequestBuilder builder, String str)
|
||||||
switch (event.type) {
|
{
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
case SOCKET_EVENT_DIED:
|
void http_request_builder_body(HTTP_RequestBuilder builder, String str)
|
||||||
{
|
{
|
||||||
ClientConnection *conn = event.user_data;
|
// TODO
|
||||||
conn->state = CLIENT_CONNECTION_DONE;
|
}
|
||||||
|
|
||||||
int tail = (client->ready_head + client->ready_count) % CLIENT_MAX_CONNS;
|
int http_request_builder_send(HTTP_RequestBuilder builder)
|
||||||
client->ready[tail] = conn - client->conns;
|
{
|
||||||
client->ready_count++;
|
// TODO
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
|
|
||||||
case SOCKET_EVENT_READY:
|
int http_client_register_events(HTTP_Client *client,
|
||||||
{
|
struct pollfd *polled, int max_polled)
|
||||||
ClientConnection *conn = event.user_data;
|
{
|
||||||
|
return socket_manager_register_events(
|
||||||
|
&client->sockets, polled, max_polled);
|
||||||
|
}
|
||||||
|
|
||||||
if (conn->sock == -1)
|
int http_client_process_events(HTTP_Client *client,
|
||||||
conn->sock = event.handle;
|
struct pollfd *polled, int num_polled)
|
||||||
|
{
|
||||||
|
SocketEvent events[HTTP_CLIENT_CAPACITY];
|
||||||
|
int num_events = socket_manager_translate_events(
|
||||||
|
&client->sockets, polled, num_polled);
|
||||||
|
|
||||||
HTTP_EngineState engine_state;
|
for (int i = 0; i < num_events; i++) {
|
||||||
engine_state = http_engine_state(&conn->eng);
|
|
||||||
|
|
||||||
if (engine_state == HTTP_ENGINE_STATE_CLIENT_RECV_BUF) {
|
if (events[i].type == SOCKET_EVENT_DISCONNECT) {
|
||||||
int len;
|
|
||||||
char *buf;
|
|
||||||
buf = http_engine_recvbuf(&conn->eng, &len);
|
|
||||||
if (buf) {
|
|
||||||
int ret = socket_pool_read(client->socket_pool, conn->sock, buf, len);
|
|
||||||
if (conn->trace)
|
|
||||||
print_bytes(HTTP_STR(">> "), (HTTP_String) { buf, ret });
|
|
||||||
http_engine_recvack(&conn->eng, ret);
|
|
||||||
}
|
|
||||||
} else if (engine_state == HTTP_ENGINE_STATE_CLIENT_SEND_BUF) {
|
|
||||||
int len;
|
|
||||||
char *buf;
|
|
||||||
buf = http_engine_sendbuf(&conn->eng, &len);
|
|
||||||
if (buf) {
|
|
||||||
int ret = socket_pool_write(client->socket_pool, conn->sock, buf, len);
|
|
||||||
if (conn->trace)
|
|
||||||
print_bytes(HTTP_STR("<< "), (HTTP_String) { buf, ret });
|
|
||||||
http_engine_sendack(&conn->eng, ret);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
engine_state = http_engine_state(&conn->eng);
|
// TODO
|
||||||
|
|
||||||
if (engine_state == HTTP_ENGINE_STATE_CLIENT_CLOSED ||
|
} else if (events[i].type == SOCKET_EVENT_READY) {
|
||||||
engine_state == HTTP_ENGINE_STATE_CLIENT_READY)
|
|
||||||
socket_pool_close(client->socket_pool, conn->sock);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SOCKET_EVENT_ERROR:
|
// TODO
|
||||||
return -1;
|
|
||||||
|
|
||||||
case SOCKET_EVENT_SIGNAL:
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
int index = client->ready[client->ready_head];
|
// TODO
|
||||||
client->ready_head = (client->ready_head + 1) % CLIENT_MAX_CONNS;
|
|
||||||
client->ready_count--;
|
|
||||||
|
|
||||||
ClientConnection *conn = &client->conns[index];
|
|
||||||
|
|
||||||
HTTP_Response *result2 = http_engine_getres(&conn->eng);
|
|
||||||
|
|
||||||
if (result)
|
|
||||||
*result = result2;
|
|
||||||
|
|
||||||
if (user_data)
|
|
||||||
*user_data = conn->user_data;
|
|
||||||
|
|
||||||
if (result2 == NULL) {
|
|
||||||
http_engine_free(&conn->eng);
|
|
||||||
conn->state = CLIENT_CONNECTION_FREE;
|
|
||||||
client->num_conns--;
|
|
||||||
} else {
|
|
||||||
result2->context = client;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static ClientConnection *client_builder_to_conn(HTTP_RequestBuilder handle)
|
bool http_client_next_response(HTTP_Client *client,
|
||||||
|
HTTP_Response **response);
|
||||||
{
|
{
|
||||||
if (handle.data0 == NULL)
|
if (server->num_ready == 0)
|
||||||
return NULL;
|
return false;
|
||||||
|
|
||||||
HTTP_Client *client = handle.data0;
|
HTTP_ClientConn *conn = &client->conns[client->ready_head];
|
||||||
|
client->ready_head = (client->ready_head + 1) % HTTP_CLIENT_CAPACITY;
|
||||||
|
client->num_ready--;
|
||||||
|
|
||||||
if (handle.data1 >= CLIENT_MAX_CONNS)
|
assert(conn->state == HTTP_CLIENT_CONN_COMPLETE);
|
||||||
return NULL;
|
*response = &conn->response;
|
||||||
|
return true;
|
||||||
ClientConnection *conn = &client->conns[handle.data1];
|
|
||||||
|
|
||||||
if (handle.data2 != conn->gen)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
return conn;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void http_request_builder_user_data(HTTP_RequestBuilder builder, void *user_data)
|
void http_free_response(HTTP_Response *res)
|
||||||
{
|
{
|
||||||
ClientConnection *conn = client_builder_to_conn(builder);
|
// TODO
|
||||||
if (conn == NULL)
|
|
||||||
return;
|
|
||||||
if (conn->state != CLIENT_CONNECTION_INIT)
|
|
||||||
return;
|
|
||||||
|
|
||||||
conn->user_data = user_data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void http_request_builder_trace(HTTP_RequestBuilder builder, bool trace)
|
|
||||||
{
|
|
||||||
ClientConnection *conn = client_builder_to_conn(builder);
|
|
||||||
if (conn == NULL)
|
|
||||||
return;
|
|
||||||
if (conn->state != CLIENT_CONNECTION_INIT)
|
|
||||||
return;
|
|
||||||
|
|
||||||
conn->trace = trace;
|
|
||||||
}
|
|
||||||
|
|
||||||
void http_request_builder_line(HTTP_RequestBuilder builder, HTTP_Method method, HTTP_String url)
|
|
||||||
{
|
|
||||||
ClientConnection *conn = client_builder_to_conn(builder);
|
|
||||||
if (conn == NULL)
|
|
||||||
return;
|
|
||||||
if (conn->state != CLIENT_CONNECTION_INIT)
|
|
||||||
return;
|
|
||||||
|
|
||||||
HTTP_Client *client = builder.data0;
|
|
||||||
|
|
||||||
HTTP_URL parsed_url;
|
|
||||||
int ret = http_parse_url(url.ptr, url.len, &parsed_url);
|
|
||||||
if (ret != url.len) {
|
|
||||||
conn->state = CLIENT_CONNECTION_INIT_ERROR;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool secure = false;
|
|
||||||
if (http_streq(parsed_url.scheme, HTTP_STR("https"))) {
|
|
||||||
secure = true;
|
|
||||||
} else if (!http_streq(parsed_url.scheme, HTTP_STR("http"))) {
|
|
||||||
conn->state = CLIENT_CONNECTION_INIT_ERROR;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int port = parsed_url.authority.port;
|
|
||||||
if (port == 0) {
|
|
||||||
if (secure)
|
|
||||||
port = 443;
|
|
||||||
else
|
|
||||||
port = 80;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (parsed_url.authority.host.mode) {
|
|
||||||
case HTTP_HOST_MODE_IPV4: ret = socket_pool_connect_ipv4(client->socket_pool, secure, parsed_url.authority.host.ipv4, port, conn); break;
|
|
||||||
case HTTP_HOST_MODE_IPV6: ret = socket_pool_connect_ipv6(client->socket_pool, secure, parsed_url.authority.host.ipv6, port, conn); break;
|
|
||||||
case HTTP_HOST_MODE_NAME: ret = socket_pool_connect (client->socket_pool, secure, parsed_url.authority.host.name, port, conn); break;
|
|
||||||
case HTTP_HOST_MODE_VOID: ret = -1; return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ret < 0) {
|
|
||||||
conn->state = CLIENT_CONNECTION_INIT_ERROR;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
http_engine_url(&conn->eng, method, url, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void http_request_builder_header(HTTP_RequestBuilder handle, HTTP_String str)
|
|
||||||
{
|
|
||||||
ClientConnection *conn = client_builder_to_conn(handle);
|
|
||||||
if (conn == NULL)
|
|
||||||
return;
|
|
||||||
if (conn->state != CLIENT_CONNECTION_INIT)
|
|
||||||
return;
|
|
||||||
|
|
||||||
http_engine_header(&conn->eng, str);
|
|
||||||
}
|
|
||||||
|
|
||||||
void http_request_builder_body(HTTP_RequestBuilder handle, HTTP_String str)
|
|
||||||
{
|
|
||||||
ClientConnection *conn = client_builder_to_conn(handle);
|
|
||||||
if (conn == NULL)
|
|
||||||
return;
|
|
||||||
if (conn->state != CLIENT_CONNECTION_INIT)
|
|
||||||
return;
|
|
||||||
|
|
||||||
http_engine_body(&conn->eng, str);
|
|
||||||
}
|
|
||||||
|
|
||||||
void http_request_builder_submit(HTTP_RequestBuilder handle)
|
|
||||||
{
|
|
||||||
HTTP_Client *client = handle.data0;
|
|
||||||
ClientConnection *conn = client_builder_to_conn(handle);
|
|
||||||
if (conn == NULL)
|
|
||||||
return;
|
|
||||||
if (conn->state != CLIENT_CONNECTION_INIT &&
|
|
||||||
conn->state != CLIENT_CONNECTION_INIT_ERROR)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// TODO: invalidate the handle
|
|
||||||
|
|
||||||
if (conn->state == CLIENT_CONNECTION_INIT_ERROR) {
|
|
||||||
|
|
||||||
conn->state = CLIENT_CONNECTION_DONE;
|
|
||||||
|
|
||||||
int tail = (client->ready_head + client->ready_count) % CLIENT_MAX_CONNS;
|
|
||||||
client->ready[tail] = conn - client->conns;
|
|
||||||
client->ready_count++;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
http_engine_done(&conn->eng);
|
|
||||||
conn->state = CLIENT_CONNECTION_WAIT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void http_response_free(HTTP_Response *res)
|
|
||||||
{
|
|
||||||
if (res == NULL)
|
|
||||||
return;
|
|
||||||
|
|
||||||
HTTP_Client *client = res->context;
|
|
||||||
|
|
||||||
ClientConnection *conn = NULL;
|
|
||||||
for (int i = 0, j = 0; j < client->num_conns; i++) {
|
|
||||||
|
|
||||||
if (client->conns[i].state == CLIENT_CONNECTION_FREE)
|
|
||||||
continue;
|
|
||||||
j++;
|
|
||||||
|
|
||||||
if (client->conns[i].state != CLIENT_CONNECTION_DONE)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (http_engine_getres(&client->conns[i].eng) == res) {
|
|
||||||
conn = &client->conns[i];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HTTP_ASSERT(conn);
|
|
||||||
|
|
||||||
http_engine_free(&conn->eng);
|
|
||||||
conn->state = CLIENT_CONNECTION_FREE;
|
|
||||||
client->num_conns--;
|
|
||||||
}
|
|
||||||
|
|
||||||
static HTTP_Client *default_client___; // TODO: deinitialize the default client when http_global_free is called
|
|
||||||
|
|
||||||
static HTTP_Client *get_default_client(void)
|
|
||||||
{
|
|
||||||
if (default_client___ == NULL)
|
|
||||||
default_client___ = http_client_init();
|
|
||||||
return default_client___;
|
|
||||||
}
|
|
||||||
|
|
||||||
HTTP_Response *http_get(HTTP_String url, HTTP_String *headers, int num_headers)
|
|
||||||
{
|
|
||||||
HTTP_Client *client = get_default_client();
|
|
||||||
if (client == NULL)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
HTTP_RequestBuilder builder;
|
|
||||||
int ret = http_client_get_builder(client, &builder);
|
|
||||||
if (ret < 0)
|
|
||||||
return NULL;
|
|
||||||
http_request_builder_line(builder, HTTP_METHOD_GET, url);
|
|
||||||
for (int i = 0; i < num_headers; i++)
|
|
||||||
http_request_builder_header(builder, headers[i]);
|
|
||||||
http_request_builder_submit(builder);
|
|
||||||
|
|
||||||
HTTP_Response *res;
|
|
||||||
ret = http_client_wait(client, &res, NULL); // TODO: it's assumed there is only one request pending
|
|
||||||
if (ret < 0)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
HTTP_Response *http_post(HTTP_String url, HTTP_String *headers, int num_headers, HTTP_String body)
|
|
||||||
{
|
|
||||||
HTTP_Client *client = get_default_client();
|
|
||||||
if (client == NULL)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
HTTP_RequestBuilder builder;
|
|
||||||
int ret = http_client_get_builder(client, &builder);
|
|
||||||
if (ret < 0)
|
|
||||||
return NULL;
|
|
||||||
http_request_builder_line(builder, HTTP_METHOD_POST, url);
|
|
||||||
for (int i = 0; i < num_headers; i++)
|
|
||||||
http_request_builder_header(builder, headers[i]);
|
|
||||||
http_request_builder_body(builder, body);
|
|
||||||
http_request_builder_submit(builder);
|
|
||||||
|
|
||||||
HTTP_Response *res;
|
|
||||||
ret = http_client_wait(client, &res, NULL); // TODO: it's assumed there is only one request pending
|
|
||||||
if (ret < 0)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
+99
-79
@@ -1,98 +1,118 @@
|
|||||||
#ifndef CLIENT_INCLUDED
|
|
||||||
#define CLIENT_INCLUDED
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
#ifndef HTTP_CLIENT_CAPACITY
|
||||||
|
// The maximum ammount of requests that can be performed
|
||||||
#ifndef HTTP_AMALGAMATION
|
// in parallel.
|
||||||
#include "parse.h"
|
#define HTTP_CLIENT_CAPACITY (1<<7)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Initialize the global state of cHTTP.
|
typedef enum {
|
||||||
//
|
HTTP_CLIENT_CONN_FREE,
|
||||||
// cHTTP tries to avoid global state. What this function
|
HTTP_CLIENT_CONN_WAIT_LINE,
|
||||||
// does is call the global initialization functions of
|
HTTP_CLIENT_CONN_WAIT_HEADER,
|
||||||
// its dependencies (OpenSSL and Winsock)
|
HTTP_CLIENT_CONN_WAIT_BODY,
|
||||||
int http_global_init(void);
|
HTTP_CLIENT_CONN_FLUSHING,
|
||||||
|
HTTP_CLIENT_CONN_BUFFERING,
|
||||||
|
HTTP_CLIENT_CONN_COMPLETE,
|
||||||
|
} HTTP_ClientConnState;
|
||||||
|
|
||||||
// Free the global state of cHTTP.
|
// Fields of this struct are private
|
||||||
void http_global_free(void);
|
|
||||||
|
|
||||||
// Opaque type describing an "HTTP client". Any request
|
|
||||||
// that is started must always be associated to an HTTP
|
|
||||||
// client object.
|
|
||||||
typedef struct HTTP_Client HTTP_Client;
|
|
||||||
|
|
||||||
// Handle for a pending request. This should be considered
|
|
||||||
// opaque. Don't read or modify its fields!
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
void *data0;
|
HTTP_ClientConnState state;
|
||||||
int data1;
|
ByteQueue input;
|
||||||
int data2;
|
ByteQueue output;
|
||||||
} HTTP_RequestBuilder;
|
} HTTP_ClientConn;
|
||||||
|
|
||||||
// Initialize a client object. If something goes wrong,
|
// Fields of this struct are private
|
||||||
// NULL is returned.
|
typedef struct {
|
||||||
HTTP_Client *http_client_init(void);
|
|
||||||
|
|
||||||
// Deinitialize a client object
|
// Array of connections. The counter contains the
|
||||||
|
// number of structs such that state=FREE.
|
||||||
|
int num_conns;
|
||||||
|
HTTP_ClientConn conns[HTTP_CLIENT_CAPACITY];
|
||||||
|
|
||||||
|
// Queue of indices referring to connections that
|
||||||
|
// are in the COMPLETE state.
|
||||||
|
int num_ready;
|
||||||
|
int ready_head;
|
||||||
|
int ready[HTTP_CLIENT_CAPACITY];
|
||||||
|
|
||||||
|
// Asynchronous TCP and TLS socket abstraction
|
||||||
|
SocketManager sockets;
|
||||||
|
|
||||||
|
// The server object doesn't interact with this
|
||||||
|
// field directly, it just initializes the socket
|
||||||
|
// manager with a pointer to it. This allows
|
||||||
|
// allocating the exact number of sockets we
|
||||||
|
// will need.
|
||||||
|
Socket socket_pool[HTTP_CLIENT_CAPACITY];
|
||||||
|
|
||||||
|
} HTTP_Client;
|
||||||
|
|
||||||
|
// Initialize an HTTP client object. This allows one to
|
||||||
|
// perform a number of requests in parallel.
|
||||||
|
int http_client_init(HTTP_Client *client);
|
||||||
|
|
||||||
|
// Release resources associated to a client object.
|
||||||
void http_client_free(HTTP_Client *client);
|
void http_client_free(HTTP_Client *client);
|
||||||
|
|
||||||
// Create a request object associated to the given client.
|
// When a thread is blocked waiting for client events,
|
||||||
// On success, 0 is returned and the handle is initialized.
|
// other threads can call this function to wake it up.
|
||||||
// On error, -1 is returned.
|
int http_client_wakeup(HTTP_Client *client);
|
||||||
int http_client_get_builder(HTTP_Client *client, HTTP_RequestBuilder *builder);
|
|
||||||
|
|
||||||
void http_request_builder_user_data(HTTP_RequestBuilder builder, void *user_data);
|
typedef struct {
|
||||||
|
HTTP_Client *client;
|
||||||
|
uint16_t index;
|
||||||
|
uint16_t gen;
|
||||||
|
} HTTP_RequestBuilder;
|
||||||
|
|
||||||
// Enable/disable I/O tracing for the specified request.
|
// Create a new request builder object. If the response
|
||||||
// This must be done when the request is in the initialization
|
// pointer is NULL, a brand new builder is created. If
|
||||||
// phase.
|
// response isn't NULL (and http_free_response wasn't
|
||||||
void http_request_builder_trace(HTTP_RequestBuilder builder, bool trace);
|
// called on it yet), the connection associated to that
|
||||||
|
// previous exchange is reused. Note that it's up to the
|
||||||
|
// user to make sure the requests are targeting the same
|
||||||
|
// host. Returns 0 on success, -1 on error.
|
||||||
|
int http_client_get_builder(HTTP_Client *client,
|
||||||
|
HTTP_Response *response, HTTP_RequestBuilder *builder);
|
||||||
|
|
||||||
// Set the method and URL of the specified request object.
|
// Set the URL of the current request. This is the first
|
||||||
// This must be the first thing you do after http_client_request
|
// function of the request builder that the user must call.
|
||||||
// is called (you may http_request_trace before, but nothing
|
void http_request_builder_url(HTTP_RequestBuilder builder, String url);
|
||||||
// else!)
|
|
||||||
void http_request_builder_line(HTTP_RequestBuilder builder, HTTP_Method method, HTTP_String url);
|
|
||||||
|
|
||||||
// Append a header to the specified request. You must call
|
// After the URL, the user may set zero or more headers.
|
||||||
// this after http_request_line and may do so multiple times.
|
void http_request_builder_header(HTTP_RequestBuilder builder, String str);
|
||||||
void http_request_builder_header(HTTP_RequestBuilder builder, HTTP_String str);
|
|
||||||
|
|
||||||
// Append some data to the request's body. You must call
|
// Append bytes to the request's body. You can call this
|
||||||
// this after either http_request_line or http_request_header.
|
// any amount of times, as long as it's after having set
|
||||||
void http_request_builder_body(HTTP_RequestBuilder builder, HTTP_String str);
|
// the URL.
|
||||||
|
void http_request_builder_body(HTTP_RequestBuilder builder, String str);
|
||||||
|
|
||||||
// Mark the initialization of the request as completed and
|
// Mark this request as complete. This invalidates the
|
||||||
// perform the request.
|
// builder.
|
||||||
void http_request_builder_submit(HTTP_RequestBuilder builder);
|
void http_request_builder_send(HTTP_RequestBuilder builder);
|
||||||
|
|
||||||
// Free resources associated to a request. This must be called
|
// List all low-level socket events the client is
|
||||||
// after the request has completed.
|
// waiting for such that the caller can call poll()
|
||||||
//
|
// with it.
|
||||||
// TODO: allow aborting pending requests
|
int http_client_register_events(HTTP_Client *client,
|
||||||
void http_response_free(HTTP_Response *res);
|
struct pollfd *polled, int max_polled);
|
||||||
|
|
||||||
// Wait for the completion of one request associated to
|
// The caller has waited for poll() to return and some
|
||||||
// the client. The handle of the resolved request is returned
|
// I/O events to be triggered, so now the HTTP client
|
||||||
// through the handle output parameter. If you're not
|
// can continue its buffering and flushing operations.
|
||||||
// interested in which request completed (like when you
|
int http_client_process_events(HTTP_Client *client,
|
||||||
// have only one pending request), you can pass NULL.
|
struct pollfd *polled, int num_polled);
|
||||||
//
|
|
||||||
// On error -1 is retutned, else 0 is returned and the
|
|
||||||
// handle is initialized.
|
|
||||||
//
|
|
||||||
// Note that calling this function when no requests are
|
|
||||||
// pending is considered an error.
|
|
||||||
int http_client_wait(HTTP_Client *client, HTTP_Response **res, void **user_data);
|
|
||||||
|
|
||||||
// TODO: comment
|
// After some I/O events were processes, some responses
|
||||||
HTTP_Response *http_get(HTTP_String url,
|
// may be availabe. This function returns one of the
|
||||||
HTTP_String *headers, int num_headers);
|
// buffered responses. If a request was available, true
|
||||||
|
// is returned. If no more are avaiable, false is returned.
|
||||||
|
// The returned response must either be freed using the
|
||||||
|
// http_free_response function or reused by passing it
|
||||||
|
// to http_client_get_builder.
|
||||||
|
bool http_client_next_response(HTTP_Client *client,
|
||||||
|
HTTP_Response **response);
|
||||||
|
|
||||||
// TODO: comment
|
// Free a response object. You can't access its fields
|
||||||
HTTP_Response *http_post(HTTP_String url,
|
// again after this.
|
||||||
HTTP_String *headers, int num_headers,
|
void http_free_response(HTTP_Response *res);
|
||||||
HTTP_String body);
|
|
||||||
|
|
||||||
#endif // CLIENT_INCLUDED
|
|
||||||
|
|||||||
-988
@@ -1,988 +0,0 @@
|
|||||||
#include <stdio.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdarg.h>
|
|
||||||
#include <assert.h> // TODO: remove some of these headers
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <limits.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#ifndef HTTP_AMALGAMATION
|
|
||||||
#include "basic.h"
|
|
||||||
#include "engine.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// This is the implementation of a byte queue useful
|
|
||||||
// for systems that need to process engs of bytes.
|
|
||||||
//
|
|
||||||
// It features sticky errors, a zero-copy interface,
|
|
||||||
// and a safe mechanism to patch previously written
|
|
||||||
// bytes.
|
|
||||||
//
|
|
||||||
// Only up to 4GB of data can be stored at once.
|
|
||||||
|
|
||||||
enum {
|
|
||||||
BYTE_QUEUE_ERROR = 1 << 0,
|
|
||||||
BYTE_QUEUE_READ = 1 << 1,
|
|
||||||
BYTE_QUEUE_WRITE = 1 << 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
static void*
|
|
||||||
callback_malloc(HTTP_ByteQueue *queue, int len)
|
|
||||||
{
|
|
||||||
return queue->memfunc(HTTP_MEMFUNC_MALLOC, NULL, len, queue->memfuncdata);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
callback_free(HTTP_ByteQueue *queue, void *ptr, int len)
|
|
||||||
{
|
|
||||||
queue->memfunc(HTTP_MEMFUNC_FREE, ptr, len, queue->memfuncdata);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the queue
|
|
||||||
static void
|
|
||||||
byte_queue_init(HTTP_ByteQueue *queue, unsigned int limit, HTTP_MemoryFunc memfunc, void *memfuncdata)
|
|
||||||
{
|
|
||||||
queue->flags = 0;
|
|
||||||
queue->head = 0;
|
|
||||||
queue->size = 0;
|
|
||||||
queue->used = 0;
|
|
||||||
queue->curs = 0;
|
|
||||||
queue->limit = limit;
|
|
||||||
queue->data = NULL;
|
|
||||||
queue->read_target = NULL;
|
|
||||||
queue->memfunc = memfunc;
|
|
||||||
queue->memfuncdata = memfuncdata;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deinitialize the queue
|
|
||||||
static void
|
|
||||||
byte_queue_free(HTTP_ByteQueue *queue)
|
|
||||||
{
|
|
||||||
if (queue->read_target) {
|
|
||||||
if (queue->read_target != queue->data)
|
|
||||||
callback_free(queue, queue->read_target, queue->read_target_size);
|
|
||||||
queue->read_target = NULL;
|
|
||||||
queue->read_target_size = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
callback_free(queue, queue->data, queue->size);
|
|
||||||
queue->data = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
byte_queue_error(HTTP_ByteQueue *queue)
|
|
||||||
{
|
|
||||||
return queue->flags & BYTE_QUEUE_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
byte_queue_empty(HTTP_ByteQueue *queue)
|
|
||||||
{
|
|
||||||
return queue->used == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start a read operation on the queue.
|
|
||||||
//
|
|
||||||
// This function returnes the pointer to the memory region containing the bytes
|
|
||||||
// to read. Callers can't read more than [*len] bytes from it. To complete the
|
|
||||||
// read, the [byte_queue_read_ack] function must be called with the number of
|
|
||||||
// bytes that were acknowledged by the caller.
|
|
||||||
//
|
|
||||||
// Note:
|
|
||||||
// - You can't have more than one pending read.
|
|
||||||
static char*
|
|
||||||
byte_queue_read_buf(HTTP_ByteQueue *queue, int *len)
|
|
||||||
{
|
|
||||||
if (queue->flags & BYTE_QUEUE_ERROR) {
|
|
||||||
*len = 0;
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
HTTP_ASSERT((queue->flags & BYTE_QUEUE_READ) == 0);
|
|
||||||
queue->flags |= BYTE_QUEUE_READ;
|
|
||||||
queue->read_target = queue->data;
|
|
||||||
queue->read_target_size = queue->size;
|
|
||||||
|
|
||||||
*len = queue->used;
|
|
||||||
if (queue->data == NULL)
|
|
||||||
return NULL;
|
|
||||||
return queue->data + queue->head;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Complete a previously started operation on the queue.
|
|
||||||
static void
|
|
||||||
byte_queue_read_ack(HTTP_ByteQueue *queue, int num)
|
|
||||||
{
|
|
||||||
HTTP_ASSERT(num >= 0);
|
|
||||||
|
|
||||||
if (queue->flags & BYTE_QUEUE_ERROR)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if ((queue->flags & BYTE_QUEUE_READ) == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
queue->flags &= ~BYTE_QUEUE_READ;
|
|
||||||
|
|
||||||
HTTP_ASSERT((unsigned int) num <= queue->used);
|
|
||||||
queue->head += (unsigned int) num;
|
|
||||||
queue->used -= (unsigned int) num;
|
|
||||||
queue->curs += (unsigned int) num;
|
|
||||||
|
|
||||||
if (queue->read_target) {
|
|
||||||
if (queue->read_target != queue->data)
|
|
||||||
callback_free(queue, queue->read_target, queue->read_target_size);
|
|
||||||
queue->read_target = NULL;
|
|
||||||
queue->read_target_size = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static char*
|
|
||||||
byte_queue_write_buf(HTTP_ByteQueue *queue, int *cap)
|
|
||||||
{
|
|
||||||
if ((queue->flags & BYTE_QUEUE_ERROR) || queue->data == NULL) {
|
|
||||||
*cap = 0;
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
HTTP_ASSERT((queue->flags & BYTE_QUEUE_WRITE) == 0);
|
|
||||||
queue->flags |= BYTE_QUEUE_WRITE;
|
|
||||||
|
|
||||||
unsigned int ucap = queue->size - (queue->head + queue->used);
|
|
||||||
if (ucap > INT_MAX) ucap = INT_MAX;
|
|
||||||
|
|
||||||
*cap = (int) ucap;
|
|
||||||
return queue->data + (queue->head + queue->used);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
byte_queue_write_ack(HTTP_ByteQueue *queue, int num)
|
|
||||||
{
|
|
||||||
HTTP_ASSERT(num >= 0);
|
|
||||||
|
|
||||||
if (queue->flags & BYTE_QUEUE_ERROR)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if ((queue->flags & BYTE_QUEUE_WRITE) == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
queue->flags &= ~BYTE_QUEUE_WRITE;
|
|
||||||
queue->used += (unsigned int) num;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sets the minimum capacity for the next write operation
|
|
||||||
// and returns 1 if the content of the queue was moved, else
|
|
||||||
// 0 is returned.
|
|
||||||
//
|
|
||||||
// You must not call this function while a write is pending.
|
|
||||||
// In other words, you must do this:
|
|
||||||
//
|
|
||||||
// byte_queue_write_setmincap(queue, mincap);
|
|
||||||
// dst = byte_queue_write_buf(queue, &cap);
|
|
||||||
// ...
|
|
||||||
// byte_queue_write_ack(num);
|
|
||||||
//
|
|
||||||
// And NOT this:
|
|
||||||
//
|
|
||||||
// dst = byte_queue_write_buf(queue, &cap);
|
|
||||||
// byte_queue_write_setmincap(queue, mincap); <-- BAD
|
|
||||||
// ...
|
|
||||||
// byte_queue_write_ack(num);
|
|
||||||
//
|
|
||||||
static int
|
|
||||||
byte_queue_write_setmincap(HTTP_ByteQueue *queue, int mincap)
|
|
||||||
{
|
|
||||||
HTTP_ASSERT(mincap >= 0);
|
|
||||||
unsigned int umincap = (unsigned int) mincap;
|
|
||||||
|
|
||||||
// Sticky error
|
|
||||||
if (queue->flags & BYTE_QUEUE_ERROR)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
// In general, the queue's contents look like this:
|
|
||||||
//
|
|
||||||
// size
|
|
||||||
// v
|
|
||||||
// [___xxxxxxxxxxxx________]
|
|
||||||
// ^ ^ ^
|
|
||||||
// 0 head head + used
|
|
||||||
//
|
|
||||||
// This function needs to make sure that at least [mincap]
|
|
||||||
// bytes are available on the right side of the content.
|
|
||||||
//
|
|
||||||
// We have 3 cases:
|
|
||||||
//
|
|
||||||
// 1) If there is enough memory already, this function doesn't
|
|
||||||
// need to do anything.
|
|
||||||
//
|
|
||||||
// 2) If there isn't enough memory on the right but there is
|
|
||||||
// enough free memory if we cound the left unused region,
|
|
||||||
// then the content is moved back to the
|
|
||||||
// start of the buffer.
|
|
||||||
//
|
|
||||||
// 3) If there isn't enough memory considering both sides, this
|
|
||||||
// function needs to allocate a new buffer.
|
|
||||||
//
|
|
||||||
// If there are pending read or write operations, the application
|
|
||||||
// is holding pointers to the buffer, so we need to make sure
|
|
||||||
// to not invalidate them. The only real problem is pending reads
|
|
||||||
// since this function can only be called before starting a write
|
|
||||||
// opearation.
|
|
||||||
//
|
|
||||||
// To avoid invalidating the read pointer when we allocate a new
|
|
||||||
// buffer, we don't free the old buffer. Instead, we store the
|
|
||||||
// pointer in the "old" field so that the read ack function can
|
|
||||||
// free it.
|
|
||||||
//
|
|
||||||
// To avoid invalidating the pointer when we are moving back the
|
|
||||||
// content since there is enough memory at the start of the buffer,
|
|
||||||
// we just avoid that. Even if there is enough memory considering
|
|
||||||
// left and right free regions, we allocate a new buffer.
|
|
||||||
|
|
||||||
HTTP_ASSERT((queue->flags & BYTE_QUEUE_WRITE) == 0);
|
|
||||||
|
|
||||||
unsigned int total_free_space = queue->size - queue->used;
|
|
||||||
unsigned int free_space_after_data = queue->size - queue->used - queue->head;
|
|
||||||
|
|
||||||
int moved = 0;
|
|
||||||
if (free_space_after_data < umincap) {
|
|
||||||
|
|
||||||
if (total_free_space < umincap || (queue->read_target == queue->data)) {
|
|
||||||
// Resize required
|
|
||||||
|
|
||||||
if (queue->used + umincap > queue->limit) {
|
|
||||||
queue->flags |= BYTE_QUEUE_ERROR;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned int size;
|
|
||||||
if (queue->size > UINT32_MAX / 2)
|
|
||||||
size = UINT32_MAX;
|
|
||||||
else
|
|
||||||
size = 2 * queue->size;
|
|
||||||
|
|
||||||
if (size < queue->used + umincap)
|
|
||||||
size = queue->used + umincap;
|
|
||||||
|
|
||||||
if (size > queue->limit)
|
|
||||||
size = queue->limit;
|
|
||||||
|
|
||||||
char *data = callback_malloc(queue, size);
|
|
||||||
if (!data) {
|
|
||||||
queue->flags |= BYTE_QUEUE_ERROR;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (queue->used > 0)
|
|
||||||
memcpy(data, queue->data + queue->head, queue->used);
|
|
||||||
|
|
||||||
if (queue->read_target != queue->data)
|
|
||||||
callback_free(queue, queue->data, queue->size);
|
|
||||||
|
|
||||||
queue->data = data;
|
|
||||||
queue->head = 0;
|
|
||||||
queue->size = size;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// Move required
|
|
||||||
memmove(queue->data, queue->data + queue->head, queue->used);
|
|
||||||
queue->head = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
moved = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return moved;
|
|
||||||
}
|
|
||||||
|
|
||||||
static HTTP_ByteQueueOffset
|
|
||||||
byte_queue_offset(HTTP_ByteQueue *queue)
|
|
||||||
{
|
|
||||||
if (queue->flags & BYTE_QUEUE_ERROR)
|
|
||||||
return (HTTP_ByteQueueOffset) { 0 };
|
|
||||||
return (HTTP_ByteQueueOffset) { queue->curs + queue->used };
|
|
||||||
}
|
|
||||||
|
|
||||||
static unsigned int
|
|
||||||
byte_queue_size_from_offset(HTTP_ByteQueue *queue, HTTP_ByteQueueOffset off)
|
|
||||||
{
|
|
||||||
return queue->curs + queue->used - off;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
byte_queue_patch(HTTP_ByteQueue *queue, HTTP_ByteQueueOffset off,
|
|
||||||
char *src, unsigned int len)
|
|
||||||
{
|
|
||||||
if (queue->flags & BYTE_QUEUE_ERROR)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Check that the offset is in range
|
|
||||||
HTTP_ASSERT(off >= queue->curs && off - queue->curs < queue->used);
|
|
||||||
|
|
||||||
// Check that the length is in range
|
|
||||||
HTTP_ASSERT(len <= queue->used - (off - queue->curs));
|
|
||||||
|
|
||||||
// Perform the patch
|
|
||||||
char *dst = queue->data + queue->head + (off - queue->curs);
|
|
||||||
memcpy(dst, src, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
byte_queue_remove_from_offset(HTTP_ByteQueue *queue, HTTP_ByteQueueOffset offset)
|
|
||||||
{
|
|
||||||
if (queue->flags & BYTE_QUEUE_ERROR)
|
|
||||||
return;
|
|
||||||
|
|
||||||
unsigned long long num = (queue->curs + queue->used) - offset;
|
|
||||||
HTTP_ASSERT(num <= queue->used);
|
|
||||||
|
|
||||||
queue->used -= num;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
byte_queue_write(HTTP_ByteQueue *queue, const char *str, int len)
|
|
||||||
{
|
|
||||||
if (str == NULL) str = "";
|
|
||||||
if (len < 0) len = strlen(str);
|
|
||||||
|
|
||||||
int cap;
|
|
||||||
byte_queue_write_setmincap(queue, len);
|
|
||||||
char *dst = byte_queue_write_buf(queue, &cap);
|
|
||||||
if (dst) memcpy(dst, str, len);
|
|
||||||
byte_queue_write_ack(queue, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
byte_queue_write_fmt2(HTTP_ByteQueue *queue, const char *fmt, va_list args)
|
|
||||||
{
|
|
||||||
if (queue->flags & BYTE_QUEUE_ERROR)
|
|
||||||
return;
|
|
||||||
|
|
||||||
va_list args2;
|
|
||||||
va_copy(args2, args);
|
|
||||||
|
|
||||||
int cap;
|
|
||||||
byte_queue_write_setmincap(queue, 128);
|
|
||||||
char *dst = byte_queue_write_buf(queue, &cap);
|
|
||||||
|
|
||||||
int len = vsnprintf(dst, cap, fmt, args);
|
|
||||||
if (len < 0) {
|
|
||||||
queue->flags |= BYTE_QUEUE_ERROR;
|
|
||||||
va_end(args2);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (len > cap) {
|
|
||||||
byte_queue_write_ack(queue, 0);
|
|
||||||
byte_queue_write_setmincap(queue, len+1);
|
|
||||||
dst = byte_queue_write_buf(queue, &cap);
|
|
||||||
vsnprintf(dst, cap, fmt, args2);
|
|
||||||
}
|
|
||||||
|
|
||||||
byte_queue_write_ack(queue, len);
|
|
||||||
|
|
||||||
va_end(args2);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
byte_queue_write_fmt(HTTP_ByteQueue *queue, const char *fmt, ...)
|
|
||||||
{
|
|
||||||
va_list args;
|
|
||||||
va_start(args, fmt);
|
|
||||||
byte_queue_write_fmt2(queue, fmt, args);
|
|
||||||
va_end(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
#define TEN_SPACES " "
|
|
||||||
|
|
||||||
void http_engine_init(HTTP_Engine *eng, int client, HTTP_MemoryFunc memfunc, void *memfuncdata)
|
|
||||||
{
|
|
||||||
if (client)
|
|
||||||
eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_URL;
|
|
||||||
else
|
|
||||||
eng->state = HTTP_ENGINE_STATE_SERVER_RECV_BUF;
|
|
||||||
|
|
||||||
eng->closing = 0;
|
|
||||||
eng->numexch = 0;
|
|
||||||
|
|
||||||
byte_queue_init(&eng->input, 1<<20, memfunc, memfuncdata);
|
|
||||||
byte_queue_init(&eng->output, 1<<20, memfunc, memfuncdata);
|
|
||||||
}
|
|
||||||
|
|
||||||
void http_engine_free(HTTP_Engine *eng)
|
|
||||||
{
|
|
||||||
byte_queue_free(&eng->input);
|
|
||||||
byte_queue_free(&eng->output);
|
|
||||||
eng->state = HTTP_ENGINE_STATE_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
void http_engine_close(HTTP_Engine *eng)
|
|
||||||
{
|
|
||||||
if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT)
|
|
||||||
eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED;
|
|
||||||
else
|
|
||||||
eng->state = HTTP_ENGINE_STATE_SERVER_CLOSED;
|
|
||||||
}
|
|
||||||
|
|
||||||
HTTP_EngineState http_engine_state(HTTP_Engine *eng)
|
|
||||||
{
|
|
||||||
return eng->state;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* http_engine_statestr(HTTP_EngineState state) { // TODO: remove
|
|
||||||
switch (state) {
|
|
||||||
case HTTP_ENGINE_STATE_NONE: return "NONE";
|
|
||||||
case HTTP_ENGINE_STATE_CLIENT_PREP_URL: return "CLIENT_PREP_URL";
|
|
||||||
case HTTP_ENGINE_STATE_CLIENT_PREP_HEADER: return "CLIENT_PREP_HEADER";
|
|
||||||
case HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF: return "CLIENT_PREP_BODY_BUF";
|
|
||||||
case HTTP_ENGINE_STATE_CLIENT_PREP_BODY_ACK: return "CLIENT_PREP_BODY_ACK";
|
|
||||||
case HTTP_ENGINE_STATE_CLIENT_PREP_ERROR: return "CLIENT_PREP_ERROR";
|
|
||||||
case HTTP_ENGINE_STATE_CLIENT_SEND_BUF: return "CLIENT_SEND_BUF";
|
|
||||||
case HTTP_ENGINE_STATE_CLIENT_SEND_ACK: return "CLIENT_SEND_ACK";
|
|
||||||
case HTTP_ENGINE_STATE_CLIENT_RECV_BUF: return "CLIENT_RECV_BUF";
|
|
||||||
case HTTP_ENGINE_STATE_CLIENT_RECV_ACK: return "CLIENT_RECV_ACK";
|
|
||||||
case HTTP_ENGINE_STATE_CLIENT_READY: return "CLIENT_READY";
|
|
||||||
case HTTP_ENGINE_STATE_CLIENT_CLOSED: return "CLIENT_CLOSED";
|
|
||||||
case HTTP_ENGINE_STATE_SERVER_RECV_BUF: return "SERVER_RECV_BUF";
|
|
||||||
case HTTP_ENGINE_STATE_SERVER_RECV_ACK: return "SERVER_RECV_ACK";
|
|
||||||
case HTTP_ENGINE_STATE_SERVER_PREP_STATUS: return "SERVER_PREP_STATUS";
|
|
||||||
case HTTP_ENGINE_STATE_SERVER_PREP_HEADER: return "SERVER_PREP_HEADER";
|
|
||||||
case HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF: return "SERVER_PREP_BODY_BUF";
|
|
||||||
case HTTP_ENGINE_STATE_SERVER_PREP_BODY_ACK: return "SERVER_PREP_BODY_ACK";
|
|
||||||
case HTTP_ENGINE_STATE_SERVER_PREP_ERROR: return "SERVER_PREP_ERROR";
|
|
||||||
case HTTP_ENGINE_STATE_SERVER_SEND_BUF: return "SERVER_SEND_BUF";
|
|
||||||
case HTTP_ENGINE_STATE_SERVER_SEND_ACK: return "SERVER_SEND_ACK";
|
|
||||||
case HTTP_ENGINE_STATE_SERVER_CLOSED: return "SERVER_CLOSED";
|
|
||||||
default: return "UNKNOWN";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
char *http_engine_recvbuf(HTTP_Engine *eng, int *cap)
|
|
||||||
{
|
|
||||||
if ((eng->state & HTTP_ENGINE_STATEBIT_RECV_BUF) == 0) {
|
|
||||||
*cap = 0;
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
eng->state &= ~HTTP_ENGINE_STATEBIT_RECV_BUF;
|
|
||||||
eng->state |= HTTP_ENGINE_STATEBIT_RECV_ACK;
|
|
||||||
|
|
||||||
byte_queue_write_setmincap(&eng->input, 1<<9);
|
|
||||||
if (byte_queue_error(&eng->input)) {
|
|
||||||
*cap = 0;
|
|
||||||
if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT)
|
|
||||||
eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED;
|
|
||||||
else
|
|
||||||
eng->state = HTTP_ENGINE_STATE_SERVER_CLOSED;
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return byte_queue_write_buf(&eng->input, cap);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
should_keep_alive(HTTP_Engine *eng)
|
|
||||||
{
|
|
||||||
HTTP_ASSERT(eng->state & HTTP_ENGINE_STATEBIT_PREP);
|
|
||||||
|
|
||||||
#if 0
|
|
||||||
// If the parent system doesn't want us to reuse
|
|
||||||
// the connection, we certainly can't keep alive.
|
|
||||||
if ((eng->state & TINYHTTP_STREAM_REUSE) == 0)
|
|
||||||
return 0;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (eng->numexch >= 100) // TODO: Make this a parameter
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
HTTP_Request *req = &eng->result.req;
|
|
||||||
|
|
||||||
// If the client is using HTTP/1.0, we can't
|
|
||||||
// keep alive.
|
|
||||||
if (req->minor == 0)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
// TODO: This assumes "Connection" can only hold a single token,
|
|
||||||
// but this is not true.
|
|
||||||
int i = http_find_header(req->headers, req->num_headers, HTTP_STR("Connection"));
|
|
||||||
if (i >= 0 && http_streqcase(req->headers[i].value, HTTP_STR("Close")))
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void process_incoming_request(HTTP_Engine *eng)
|
|
||||||
{
|
|
||||||
HTTP_ASSERT(eng->state == HTTP_ENGINE_STATE_SERVER_RECV_ACK
|
|
||||||
|| eng->state == HTTP_ENGINE_STATE_SERVER_SEND_ACK
|
|
||||||
|| eng->state == HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF
|
|
||||||
|| eng->state == HTTP_ENGINE_STATE_SERVER_PREP_ERROR);
|
|
||||||
|
|
||||||
char *src;
|
|
||||||
int len;
|
|
||||||
src = byte_queue_read_buf(&eng->input, &len);
|
|
||||||
|
|
||||||
int ret = http_parse_request(src, len, &eng->result.req);
|
|
||||||
|
|
||||||
if (ret == 0) {
|
|
||||||
byte_queue_read_ack(&eng->input, 0);
|
|
||||||
eng->state = HTTP_ENGINE_STATE_SERVER_RECV_BUF;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ret < 0) {
|
|
||||||
byte_queue_read_ack(&eng->input, 0);
|
|
||||||
byte_queue_write(&eng->output,
|
|
||||||
"HTTP/1.1 400 Bad Request\r\n"
|
|
||||||
"Connection: Close\r\n"
|
|
||||||
"Content-Length: 0\r\n"
|
|
||||||
"\r\n", -1
|
|
||||||
);
|
|
||||||
if (byte_queue_error(&eng->output))
|
|
||||||
eng->state = HTTP_ENGINE_STATE_SERVER_CLOSED;
|
|
||||||
else {
|
|
||||||
eng->closing = 1;
|
|
||||||
eng->state = HTTP_ENGINE_STATE_SERVER_SEND_BUF;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
HTTP_ASSERT(ret > 0);
|
|
||||||
|
|
||||||
eng->state = HTTP_ENGINE_STATE_SERVER_PREP_STATUS;
|
|
||||||
eng->reqsize = ret;
|
|
||||||
eng->keepalive = should_keep_alive(eng);
|
|
||||||
eng->response_offset = byte_queue_offset(&eng->output);
|
|
||||||
}
|
|
||||||
|
|
||||||
void http_engine_recvack(HTTP_Engine *eng, int num)
|
|
||||||
{
|
|
||||||
if ((eng->state & HTTP_ENGINE_STATEBIT_RECV_ACK) == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
byte_queue_write_ack(&eng->input, num);
|
|
||||||
|
|
||||||
if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) {
|
|
||||||
|
|
||||||
char *src;
|
|
||||||
int len;
|
|
||||||
src = byte_queue_read_buf(&eng->input, &len);
|
|
||||||
|
|
||||||
int ret = http_parse_response(src, len, &eng->result.res);
|
|
||||||
|
|
||||||
if (ret == 0) {
|
|
||||||
byte_queue_read_ack(&eng->input, 0);
|
|
||||||
eng->state = HTTP_ENGINE_STATE_CLIENT_RECV_BUF;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ret < 0) {
|
|
||||||
eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
HTTP_ASSERT(ret > 0);
|
|
||||||
|
|
||||||
eng->state = HTTP_ENGINE_STATE_CLIENT_READY;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
process_incoming_request(eng);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
char *http_engine_sendbuf(HTTP_Engine *eng, int *len)
|
|
||||||
{
|
|
||||||
if ((eng->state & HTTP_ENGINE_STATEBIT_SEND_BUF) == 0) {
|
|
||||||
*len = 0;
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
eng->state &= ~HTTP_ENGINE_STATEBIT_SEND_BUF;
|
|
||||||
eng->state |= HTTP_ENGINE_STATEBIT_SEND_ACK;
|
|
||||||
|
|
||||||
return byte_queue_read_buf(&eng->output, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
void http_engine_sendack(HTTP_Engine *eng, int num)
|
|
||||||
{
|
|
||||||
if (eng->state != HTTP_ENGINE_STATE_SERVER_SEND_ACK &&
|
|
||||||
eng->state != HTTP_ENGINE_STATE_CLIENT_SEND_ACK)
|
|
||||||
return;
|
|
||||||
|
|
||||||
byte_queue_read_ack(&eng->output, num);
|
|
||||||
|
|
||||||
if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) {
|
|
||||||
if (byte_queue_empty(&eng->output))
|
|
||||||
eng->state = HTTP_ENGINE_STATE_CLIENT_RECV_BUF;
|
|
||||||
else
|
|
||||||
eng->state = HTTP_ENGINE_STATE_CLIENT_SEND_BUF;
|
|
||||||
} else {
|
|
||||||
if (byte_queue_empty(&eng->output)) {
|
|
||||||
if (!eng->closing && eng->keepalive)
|
|
||||||
process_incoming_request(eng);
|
|
||||||
else
|
|
||||||
eng->state = HTTP_ENGINE_STATE_SERVER_CLOSED;
|
|
||||||
} else
|
|
||||||
eng->state = HTTP_ENGINE_STATE_SERVER_SEND_BUF;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HTTP_Request *http_engine_getreq(HTTP_Engine *eng)
|
|
||||||
{
|
|
||||||
if ((eng->state & HTTP_ENGINE_STATEBIT_REQUEST) == 0)
|
|
||||||
return NULL;
|
|
||||||
return &eng->result.req;
|
|
||||||
}
|
|
||||||
|
|
||||||
HTTP_Response *http_engine_getres(HTTP_Engine *eng)
|
|
||||||
{
|
|
||||||
if ((eng->state & HTTP_ENGINE_STATEBIT_RESPONSE) == 0)
|
|
||||||
return NULL;
|
|
||||||
return &eng->result.res;
|
|
||||||
}
|
|
||||||
|
|
||||||
void http_engine_url(HTTP_Engine *eng, HTTP_Method method, HTTP_String url, int minor)
|
|
||||||
{
|
|
||||||
if (eng->state != HTTP_ENGINE_STATE_CLIENT_PREP_URL)
|
|
||||||
return;
|
|
||||||
|
|
||||||
eng->response_offset = byte_queue_offset(&eng->output); // TODO: rename response_offset to something that makes sense for clients
|
|
||||||
|
|
||||||
HTTP_URL parsed_url;
|
|
||||||
int ret = http_parse_url(url.ptr, url.len, &parsed_url);
|
|
||||||
if (ret != url.len) {
|
|
||||||
eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_ERROR;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
HTTP_String method_and_space = HTTP_STR("???");
|
|
||||||
switch (method) {
|
|
||||||
case HTTP_METHOD_GET : method_and_space = HTTP_STR("GET "); break;
|
|
||||||
case HTTP_METHOD_HEAD : method_and_space = HTTP_STR("HEAD "); break;
|
|
||||||
case HTTP_METHOD_POST : method_and_space = HTTP_STR("POST "); break;
|
|
||||||
case HTTP_METHOD_PUT : method_and_space = HTTP_STR("PUT "); break;
|
|
||||||
case HTTP_METHOD_DELETE : method_and_space = HTTP_STR("DELETE "); break;
|
|
||||||
case HTTP_METHOD_CONNECT: method_and_space = HTTP_STR("CONNECT "); break;
|
|
||||||
case HTTP_METHOD_OPTIONS: method_and_space = HTTP_STR("OPTIONS "); break;
|
|
||||||
case HTTP_METHOD_TRACE : method_and_space = HTTP_STR("TRACE "); break;
|
|
||||||
case HTTP_METHOD_PATCH : method_and_space = HTTP_STR("PATCH "); break;
|
|
||||||
}
|
|
||||||
|
|
||||||
HTTP_String path = parsed_url.path;
|
|
||||||
if (path.len == 0)
|
|
||||||
path = HTTP_STR("/");
|
|
||||||
|
|
||||||
byte_queue_write(&eng->output, method_and_space.ptr, method_and_space.len);
|
|
||||||
byte_queue_write(&eng->output, path.ptr, path.len);
|
|
||||||
byte_queue_write(&eng->output, parsed_url.query.ptr, parsed_url.query.len);
|
|
||||||
byte_queue_write(&eng->output, minor ? " HTTP/1.1\r\nHost: " : " HTTP/1.0\r\nHost: ", -1);
|
|
||||||
byte_queue_write(&eng->output, parsed_url.authority.host.text.ptr, parsed_url.authority.host.text.len);
|
|
||||||
if (parsed_url.authority.port > 0)
|
|
||||||
byte_queue_write_fmt(&eng->output, "%d", parsed_url.authority.port);
|
|
||||||
byte_queue_write(&eng->output, "\r\n", 2);
|
|
||||||
|
|
||||||
eng->keepalive = 1; // TODO
|
|
||||||
|
|
||||||
eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_HEADER;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static const char*
|
|
||||||
get_status_text(int code)
|
|
||||||
{
|
|
||||||
switch(code) {
|
|
||||||
|
|
||||||
case 100: return "Continue";
|
|
||||||
case 101: return "Switching Protocols";
|
|
||||||
case 102: return "Processing";
|
|
||||||
|
|
||||||
case 200: return "OK";
|
|
||||||
case 201: return "Created";
|
|
||||||
case 202: return "Accepted";
|
|
||||||
case 203: return "Non-Authoritative Information";
|
|
||||||
case 204: return "No Content";
|
|
||||||
case 205: return "Reset Content";
|
|
||||||
case 206: return "Partial Content";
|
|
||||||
case 207: return "Multi-Status";
|
|
||||||
case 208: return "Already Reported";
|
|
||||||
|
|
||||||
case 300: return "Multiple Choices";
|
|
||||||
case 301: return "Moved Permanently";
|
|
||||||
case 302: return "Found";
|
|
||||||
case 303: return "See Other";
|
|
||||||
case 304: return "Not Modified";
|
|
||||||
case 305: return "Use Proxy";
|
|
||||||
case 306: return "Switch Proxy";
|
|
||||||
case 307: return "Temporary Redirect";
|
|
||||||
case 308: return "Permanent Redirect";
|
|
||||||
|
|
||||||
case 400: return "Bad Request";
|
|
||||||
case 401: return "Unauthorized";
|
|
||||||
case 402: return "Payment Required";
|
|
||||||
case 403: return "Forbidden";
|
|
||||||
case 404: return "Not Found";
|
|
||||||
case 405: return "Method Not Allowed";
|
|
||||||
case 406: return "Not Acceptable";
|
|
||||||
case 407: return "Proxy Authentication Required";
|
|
||||||
case 408: return "Request Timeout";
|
|
||||||
case 409: return "Conflict";
|
|
||||||
case 410: return "Gone";
|
|
||||||
case 411: return "Length Required";
|
|
||||||
case 412: return "Precondition Failed";
|
|
||||||
case 413: return "Request Entity Too Large";
|
|
||||||
case 414: return "Request-URI Too Long";
|
|
||||||
case 415: return "Unsupported Media Type";
|
|
||||||
case 416: return "Requested Range Not Satisfiable";
|
|
||||||
case 417: return "Expectation Failed";
|
|
||||||
case 418: return "I'm a teapot";
|
|
||||||
case 420: return "Enhance your calm";
|
|
||||||
case 422: return "Unprocessable Entity";
|
|
||||||
case 426: return "Upgrade Required";
|
|
||||||
case 429: return "Too many requests";
|
|
||||||
case 431: return "Request Header Fields Too Large";
|
|
||||||
case 449: return "Retry With";
|
|
||||||
case 451: return "Unavailable For Legal Reasons";
|
|
||||||
|
|
||||||
case 500: return "Internal Server Error";
|
|
||||||
case 501: return "Not Implemented";
|
|
||||||
case 502: return "Bad Gateway";
|
|
||||||
case 503: return "Service Unavailable";
|
|
||||||
case 504: return "Gateway Timeout";
|
|
||||||
case 505: return "HTTP Version Not Supported";
|
|
||||||
case 509: return "Bandwidth Limit Exceeded";
|
|
||||||
}
|
|
||||||
return "???";
|
|
||||||
}
|
|
||||||
|
|
||||||
void http_engine_status(HTTP_Engine *eng, int status)
|
|
||||||
{
|
|
||||||
if (eng->state != HTTP_ENGINE_STATE_SERVER_PREP_STATUS)
|
|
||||||
return;
|
|
||||||
|
|
||||||
byte_queue_write_fmt(&eng->output,
|
|
||||||
"HTTP/1.1 %d %s\r\n",
|
|
||||||
status, get_status_text(status));
|
|
||||||
|
|
||||||
eng->state = HTTP_ENGINE_STATE_SERVER_PREP_HEADER;
|
|
||||||
}
|
|
||||||
|
|
||||||
void http_engine_header(HTTP_Engine *eng, HTTP_String str)
|
|
||||||
{
|
|
||||||
if ((eng->state & HTTP_ENGINE_STATEBIT_PREP_HEADER) == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// TODO: Check that the header is valid
|
|
||||||
|
|
||||||
byte_queue_write(&eng->output, str.ptr, str.len);
|
|
||||||
byte_queue_write(&eng->output, "\r\n", 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
void http_engine_header_fmt2(HTTP_Engine *eng, const char *fmt, va_list args)
|
|
||||||
{
|
|
||||||
if ((eng->state & HTTP_ENGINE_STATEBIT_PREP_HEADER) == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// TODO: Check that the header is valid
|
|
||||||
|
|
||||||
byte_queue_write_fmt2(&eng->output, fmt, args);
|
|
||||||
byte_queue_write(&eng->output, "\r\n", 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
void http_engine_header_fmt(HTTP_Engine *eng, const char *fmt, ...)
|
|
||||||
{
|
|
||||||
va_list args;
|
|
||||||
va_start(args, fmt);
|
|
||||||
http_engine_header_fmt2(eng, fmt, args);
|
|
||||||
va_end(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
complete_message_head(HTTP_Engine *eng)
|
|
||||||
{
|
|
||||||
if (eng->keepalive) byte_queue_write(&eng->output, "Connection: Keep-Alive\r\n", -1);
|
|
||||||
else byte_queue_write(&eng->output, "Connection: Close\r\n", -1);
|
|
||||||
|
|
||||||
byte_queue_write(&eng->output, "Content-Length: ", -1);
|
|
||||||
eng->content_length_value_offset = byte_queue_offset(&eng->output);
|
|
||||||
byte_queue_write(&eng->output, TEN_SPACES "\r\n", -1);
|
|
||||||
|
|
||||||
byte_queue_write(&eng->output, "\r\n", -1);
|
|
||||||
eng->content_length_offset = byte_queue_offset(&eng->output);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void complete_message_body(HTTP_Engine *eng)
|
|
||||||
{
|
|
||||||
unsigned int content_length = byte_queue_size_from_offset(&eng->output, eng->content_length_offset);
|
|
||||||
|
|
||||||
if (content_length > UINT32_MAX) {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
char tmp[10];
|
|
||||||
|
|
||||||
tmp[0] = '0' + content_length / 1000000000; content_length %= 1000000000;
|
|
||||||
tmp[1] = '0' + content_length / 100000000; content_length %= 100000000;
|
|
||||||
tmp[2] = '0' + content_length / 10000000; content_length %= 10000000;
|
|
||||||
tmp[3] = '0' + content_length / 1000000; content_length %= 1000000;
|
|
||||||
tmp[4] = '0' + content_length / 100000; content_length %= 100000;
|
|
||||||
tmp[5] = '0' + content_length / 10000; content_length %= 10000;
|
|
||||||
tmp[6] = '0' + content_length / 1000; content_length %= 1000;
|
|
||||||
tmp[7] = '0' + content_length / 100; content_length %= 100;
|
|
||||||
tmp[8] = '0' + content_length / 10; content_length %= 10;
|
|
||||||
tmp[9] = '0' + content_length;
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
while (i < 9 && tmp[i] == '0')
|
|
||||||
i++;
|
|
||||||
|
|
||||||
byte_queue_patch(&eng->output, eng->content_length_value_offset, tmp + i, 10 - i);
|
|
||||||
}
|
|
||||||
|
|
||||||
void http_engine_body(HTTP_Engine *eng, HTTP_String str)
|
|
||||||
{
|
|
||||||
http_engine_bodycap(eng, str.len);
|
|
||||||
int cap;
|
|
||||||
char *buf = http_engine_bodybuf(eng, &cap);
|
|
||||||
if (buf) {
|
|
||||||
memcpy(buf, str.ptr, str.len);
|
|
||||||
http_engine_bodyack(eng, str.len);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void ensure_body_entered(HTTP_Engine *eng)
|
|
||||||
{
|
|
||||||
if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) {
|
|
||||||
|
|
||||||
if (eng->state == HTTP_ENGINE_STATE_CLIENT_PREP_HEADER) {
|
|
||||||
complete_message_head(eng);
|
|
||||||
eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF;
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
if (eng->state == HTTP_ENGINE_STATE_SERVER_PREP_HEADER) {
|
|
||||||
complete_message_head(eng);
|
|
||||||
eng->state = HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void http_engine_bodycap(HTTP_Engine *eng, int mincap)
|
|
||||||
{
|
|
||||||
ensure_body_entered(eng);
|
|
||||||
if (eng->state != HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF &&
|
|
||||||
eng->state != HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF)
|
|
||||||
return;
|
|
||||||
|
|
||||||
byte_queue_write_setmincap(&eng->output, mincap);
|
|
||||||
}
|
|
||||||
|
|
||||||
char *http_engine_bodybuf(HTTP_Engine *eng, int *cap)
|
|
||||||
{
|
|
||||||
ensure_body_entered(eng);
|
|
||||||
if (eng->state != HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF &&
|
|
||||||
eng->state != HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF) {
|
|
||||||
*cap = 0;
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT)
|
|
||||||
eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_BODY_ACK;
|
|
||||||
else
|
|
||||||
eng->state = HTTP_ENGINE_STATE_SERVER_PREP_BODY_ACK;
|
|
||||||
|
|
||||||
return byte_queue_write_buf(&eng->output, cap);
|
|
||||||
}
|
|
||||||
|
|
||||||
void http_engine_bodyack(HTTP_Engine *eng, int num)
|
|
||||||
{
|
|
||||||
if (eng->state != HTTP_ENGINE_STATE_CLIENT_PREP_BODY_ACK &&
|
|
||||||
eng->state != HTTP_ENGINE_STATE_SERVER_PREP_BODY_ACK)
|
|
||||||
return;
|
|
||||||
|
|
||||||
byte_queue_write_ack(&eng->output, num);
|
|
||||||
|
|
||||||
if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT)
|
|
||||||
eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF;
|
|
||||||
else
|
|
||||||
eng->state = HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF;
|
|
||||||
}
|
|
||||||
|
|
||||||
void http_engine_done(HTTP_Engine *eng)
|
|
||||||
{
|
|
||||||
if ((eng->state & HTTP_ENGINE_STATEBIT_PREP) == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) {
|
|
||||||
|
|
||||||
if (eng->state == HTTP_ENGINE_STATE_CLIENT_PREP_URL) {
|
|
||||||
eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (eng->state == HTTP_ENGINE_STATE_CLIENT_PREP_HEADER) {
|
|
||||||
complete_message_head(eng);
|
|
||||||
eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (eng->state == HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF)
|
|
||||||
complete_message_body(eng);
|
|
||||||
|
|
||||||
if (eng->state == HTTP_ENGINE_STATE_CLIENT_PREP_ERROR) {
|
|
||||||
eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (byte_queue_error(&eng->output)) {
|
|
||||||
eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
eng->state = HTTP_ENGINE_STATE_CLIENT_SEND_BUF;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
if (eng->state == HTTP_ENGINE_STATE_SERVER_PREP_HEADER) {
|
|
||||||
complete_message_head(eng);
|
|
||||||
eng->state = HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (eng->state == HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF)
|
|
||||||
complete_message_body(eng);
|
|
||||||
|
|
||||||
if (eng->state == HTTP_ENGINE_STATE_SERVER_PREP_ERROR) {
|
|
||||||
byte_queue_remove_from_offset(&eng->output, eng->response_offset);
|
|
||||||
byte_queue_write(&eng->output,
|
|
||||||
"HTTP/1.1 500 Internal Server Error\r\n"
|
|
||||||
"Content-Length: 0\r\n"
|
|
||||||
"Connection: Close\r\n"
|
|
||||||
"\r\n",
|
|
||||||
-1
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (byte_queue_error(&eng->output)) {
|
|
||||||
eng->state = HTTP_ENGINE_STATE_SERVER_CLOSED;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
byte_queue_read_ack(&eng->input, eng->reqsize);
|
|
||||||
eng->state = HTTP_ENGINE_STATE_SERVER_SEND_BUF;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void http_engine_undo(HTTP_Engine *eng)
|
|
||||||
{
|
|
||||||
if ((eng->state & HTTP_ENGINE_STATEBIT_PREP) == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
byte_queue_write_ack(&eng->output, 0);
|
|
||||||
byte_queue_remove_from_offset(&eng->output, eng->response_offset);
|
|
||||||
|
|
||||||
if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT)
|
|
||||||
eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_URL;
|
|
||||||
else
|
|
||||||
eng->state = HTTP_ENGINE_STATE_SERVER_PREP_STATUS;
|
|
||||||
}
|
|
||||||
-122
@@ -1,122 +0,0 @@
|
|||||||
#ifndef HTTP_ENGINE_INCLUDED
|
|
||||||
#define HTTP_ENGINE_INCLUDED
|
|
||||||
|
|
||||||
#ifndef HTTP_AMALGAMATION
|
|
||||||
#include "parse.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
HTTP_MEMFUNC_MALLOC,
|
|
||||||
HTTP_MEMFUNC_FREE,
|
|
||||||
} HTTP_MemoryFuncTag;
|
|
||||||
|
|
||||||
typedef void*(*HTTP_MemoryFunc)(HTTP_MemoryFuncTag tag,
|
|
||||||
void *ptr, int len, void *data);
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
|
|
||||||
HTTP_MemoryFunc memfunc;
|
|
||||||
void *memfuncdata;
|
|
||||||
|
|
||||||
unsigned long long curs;
|
|
||||||
|
|
||||||
char* data;
|
|
||||||
unsigned int head;
|
|
||||||
unsigned int size;
|
|
||||||
unsigned int used;
|
|
||||||
unsigned int limit;
|
|
||||||
|
|
||||||
char* read_target;
|
|
||||||
unsigned int read_target_size;
|
|
||||||
|
|
||||||
int flags;
|
|
||||||
} HTTP_ByteQueue;
|
|
||||||
|
|
||||||
typedef unsigned long long HTTP_ByteQueueOffset;
|
|
||||||
|
|
||||||
#define HTTP_ENGINE_STATEBIT_CLIENT (1 << 0)
|
|
||||||
#define HTTP_ENGINE_STATEBIT_CLOSED (1 << 1)
|
|
||||||
#define HTTP_ENGINE_STATEBIT_RECV_BUF (1 << 2)
|
|
||||||
#define HTTP_ENGINE_STATEBIT_RECV_ACK (1 << 3)
|
|
||||||
#define HTTP_ENGINE_STATEBIT_SEND_BUF (1 << 4)
|
|
||||||
#define HTTP_ENGINE_STATEBIT_SEND_ACK (1 << 5)
|
|
||||||
#define HTTP_ENGINE_STATEBIT_REQUEST (1 << 6)
|
|
||||||
#define HTTP_ENGINE_STATEBIT_RESPONSE (1 << 7)
|
|
||||||
#define HTTP_ENGINE_STATEBIT_PREP (1 << 8)
|
|
||||||
#define HTTP_ENGINE_STATEBIT_PREP_HEADER (1 << 9)
|
|
||||||
#define HTTP_ENGINE_STATEBIT_PREP_BODY_BUF (1 << 10)
|
|
||||||
#define HTTP_ENGINE_STATEBIT_PREP_BODY_ACK (1 << 11)
|
|
||||||
#define HTTP_ENGINE_STATEBIT_PREP_ERROR (1 << 12)
|
|
||||||
#define HTTP_ENGINE_STATEBIT_PREP_URL (1 << 13)
|
|
||||||
#define HTTP_ENGINE_STATEBIT_PREP_STATUS (1 << 14)
|
|
||||||
#define HTTP_ENGINE_STATEBIT_CLOSING (1 << 15)
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
HTTP_ENGINE_STATE_NONE = 0,
|
|
||||||
HTTP_ENGINE_STATE_CLIENT_PREP_URL = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_URL,
|
|
||||||
HTTP_ENGINE_STATE_CLIENT_PREP_HEADER = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_HEADER,
|
|
||||||
HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY_BUF,
|
|
||||||
HTTP_ENGINE_STATE_CLIENT_PREP_BODY_ACK = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY_ACK,
|
|
||||||
HTTP_ENGINE_STATE_CLIENT_PREP_ERROR = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_ERROR,
|
|
||||||
HTTP_ENGINE_STATE_CLIENT_SEND_BUF = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_SEND_BUF,
|
|
||||||
HTTP_ENGINE_STATE_CLIENT_SEND_ACK = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_SEND_ACK,
|
|
||||||
HTTP_ENGINE_STATE_CLIENT_RECV_BUF = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_RECV_BUF,
|
|
||||||
HTTP_ENGINE_STATE_CLIENT_RECV_ACK = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_RECV_ACK,
|
|
||||||
HTTP_ENGINE_STATE_CLIENT_READY = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_RESPONSE,
|
|
||||||
HTTP_ENGINE_STATE_CLIENT_CLOSED = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_CLOSED,
|
|
||||||
HTTP_ENGINE_STATE_SERVER_RECV_BUF = HTTP_ENGINE_STATEBIT_RECV_BUF,
|
|
||||||
HTTP_ENGINE_STATE_SERVER_RECV_ACK = HTTP_ENGINE_STATEBIT_RECV_ACK,
|
|
||||||
HTTP_ENGINE_STATE_SERVER_PREP_STATUS = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_STATUS,
|
|
||||||
HTTP_ENGINE_STATE_SERVER_PREP_HEADER = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_HEADER,
|
|
||||||
HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY_BUF,
|
|
||||||
HTTP_ENGINE_STATE_SERVER_PREP_BODY_ACK = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY_ACK,
|
|
||||||
HTTP_ENGINE_STATE_SERVER_PREP_ERROR = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_ERROR,
|
|
||||||
HTTP_ENGINE_STATE_SERVER_SEND_BUF = HTTP_ENGINE_STATEBIT_SEND_BUF,
|
|
||||||
HTTP_ENGINE_STATE_SERVER_SEND_ACK = HTTP_ENGINE_STATEBIT_SEND_ACK,
|
|
||||||
HTTP_ENGINE_STATE_SERVER_CLOSED = HTTP_ENGINE_STATEBIT_CLIENT,
|
|
||||||
} HTTP_EngineState;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
HTTP_EngineState state;
|
|
||||||
HTTP_ByteQueue input;
|
|
||||||
HTTP_ByteQueue output;
|
|
||||||
int numexch;
|
|
||||||
int reqsize;
|
|
||||||
int closing;
|
|
||||||
int keepalive;
|
|
||||||
HTTP_ByteQueueOffset response_offset;
|
|
||||||
HTTP_ByteQueueOffset content_length_offset;
|
|
||||||
HTTP_ByteQueueOffset content_length_value_offset;
|
|
||||||
union {
|
|
||||||
HTTP_Request req;
|
|
||||||
HTTP_Response res;
|
|
||||||
} result;
|
|
||||||
} HTTP_Engine;
|
|
||||||
|
|
||||||
void http_engine_init (HTTP_Engine *eng, int client, HTTP_MemoryFunc memfunc, void *memfuncdata);
|
|
||||||
void http_engine_free (HTTP_Engine *eng);
|
|
||||||
|
|
||||||
void http_engine_close (HTTP_Engine *eng);
|
|
||||||
HTTP_EngineState http_engine_state (HTTP_Engine *eng);
|
|
||||||
|
|
||||||
const char* http_engine_statestr(HTTP_EngineState state); // TODO: remove
|
|
||||||
|
|
||||||
char* http_engine_recvbuf (HTTP_Engine *eng, int *cap);
|
|
||||||
void http_engine_recvack (HTTP_Engine *eng, int num);
|
|
||||||
char* http_engine_sendbuf (HTTP_Engine *eng, int *len);
|
|
||||||
void http_engine_sendack (HTTP_Engine *eng, int num);
|
|
||||||
|
|
||||||
HTTP_Request* http_engine_getreq (HTTP_Engine *eng);
|
|
||||||
HTTP_Response* http_engine_getres (HTTP_Engine *eng);
|
|
||||||
|
|
||||||
void http_engine_url (HTTP_Engine *eng, HTTP_Method method, HTTP_String url, int minor);
|
|
||||||
void http_engine_status (HTTP_Engine *eng, int status);
|
|
||||||
void http_engine_header (HTTP_Engine *eng, HTTP_String str);
|
|
||||||
void http_engine_body (HTTP_Engine *eng, HTTP_String str);
|
|
||||||
void http_engine_bodycap (HTTP_Engine *eng, int mincap);
|
|
||||||
char* http_engine_bodybuf (HTTP_Engine *eng, int *cap);
|
|
||||||
void http_engine_bodyack (HTTP_Engine *eng, int num);
|
|
||||||
void http_engine_done (HTTP_Engine *eng);
|
|
||||||
void http_engine_undo (HTTP_Engine *eng);
|
|
||||||
|
|
||||||
#endif // HTTP_ENGINE_INCLUDED
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#include <windows.h>
|
||||||
|
#include <winsock2.h>
|
||||||
|
#else
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#endif
|
||||||
+7
-20
@@ -1,16 +1,3 @@
|
|||||||
#include <stdio.h> // snprintf
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <assert.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <limits.h>
|
|
||||||
|
|
||||||
#ifndef HTTP_AMALGAMATION
|
|
||||||
#include "parse.h"
|
|
||||||
#include "basic.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// From RFC 9112
|
// From RFC 9112
|
||||||
// request-target = origin-form
|
// request-target = origin-form
|
||||||
// / absolute-form
|
// / absolute-form
|
||||||
@@ -514,7 +501,7 @@ static int parse_uri(Scanner *s, HTTP_URL *url, int allow_fragment)
|
|||||||
s->cur - start,
|
s->cur - start,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (s->cur == s->len || s->src[s->cur] != ':')
|
if (s->cur == s->len || s->src[s->cur] != ':')
|
||||||
return -1; // ERROR: Missing ':' after scheme
|
return -1; // ERROR: Missing ':' after scheme
|
||||||
s->cur++;
|
s->cur++;
|
||||||
}
|
}
|
||||||
@@ -577,7 +564,7 @@ static int parse_uri(Scanner *s, HTTP_URL *url, int allow_fragment)
|
|||||||
static int parse_authority_form(Scanner *s, HTTP_Host *host, int *port)
|
static int parse_authority_form(Scanner *s, HTTP_Host *host, int *port)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
ret = parse_host(s, host);
|
ret = parse_host(s, host);
|
||||||
if (ret < 0) return ret;
|
if (ret < 0) return ret;
|
||||||
|
|
||||||
@@ -658,7 +645,7 @@ static int parse_request_target(Scanner *s, HTTP_URL *url)
|
|||||||
|
|
||||||
bool consume_str(Scanner *scan, HTTP_String token)
|
bool consume_str(Scanner *scan, HTTP_String token)
|
||||||
{
|
{
|
||||||
HTTP_ASSERT(token.len > 0);
|
assert(token.len > 0);
|
||||||
|
|
||||||
if (token.len > scan->len - scan->cur)
|
if (token.len > scan->len - scan->cur)
|
||||||
return false;
|
return false;
|
||||||
@@ -692,7 +679,7 @@ static int parse_headers(Scanner *s, HTTP_Header *headers, int max_headers)
|
|||||||
// obs-text = %x80-FF
|
// obs-text = %x80-FF
|
||||||
|
|
||||||
int start;
|
int start;
|
||||||
|
|
||||||
if (s->cur == s->len || !is_tchar(s->src[s->cur]))
|
if (s->cur == s->len || !is_tchar(s->src[s->cur]))
|
||||||
return -1; // ERROR
|
return -1; // ERROR
|
||||||
start = s->cur;
|
start = s->cur;
|
||||||
@@ -740,7 +727,7 @@ parse_transfer_encoding(HTTP_String src, TransferEncodingOption *dst, int max)
|
|||||||
|
|
||||||
int num = 0;
|
int num = 0;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
|
|
||||||
CONSUME_OPTIONAL_SEQUENCE(&s, is_space);
|
CONSUME_OPTIONAL_SEQUENCE(&s, is_space);
|
||||||
|
|
||||||
TransferEncodingOption opt;
|
TransferEncodingOption opt;
|
||||||
@@ -1304,12 +1291,12 @@ int http_get_param_i(HTTP_String body, HTTP_String str)
|
|||||||
bool http_match_host(HTTP_Request *req, HTTP_String domain, int port)
|
bool http_match_host(HTTP_Request *req, HTTP_String domain, int port)
|
||||||
{
|
{
|
||||||
int idx = http_find_header(req->headers, req->num_headers, HTTP_STR("Host"));
|
int idx = http_find_header(req->headers, req->num_headers, HTTP_STR("Host"));
|
||||||
HTTP_ASSERT(idx != -1); // Requests without the host header are always rejected
|
assert(idx != -1); // Requests without the host header are always rejected
|
||||||
|
|
||||||
char tmp[1<<8];
|
char tmp[1<<8];
|
||||||
if (port > -1 && port != 80) {
|
if (port > -1 && port != 80) {
|
||||||
int ret = snprintf(tmp, sizeof(tmp), "%.*s:%d", domain.len, domain.ptr, port);
|
int ret = snprintf(tmp, sizeof(tmp), "%.*s:%d", domain.len, domain.ptr, port);
|
||||||
HTTP_ASSERT(ret > 0);
|
assert(ret > 0);
|
||||||
domain = (HTTP_String) { tmp, ret };
|
domain = (HTTP_String) { tmp, ret };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,3 @@
|
|||||||
#ifndef PARSE_INCLUDED
|
|
||||||
#define PARSE_INCLUDED
|
|
||||||
|
|
||||||
#ifndef HTTP_AMALGAMATION
|
|
||||||
#include "basic.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define HTTP_MAX_HEADERS 32
|
#define HTTP_MAX_HEADERS 32
|
||||||
|
|
||||||
@@ -99,5 +93,3 @@ int http_get_param_i (HTTP_String body, HTTP_String str);
|
|||||||
// Checks whether the request was meant for the host with the given
|
// Checks whether the request was meant for the host with the given
|
||||||
// domain an port. If port is -1, the default value of 80 is assumed.
|
// domain an port. If port is -1, the default value of 80 is assumed.
|
||||||
bool http_match_host(HTTP_Request *req, HTTP_String domain, int port);
|
bool http_match_host(HTTP_Request *req, HTTP_String domain, int port);
|
||||||
|
|
||||||
#endif // PARSE_INCLUDED
|
|
||||||
@@ -1,220 +0,0 @@
|
|||||||
#ifndef HTTP_AMALGAMATION
|
|
||||||
#include "sec.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef HTTPS_ENABLED
|
|
||||||
|
|
||||||
void secure_context_global_init(void)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void secure_context_global_free(void)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
int secure_context_init_as_client(SecureContext *sec)
|
|
||||||
{
|
|
||||||
(void) sec;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int secure_context_init_as_server(SecureContext *sec,
|
|
||||||
HTTP_String cert_file, HTTP_String key_file)
|
|
||||||
{
|
|
||||||
(void) sec;
|
|
||||||
(void) cert_file;
|
|
||||||
(void) key_file;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int secure_context_add_cert(SecureContext *sec,
|
|
||||||
HTTP_String domain, HTTP_String cert_file,
|
|
||||||
HTTP_String key_file)
|
|
||||||
{
|
|
||||||
(void) sec;
|
|
||||||
(void) domain;
|
|
||||||
(void) cert_file;
|
|
||||||
(void) key_file;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void secure_context_free(SecureContext *sec)
|
|
||||||
{
|
|
||||||
(void) sec;
|
|
||||||
}
|
|
||||||
|
|
||||||
#else
|
|
||||||
|
|
||||||
void secure_context_global_init(void)
|
|
||||||
{
|
|
||||||
SSL_library_init();
|
|
||||||
SSL_load_error_strings();
|
|
||||||
OpenSSL_add_all_algorithms();
|
|
||||||
}
|
|
||||||
|
|
||||||
void secure_context_global_free(void)
|
|
||||||
{
|
|
||||||
EVP_cleanup();
|
|
||||||
}
|
|
||||||
|
|
||||||
int secure_context_init_as_client(SecureContext *sec)
|
|
||||||
{
|
|
||||||
SSL_CTX *ctx = SSL_CTX_new(TLS_client_method());
|
|
||||||
if (!ctx)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
|
|
||||||
|
|
||||||
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
|
|
||||||
|
|
||||||
if (SSL_CTX_set_default_verify_paths(ctx) != 1) {
|
|
||||||
SSL_CTX_free(ctx);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
sec->is_server = false;
|
|
||||||
sec->ctx = ctx;
|
|
||||||
sec->num_certs = 0;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int servername_callback(SSL *ssl, int *ad, void *arg)
|
|
||||||
{
|
|
||||||
SecureContext *sec = arg;
|
|
||||||
|
|
||||||
(void) ad; // TODO: use this?
|
|
||||||
|
|
||||||
const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
|
|
||||||
if (servername == NULL)
|
|
||||||
return SSL_TLSEXT_ERR_NOACK;
|
|
||||||
|
|
||||||
for (int i = 0; i < sec->num_certs; i++) {
|
|
||||||
CertData *cert = &sec->certs[i];
|
|
||||||
if (!strcmp(cert->domain, servername)) {
|
|
||||||
SSL_set_SSL_CTX(ssl, cert->ctx);
|
|
||||||
return SSL_TLSEXT_ERR_OK;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return SSL_TLSEXT_ERR_NOACK;
|
|
||||||
}
|
|
||||||
|
|
||||||
int secure_context_init_as_server(SecureContext *sec,
|
|
||||||
HTTP_String cert_file, HTTP_String key_file)
|
|
||||||
{
|
|
||||||
SSL_CTX *ctx = SSL_CTX_new(TLS_server_method());
|
|
||||||
if (!ctx)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
|
|
||||||
|
|
||||||
char cert_buffer[1024];
|
|
||||||
if (cert_file.len >= (int) sizeof(cert_buffer)) {
|
|
||||||
SSL_CTX_free(ctx);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
memcpy(cert_buffer, cert_file.ptr, cert_file.len);
|
|
||||||
cert_buffer[cert_file.len] = '\0';
|
|
||||||
|
|
||||||
// Copy private key file path to static buffer
|
|
||||||
char key_buffer[1024];
|
|
||||||
if (key_file.len >= (int) sizeof(key_buffer)) {
|
|
||||||
SSL_CTX_free(ctx);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
memcpy(key_buffer, key_file.ptr, key_file.len);
|
|
||||||
key_buffer[key_file.len] = '\0';
|
|
||||||
|
|
||||||
// Load certificate and private key
|
|
||||||
if (SSL_CTX_use_certificate_file(ctx, cert_buffer, SSL_FILETYPE_PEM) != 1) {
|
|
||||||
SSL_CTX_free(ctx);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SSL_CTX_use_PrivateKey_file(ctx, key_buffer, SSL_FILETYPE_PEM) != 1) {
|
|
||||||
SSL_CTX_free(ctx);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify that the private key matches the certificate
|
|
||||||
if (SSL_CTX_check_private_key(ctx) != 1) {
|
|
||||||
SSL_CTX_free(ctx);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
SSL_CTX_set_tlsext_servername_callback(ctx, servername_callback);
|
|
||||||
SSL_CTX_set_tlsext_servername_arg(ctx, sec);
|
|
||||||
|
|
||||||
sec->is_server = true;
|
|
||||||
sec->ctx = ctx;
|
|
||||||
sec->num_certs = 0;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void secure_context_free(SecureContext *sec)
|
|
||||||
{
|
|
||||||
SSL_CTX_free(sec->ctx);
|
|
||||||
for (int i = 0; i < sec->num_certs; i++)
|
|
||||||
SSL_CTX_free(sec->certs[i].ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
int secure_context_add_cert(SecureContext *sec,
|
|
||||||
HTTP_String domain, HTTP_String cert_file,
|
|
||||||
HTTP_String key_file)
|
|
||||||
{
|
|
||||||
if (!sec->is_server)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
if (sec->num_certs == MAX_CERTS)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
SSL_CTX *ctx = SSL_CTX_new(TLS_server_method());
|
|
||||||
if (!ctx)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
|
|
||||||
|
|
||||||
char cert_buffer[1024];
|
|
||||||
if (cert_file.len >= (int) sizeof(cert_buffer)) {
|
|
||||||
SSL_CTX_free(ctx);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
memcpy(cert_buffer, cert_file.ptr, cert_file.len);
|
|
||||||
cert_buffer[cert_file.len] = '\0';
|
|
||||||
|
|
||||||
char key_buffer[1024];
|
|
||||||
if (key_file.len >= (int) sizeof(key_buffer)) {
|
|
||||||
SSL_CTX_free(ctx);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
memcpy(key_buffer, key_file.ptr, key_file.len);
|
|
||||||
key_buffer[key_file.len] = '\0';
|
|
||||||
|
|
||||||
if (SSL_CTX_use_certificate_file(ctx, cert_buffer, SSL_FILETYPE_PEM) != 1) {
|
|
||||||
SSL_CTX_free(ctx);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SSL_CTX_use_PrivateKey_file(ctx, key_buffer, SSL_FILETYPE_PEM) != 1) {
|
|
||||||
SSL_CTX_free(ctx);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SSL_CTX_check_private_key(ctx) != 1) {
|
|
||||||
SSL_CTX_free(ctx);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
CertData *cert = &sec->certs[sec->num_certs];
|
|
||||||
if (domain.len >= (int) sizeof(cert->domain)) {
|
|
||||||
SSL_CTX_free(ctx);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
memcpy(cert->domain, domain.ptr, domain.len);
|
|
||||||
cert->domain[domain.len] = '\0';
|
|
||||||
cert->ctx = ctx;
|
|
||||||
sec->num_certs++;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
#ifndef SEC_INCLUDED
|
|
||||||
#define SEC_INCLUDED
|
|
||||||
|
|
||||||
|
|
||||||
#ifndef HTTP_AMALGAMATION
|
|
||||||
#include "basic.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef HTTPS_ENABLED
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
} SecureContext;
|
|
||||||
|
|
||||||
#else
|
|
||||||
|
|
||||||
#define MAX_CERTS 10
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
#include <openssl/ssl.h>
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
char domain[128];
|
|
||||||
SSL_CTX *ctx;
|
|
||||||
} CertData;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
|
|
||||||
bool is_server;
|
|
||||||
|
|
||||||
SSL_CTX *ctx;
|
|
||||||
|
|
||||||
// Only used when server
|
|
||||||
int num_certs;
|
|
||||||
CertData certs[MAX_CERTS];
|
|
||||||
|
|
||||||
} SecureContext;
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void secure_context_global_init(void);
|
|
||||||
void secure_context_global_free(void);
|
|
||||||
|
|
||||||
int secure_context_init_as_client(SecureContext *sec);
|
|
||||||
|
|
||||||
int secure_context_init_as_server(SecureContext *sec,
|
|
||||||
HTTP_String cert_file, HTTP_String key_file);
|
|
||||||
|
|
||||||
int secure_context_add_cert(SecureContext *sec,
|
|
||||||
HTTP_String domain, HTTP_String cert_file,
|
|
||||||
HTTP_String key_file);
|
|
||||||
|
|
||||||
void secure_context_free(SecureContext *sec);
|
|
||||||
|
|
||||||
#endif // SEC_INCLUDED
|
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
|
||||||
|
int global_secure_context_init(void)
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
int global_secure_context_free(void)
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
int client_secure_context_init(ClientSecureContext *ctx)
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
int client_secure_context_free(ClientSecureContext *ctx)
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
int server_secure_context_init(ServerSecureContext *ctx)
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
int server_secure_context_free(ServerSecureContext *ctx)
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
int server_secure_context_add_certificate(ServerSecureContext *ctx,
|
||||||
|
String domain, String cert_file)
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
|
||||||
|
int global_secure_context_init(void);
|
||||||
|
int global_secure_context_free(void);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
#ifdef HTTPS_ENABLED
|
||||||
|
// TODO
|
||||||
|
SSL_CTX *p;
|
||||||
|
#endif
|
||||||
|
} ClientSecureContext;
|
||||||
|
|
||||||
|
int client_secure_context_init(ClientSecureContext *ctx);
|
||||||
|
int client_secure_context_free(ClientSecureContext *ctx);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
|
||||||
|
} SecureDomain;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
#ifdef HTTPS_ENABLED
|
||||||
|
// TODO
|
||||||
|
SSL_CTX *p;
|
||||||
|
#endif
|
||||||
|
} ServerSecureContext;
|
||||||
|
|
||||||
|
int server_secure_context_init(ServerSecureContext *ctx);
|
||||||
|
int server_secure_context_free(ServerSecureContext *ctx);
|
||||||
|
int server_secure_context_add_certificate(ServerSecureContext *ctx,
|
||||||
|
String domain, String cert_file, String key_file);
|
||||||
+271
-253
@@ -1,307 +1,325 @@
|
|||||||
#include <stdint.h>
|
|
||||||
#include <stdarg.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
#ifndef HTTP_AMALGAMATION
|
int http_server_init(HTTP_Server *server)
|
||||||
#include "engine.h"
|
|
||||||
#include "server.h"
|
|
||||||
#include "socket_pool.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define MAX_CONNS (1<<10)
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
bool used;
|
|
||||||
uint16_t gen;
|
|
||||||
HTTP_Engine engine;
|
|
||||||
SocketHandle sock;
|
|
||||||
} Connection;
|
|
||||||
|
|
||||||
struct HTTP_Server {
|
|
||||||
|
|
||||||
SocketPool *socket_pool;
|
|
||||||
|
|
||||||
int num_conns;
|
|
||||||
Connection conns[MAX_CONNS];
|
|
||||||
|
|
||||||
int ready_head;
|
|
||||||
int ready_count;
|
|
||||||
int ready[MAX_CONNS];
|
|
||||||
};
|
|
||||||
|
|
||||||
HTTP_Server *http_server_init(HTTP_String addr, uint16_t port)
|
|
||||||
{
|
{
|
||||||
return http_server_init_ex(addr, port, 0, HTTP_STR(""), HTTP_STR(""));
|
|
||||||
}
|
|
||||||
|
|
||||||
HTTP_Server *http_server_init_ex(HTTP_String addr, uint16_t port,
|
|
||||||
uint16_t secure_port, HTTP_String cert_file, HTTP_String key_file)
|
|
||||||
{
|
|
||||||
HTTP_Server *server = malloc(sizeof(HTTP_Server));
|
|
||||||
if (server == NULL)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
int backlog = 32;
|
|
||||||
bool reuse_addr = true;
|
|
||||||
SocketPool *socket_pool = socket_pool_init(addr, port, secure_port, MAX_CONNS, reuse_addr, backlog, cert_file, key_file);
|
|
||||||
if (socket_pool == NULL) {
|
|
||||||
free(server);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
server->socket_pool = socket_pool;
|
|
||||||
server->num_conns = 0;
|
server->num_conns = 0;
|
||||||
|
for (int i = 0; i < HTTP_SERVER_CAPACITY; i++)
|
||||||
|
server->conns[i].state = HTTP_SERVER_CONN_FREE;
|
||||||
|
|
||||||
|
server->num_ready = 0;
|
||||||
server->ready_head = 0;
|
server->ready_head = 0;
|
||||||
server->ready_count = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < MAX_CONNS; i++) {
|
if (socket_manager_init(&server->sockets,
|
||||||
server->conns[i].used = false;
|
&server->socket_pool, HTTP_SERVER_CAPACITY) < 0)
|
||||||
server->conns[i].gen = 1;
|
return -1;
|
||||||
}
|
return 0;
|
||||||
|
|
||||||
return server;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void http_server_free(HTTP_Server *server)
|
void http_server_free(HTTP_Server *server)
|
||||||
{
|
{
|
||||||
for (int i = 0, j = 0; j < server->num_conns; i++) {
|
socket_manager_free(&server->sockets);
|
||||||
|
|
||||||
if (!server->conns[i].used)
|
for (int i = 0, j = 0; j < server->num_conns; i++) {
|
||||||
|
HTTP_ServerConn *conn = &server->conns[i];
|
||||||
|
if (conn->state != HTTP_SERVER_CONN_FREE)
|
||||||
continue;
|
continue;
|
||||||
j++;
|
j++;
|
||||||
|
|
||||||
// TODO
|
http_server_conn_free(conn);
|
||||||
}
|
}
|
||||||
|
|
||||||
socket_pool_free(server->socket_pool);
|
|
||||||
free(server);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int http_server_add_website(HTTP_Server *server, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file)
|
int http_server_listen_tcp(HTTP_Server *server,
|
||||||
|
String addr, Port port)
|
||||||
{
|
{
|
||||||
return socket_pool_add_cert(server->socket_pool, domain, cert_file, key_file);
|
if (socket_manager_listen_tcp(&server->sockets, addr, port) < 0)
|
||||||
}
|
return -1;
|
||||||
|
|
||||||
static void* server_memfunc(HTTP_MemoryFuncTag tag, void *ptr, int len, void *data) {
|
|
||||||
(void)data;
|
|
||||||
switch (tag) {
|
|
||||||
case HTTP_MEMFUNC_MALLOC:
|
|
||||||
return malloc(len);
|
|
||||||
case HTTP_MEMFUNC_FREE:
|
|
||||||
free(ptr);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
int http_server_wait(HTTP_Server *server, HTTP_Request **req, HTTP_ResponseBuilder *builder)
|
|
||||||
{
|
|
||||||
while (server->ready_count == 0) {
|
|
||||||
|
|
||||||
SocketEvent event = socket_pool_wait(server->socket_pool);
|
|
||||||
switch (event.type) {
|
|
||||||
|
|
||||||
case SOCKET_EVENT_DIED:
|
|
||||||
{
|
|
||||||
Connection *conn = event.user_data;
|
|
||||||
if (conn) {
|
|
||||||
http_engine_free(&conn->engine);
|
|
||||||
conn->used = false;
|
|
||||||
conn->gen++;
|
|
||||||
server->num_conns--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SOCKET_EVENT_READY:
|
|
||||||
{
|
|
||||||
Connection *conn = event.user_data;
|
|
||||||
if (conn == NULL) {
|
|
||||||
|
|
||||||
// Connection was just accepted
|
|
||||||
|
|
||||||
if (server->num_conns == MAX_CONNS) {
|
|
||||||
socket_pool_close(server->socket_pool, event.handle);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
while (server->conns[i].used)
|
|
||||||
i++;
|
|
||||||
|
|
||||||
conn = &server->conns[i];
|
|
||||||
conn->used = true;
|
|
||||||
conn->sock = event.handle;
|
|
||||||
http_engine_init(&conn->engine, 0, server_memfunc, NULL);
|
|
||||||
socket_pool_set_user_data(server->socket_pool, event.handle, conn);
|
|
||||||
server->num_conns++;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (http_engine_state(&conn->engine)) {
|
|
||||||
|
|
||||||
int len;
|
|
||||||
char *buf;
|
|
||||||
|
|
||||||
case HTTP_ENGINE_STATE_SERVER_RECV_BUF:
|
|
||||||
buf = http_engine_recvbuf(&conn->engine, &len);
|
|
||||||
if (buf) {
|
|
||||||
int ret = socket_pool_read(server->socket_pool, conn->sock, buf, len);
|
|
||||||
http_engine_recvack(&conn->engine, ret);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case HTTP_ENGINE_STATE_SERVER_SEND_BUF:
|
|
||||||
buf = http_engine_sendbuf(&conn->engine, &len);
|
|
||||||
if (buf) {
|
|
||||||
int ret = socket_pool_write(server->socket_pool, conn->sock, buf, len);
|
|
||||||
http_engine_sendack(&conn->engine, ret);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (http_engine_state(&conn->engine)) {
|
|
||||||
|
|
||||||
int tail;
|
|
||||||
|
|
||||||
case HTTP_ENGINE_STATE_SERVER_PREP_STATUS:
|
|
||||||
tail = (server->ready_head + server->ready_count) % MAX_CONNS;
|
|
||||||
server->ready[tail] = conn - server->conns;
|
|
||||||
server->ready_count++;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case HTTP_ENGINE_STATE_SERVER_CLOSED:
|
|
||||||
socket_pool_close(server->socket_pool, conn->sock);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SOCKET_EVENT_ERROR:
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
case SOCKET_EVENT_SIGNAL:
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int index = server->ready[server->ready_head];
|
|
||||||
server->ready_head = (server->ready_head + 1) % MAX_CONNS;
|
|
||||||
server->ready_count--;
|
|
||||||
|
|
||||||
*req = http_engine_getreq(&server->conns[index].engine);
|
|
||||||
(*req)->secure = socket_pool_secure(server->socket_pool, server->conns[index].sock);
|
|
||||||
|
|
||||||
*builder = (HTTP_ResponseBuilder) { server, index, server->conns[index].gen };
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Connection*
|
int http_server_listen_tls(HTTP_Server *server,
|
||||||
server_builder_to_conn(HTTP_ResponseBuilder builder)
|
String addr, Port port, String cert_file_name,
|
||||||
|
String key_file_name)
|
||||||
{
|
{
|
||||||
HTTP_Server *server = builder.data0;
|
if (socket_manager_listen_tls(&server->sockets, addr,
|
||||||
if (builder.data1 >= MAX_CONNS)
|
port, cert_file_name, key_file_name) < 0)
|
||||||
return NULL;
|
return -1;
|
||||||
|
return 0;
|
||||||
Connection *conn = &server->conns[builder.data1];
|
|
||||||
if (conn->gen != builder.data2)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
return conn;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void http_response_builder_status(HTTP_ResponseBuilder res, int status)
|
int http_server_add_certificate(HTTP_Server *server,
|
||||||
|
String domain, String cert_file, String key_file)
|
||||||
{
|
{
|
||||||
Connection *conn = server_builder_to_conn(res);
|
if (socket_manager_add_certificate(&server->sockets,
|
||||||
if (conn == NULL)
|
domain, cert_file, key_file) < 0)
|
||||||
return;
|
return -1;
|
||||||
|
return 0;
|
||||||
http_engine_status(&conn->engine, status);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void http_response_builder_header(HTTP_ResponseBuilder res, HTTP_String str)
|
int http_server_wakeup(HTTP_Server *server)
|
||||||
{
|
{
|
||||||
Connection *conn = server_builder_to_conn(res);
|
if (socket_manager_wakeup(&server->sockets) < 0)
|
||||||
if (conn == NULL)
|
return -1;
|
||||||
return;
|
return 0;
|
||||||
|
|
||||||
http_engine_header(&conn->engine, str);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void http_response_builder_body(HTTP_ResponseBuilder res, HTTP_String str)
|
int http_server_register_events(HTTP_Server *server,
|
||||||
|
struct pollfd *polled, int max_polled)
|
||||||
{
|
{
|
||||||
Connection *conn = server_builder_to_conn(res);
|
return socket_manager_register_events(&server->sockets, polled, max_polled);
|
||||||
if (conn == NULL)
|
|
||||||
return;
|
|
||||||
|
|
||||||
http_engine_body(&conn->engine, str);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void http_response_builder_bodycap(HTTP_ResponseBuilder res, int mincap)
|
// Look at the head of the input buffer to see if
|
||||||
|
// a request was buffered. If it was, change the
|
||||||
|
// connection's status to WAIT_STATUS and push it
|
||||||
|
// to the ready queue. If the request is invalid,
|
||||||
|
// close the socket.
|
||||||
|
static void
|
||||||
|
check_request_buffer(HTTP_Server *server, HTTP_ServerConn *conn)
|
||||||
{
|
{
|
||||||
Connection *conn = server_builder_to_conn(res);
|
assert(conn->state == HTTP_SERVER_CONN_BUFFERING);
|
||||||
if (conn == NULL)
|
|
||||||
return;
|
|
||||||
|
|
||||||
http_engine_bodycap(&conn->engine, mincap);
|
ByteView src = byte_queue_read_buf(&conn->input);
|
||||||
|
int ret = http_parse_request(src.ptr, src.len, &conn->request);
|
||||||
|
if (ret < 0) {
|
||||||
|
|
||||||
|
// Invalid request
|
||||||
|
byte_queue_read_ack(&conn->input, 0);
|
||||||
|
socket_close(&server->sockets, conn->handle);
|
||||||
|
|
||||||
|
} else if (ret == 0) {
|
||||||
|
|
||||||
|
// Still waiting
|
||||||
|
byte_queue_read_ack(&conn->input, 0);
|
||||||
|
|
||||||
|
// If the queue reached its limit and we still didn't receive
|
||||||
|
// a complete request, abort the exchange.
|
||||||
|
if (byte_queue_full(&conn->input))
|
||||||
|
socket_close(&server->sockets, conn->handle);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Ready
|
||||||
|
assert(ret == 1);
|
||||||
|
|
||||||
|
conn->state = HTTP_SERVER_CONN_STATUS;
|
||||||
|
conn->request_len = ret;
|
||||||
|
conn->response_offset = byte_queue_offset(&conn->output);
|
||||||
|
|
||||||
|
// Push to the ready queue
|
||||||
|
assert(server->num_ready < HTTP_SERVER_CAPACITY);
|
||||||
|
int tail = (server->ready_head + server->num_ready) % HTTP_SERVER_CAPACITY;
|
||||||
|
server->ready[tail] = conn - server->conns;
|
||||||
|
server->num_ready++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
char *http_response_builder_bodybuf(HTTP_ResponseBuilder res, int *cap)
|
bool http_server_next_request(HTTP_Server *server,
|
||||||
|
HTTP_Request **request, HTTP_ResponseBuilder *builder)
|
||||||
{
|
{
|
||||||
Connection *conn = server_builder_to_conn(res);
|
if (server->num_ready == 0)
|
||||||
if (conn == NULL) {
|
return false;
|
||||||
*cap = 0;
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return http_engine_bodybuf(&conn->engine, cap);
|
HTTP_ServerConn *conn = &server->conns[server->ready_head];
|
||||||
|
server->ready_head = (server->ready_head + 1) % HTTP_SERVER_CAPACITY;
|
||||||
|
server->num_ready--;
|
||||||
|
|
||||||
|
assert(conn->state == HTTP_SERVER_CONN_WAIT_STATUS);
|
||||||
|
*request = &conn->request;
|
||||||
|
*builder = (HTTP_ResponseBuilder) { server, conn - server->conns, conn->gen };
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void http_response_builder_bodyack(HTTP_ResponseBuilder res, int num)
|
int http_server_process_events(HTTP_Server *server,
|
||||||
|
struct pollfd *polled, int num_polled)
|
||||||
{
|
{
|
||||||
Connection *conn = server_builder_to_conn(res);
|
SocketEvent events[HTTP_SERVER_CAPACITY];
|
||||||
if (conn == NULL)
|
int num_events = socket_manger_translate_events(&server->sockets, polled, num_polled);
|
||||||
return;
|
if (num_events < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
http_engine_bodyack(&conn->engine, num);
|
for (int i = 0; i < num_events; i++) {
|
||||||
|
|
||||||
|
HTTP_ServerConn *conn = events[i].user;
|
||||||
|
|
||||||
|
if (events[i].type == SOCKET_EVENT_DISCONNECT) {
|
||||||
|
|
||||||
|
http_server_conn_free(conn);
|
||||||
|
server->num_conns--;
|
||||||
|
|
||||||
|
} else if (events[i].type == SOCKET_EVENT_READY) {
|
||||||
|
|
||||||
|
if (events[i].user == NULL) {
|
||||||
|
|
||||||
|
if (server->num_conns == HTTP_SERVER_CAPACITY) {
|
||||||
|
socket_close(&server->sockets, events[i].handle);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
while (server->conns[i].state != HTTP_SERVER_CONN_FREE) {
|
||||||
|
i++;
|
||||||
|
assert(i < HTTP_SERVER_CAPACITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
conn = &server->conns[i];
|
||||||
|
http_server_conn_init(conn, events[i].handle);
|
||||||
|
server->num_conns++;
|
||||||
|
|
||||||
|
socket_set_user(&server->sockets, events[i].handle, conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conn->state == HTTP_SERVER_CONN_BUFFERING) {
|
||||||
|
|
||||||
|
int min_recv = 1<<10;
|
||||||
|
byte_queue_write_setmincap(&conn->input, min_recv);
|
||||||
|
|
||||||
|
// Note that it's extra important that we don't
|
||||||
|
// buffer while the user is building the response.
|
||||||
|
// If we did that, a resize would invalidate all
|
||||||
|
// pointers on the parsed request structure.
|
||||||
|
int num = 0;
|
||||||
|
ByteView dst = byte_queue_write_buf(&conn->input);
|
||||||
|
if (dst.len) num = socket_recv(&server->sockets, conn->handle, dst.ptr, dst.len);
|
||||||
|
byte_queue_write_ack(&conn->input, num);
|
||||||
|
|
||||||
|
if (byte_queue_error(&conn->output))
|
||||||
|
socket_close(&server->sockets, conn->handle);
|
||||||
|
else
|
||||||
|
check_request_buffer(server, conn);
|
||||||
|
|
||||||
|
} else if (conn->state == HTTP_SERVER_CONN_FLUSHING) {
|
||||||
|
|
||||||
|
int num = 0;
|
||||||
|
ByteView src = byte_queue_read_buf(&conn->output);
|
||||||
|
if (src.len) num = socket_recv(&server->sockets, conn->handle, src.ptr, src.len);
|
||||||
|
byte_queue_read_ack(&conn->output, num);
|
||||||
|
|
||||||
|
if (byte_queue_error(&conn->output))
|
||||||
|
socket_close(&server->sockets, conn->handle);
|
||||||
|
else if (byte_queue_empty(&conn->output)) {
|
||||||
|
// We finished sending the response. Now we can
|
||||||
|
// either close the connection or process a new
|
||||||
|
// buffered request.
|
||||||
|
if (conn->closing) {
|
||||||
|
socket_close(&server->sockets, conn->handle);
|
||||||
|
} else {
|
||||||
|
check_request_buffer(server, conn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void http_response_builder_undo(HTTP_ResponseBuilder res)
|
// Get a connection pointer from a response builder.
|
||||||
|
// If the builder is invalid, returns NULL.
|
||||||
|
// Note that only connections in the responding states
|
||||||
|
// can be returned, as any builder is invalidated by
|
||||||
|
// incrementing the connection's generation counter
|
||||||
|
// when a response is completed.
|
||||||
|
static HTTP_ServerConn*
|
||||||
|
builder_to_conn(HTTP_ResponseBuilder builder)
|
||||||
{
|
{
|
||||||
Connection *conn = server_builder_to_conn(res);
|
HTTP_Server *server = builder.server;
|
||||||
if (conn == NULL)
|
if (server == NULL)
|
||||||
return;
|
return NULL;
|
||||||
|
|
||||||
http_engine_undo(&conn->engine);
|
if (server->index > HTTP_SERVER_CAPACITY)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
HTTP_ServerConn *conn = server->conns[server->index];
|
||||||
|
if (conn->gen != builder.gen)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return conn;
|
||||||
}
|
}
|
||||||
|
|
||||||
void http_response_builder_done(HTTP_ResponseBuilder res)
|
static void
|
||||||
|
write_status(HTTP_ServerConn *conn, int status)
|
||||||
{
|
{
|
||||||
HTTP_Server *server = res.data0;
|
byte_queue_write(&conn->output, xxx);
|
||||||
Connection *conn = server_builder_to_conn(res);
|
}
|
||||||
|
|
||||||
|
void http_response_builder_status(HTTP_ResponseBuilder builder, int status)
|
||||||
|
{
|
||||||
|
HTTP_ServerConn *conn = builder_to_conn(builder);
|
||||||
if (conn == NULL)
|
if (conn == NULL)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
http_engine_done(&conn->engine);
|
if (conn->state != HTTP_SERVER_CONN_WAIT_STATUS) {
|
||||||
|
// Reset all response content and start from scrach.
|
||||||
conn->gen++;
|
byte_queue_remove_from_offset(&conn->output, conn->response_offset);
|
||||||
if (conn->gen == 0 || conn->gen == UINT16_MAX)
|
conn->state = HTTP_SERVER_CONN_WAIT_STATUS;
|
||||||
conn->gen = 1;
|
|
||||||
|
|
||||||
HTTP_EngineState state = http_engine_state(&conn->engine);
|
|
||||||
|
|
||||||
if (state == HTTP_ENGINE_STATE_SERVER_PREP_STATUS) {
|
|
||||||
int tail = (server->ready_head + server->ready_count) % MAX_CONNS;
|
|
||||||
server->ready[tail] = res.data1;
|
|
||||||
server->ready_count++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state == HTTP_ENGINE_STATE_SERVER_CLOSED)
|
write_status(conn, status);
|
||||||
socket_pool_close(server->socket_pool, conn->sock);
|
|
||||||
}
|
conn->state = HTTP_SERVER_CONN_WAIT_HEADER;
|
||||||
|
}
|
||||||
|
|
||||||
|
void http_response_builder_header(HTTP_ResponseBuilder builder, String str)
|
||||||
|
{
|
||||||
|
HTTP_ServerConn *conn = builder_to_conn(builder);
|
||||||
|
if (conn == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (conn->state != HTTP_SERVER_CONN_WAIT_HEADER)
|
||||||
|
return;
|
||||||
|
|
||||||
|
byte_queue_write(&conn->output, xxx);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void append_special_headers(HTTP_ServerConn *conn)
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
static void patch_special_headers(HTTP_ServerConn *conn)
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
void http_response_builder_body(HTTP_ResponseBuilder builder, String str)
|
||||||
|
{
|
||||||
|
HTTP_ServerConn *conn = builder_to_conn(builder);
|
||||||
|
if (conn == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (conn->state != HTTP_SERVER_CONN_WAIT_HEADER) {
|
||||||
|
append_special_headers(conn);
|
||||||
|
conn->state = HTTP_SERVER_CONN_WAIT_BODY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conn->state != HTTP_SERVER_CONN_WAIT_BODY)
|
||||||
|
return;
|
||||||
|
|
||||||
|
byte_queue_write(&conn->output, str);
|
||||||
|
}
|
||||||
|
|
||||||
|
void http_response_builder_send(HTTP_ResponseBuilder builder, String str)
|
||||||
|
{
|
||||||
|
HTTP_ServerConn *conn = builder_to_conn(builder);
|
||||||
|
if (conn == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (conn->state == HTTP_SERVER_CONN_WAIT_STATUS) {
|
||||||
|
write_status(conn, 500);
|
||||||
|
conn->state = HTTP_SERVER_CONN_WAIT_HEADER;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conn->state == HTTP_SERVER_CONN_WAIT_HEADER) {
|
||||||
|
append_special_headers(conn);
|
||||||
|
conn->state = HTTP_SERVER_CONN_WAIT_BODY;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(conn->state == HTTP_SERVER_CONN_WAIT_BODY);
|
||||||
|
patch_special_headers(conn);
|
||||||
|
|
||||||
|
// Remove the buffered request
|
||||||
|
byte_queue_read_ack(&conn->input, conn->request_len);
|
||||||
|
|
||||||
|
conn->state = HTTP_SERVER_CONN_FLUSHING;
|
||||||
|
conn->gen++;
|
||||||
|
}
|
||||||
|
|||||||
+164
-25
@@ -1,35 +1,174 @@
|
|||||||
#ifndef HTTP_SERVER_INCLUDED
|
|
||||||
#define HTTP_SERVER_INCLUDED
|
|
||||||
|
|
||||||
#include <stdint.h>
|
#ifndef HTTP_SERVER_CAPACITY
|
||||||
|
// The maximum ammount of requests that can be handled
|
||||||
#ifndef HTTP_AMALGAMATION
|
// in parallel.
|
||||||
#include "parse.h"
|
#define HTTP_SERVER_CAPACITY (1<<9)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
|
||||||
|
// This struct is unused
|
||||||
|
HTTP_SERVER_CONN_FREE,
|
||||||
|
|
||||||
|
// No request was buffered yet.
|
||||||
|
HTTP_SERVER_CONN_BUFFERING,
|
||||||
|
|
||||||
|
// A request was just buffered and is waiting for
|
||||||
|
// the user to build a response. To be specific,
|
||||||
|
// it's waiting for the user to set a response status.
|
||||||
|
HTTP_SERVER_CONN_WAIT_STATUS,
|
||||||
|
|
||||||
|
// A request is buffered and a status was set. Now
|
||||||
|
// the user can set a header or append the first
|
||||||
|
// bytes of the response body.
|
||||||
|
HTTP_SERVER_CONN_WAIT_HEADER,
|
||||||
|
|
||||||
|
// A request is buffered and some bytes were appended
|
||||||
|
// to the response. Now the user can either append more
|
||||||
|
// bytes or send out the response.
|
||||||
|
HTTP_SERVER_CONN_WAIT_BODY,
|
||||||
|
|
||||||
|
// A response has been produced and it's being flushed.
|
||||||
|
HTTP_SERVER_CONN_FLUSHING,
|
||||||
|
|
||||||
|
} HTTP_ServerConnState;
|
||||||
|
|
||||||
|
// This structure represents the HTTP connection to
|
||||||
|
// a client.
|
||||||
typedef struct {
|
typedef struct {
|
||||||
void *data0;
|
|
||||||
int data1;
|
// If false, this struct is unused
|
||||||
int data2;
|
HTTP_ServerConnState state;
|
||||||
|
|
||||||
|
// Handle to the socket
|
||||||
|
SocketHandle handle;
|
||||||
|
|
||||||
|
// Data received by the client
|
||||||
|
ByteQueue input;
|
||||||
|
|
||||||
|
// Data being sent to the client
|
||||||
|
ByteQueue output;
|
||||||
|
|
||||||
|
// When the state is WAIT_STATUS, WAIT_HEADER,
|
||||||
|
// or WAIT_BODY, this contains the parsed version
|
||||||
|
// of the buffered request.
|
||||||
|
HTTP_Request request;
|
||||||
|
|
||||||
|
// Length of the buffered request when the request
|
||||||
|
// field is valid.
|
||||||
|
int request_len;
|
||||||
|
|
||||||
|
// Offset of the first response byte in the output
|
||||||
|
// buffer. This is useful when the user wants to
|
||||||
|
// undo the response it's building and start from
|
||||||
|
// scratch.
|
||||||
|
ByteQueueOffset response_offset;
|
||||||
|
|
||||||
|
} HTTP_ServerConn;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
|
||||||
|
// Array of connections. The counter contains the
|
||||||
|
// number of structs such that state=FREE.
|
||||||
|
int num_conns;
|
||||||
|
HTTP_ServerConn conns[HTTP_SERVER_CAPACITY];
|
||||||
|
|
||||||
|
// Queue of indices referring to connections that
|
||||||
|
// are in the WAIT_STATUS state.
|
||||||
|
int num_ready;
|
||||||
|
int ready_head;
|
||||||
|
int ready[HTTP_SERVER_CAPACITY];
|
||||||
|
|
||||||
|
// Asynchronous TCP and TLS socket abstraction
|
||||||
|
SocketManager sockets;
|
||||||
|
|
||||||
|
// The server object doesn't interact with this
|
||||||
|
// field directly, it just initializes the socket
|
||||||
|
// manager with a pointer to it. This allows
|
||||||
|
// allocating the exact number of sockets we
|
||||||
|
// will need.
|
||||||
|
Socket socket_pool[HTTP_SERVER_CAPACITY];
|
||||||
|
|
||||||
|
} HTTP_Server;
|
||||||
|
|
||||||
|
// Initialize the HTTP server object. By default, it won't
|
||||||
|
// listen for connections. You need to call
|
||||||
|
//
|
||||||
|
// http_server_listen_tcp
|
||||||
|
// http_server_listen_tls
|
||||||
|
//
|
||||||
|
// to listen for connection. Note that you can have a
|
||||||
|
// single server listening for HTTP and HTTPS requests
|
||||||
|
// by calling both.
|
||||||
|
int http_server_init(HTTP_Server *server);
|
||||||
|
|
||||||
|
// Release resources associated to the server.
|
||||||
|
void http_server_free(HTTP_Server *server);
|
||||||
|
|
||||||
|
// Enable listening for plain HTTP requests at the
|
||||||
|
// specified interface.
|
||||||
|
int http_server_listen_tcp(HTTP_Server *server,
|
||||||
|
String addr, Port port);
|
||||||
|
|
||||||
|
// Enable listening for HTTPS requests at the specified
|
||||||
|
// interfact, using the specified certificate and key
|
||||||
|
// to verify the connection.
|
||||||
|
int http_server_listen_tls(HTTP_Server *server,
|
||||||
|
String addr, Port port, String cert_file_name,
|
||||||
|
String key_file_name);
|
||||||
|
|
||||||
|
// Add the certificate for an additional domain when
|
||||||
|
// the server is listening for HTTPS requests.
|
||||||
|
int http_server_add_certificate(HTTP_Server *server,
|
||||||
|
String domain, String cert_file, String key_file);
|
||||||
|
|
||||||
|
// When a thread is blocked waiting for server events,
|
||||||
|
// other threads can call this function to wake it up.
|
||||||
|
int http_server_wakeup(HTTP_Server *server);
|
||||||
|
|
||||||
|
// List all low-level socket events the server is
|
||||||
|
// waiting for such that the caller can call poll()
|
||||||
|
// with it.
|
||||||
|
int http_server_register_events(HTTP_Server *server,
|
||||||
|
struct pollfd *polled, int max_polled);
|
||||||
|
|
||||||
|
// The caller has waited for poll() to return and some
|
||||||
|
// I/O events to be triggered, so now the HTTP server
|
||||||
|
// can continue its buffering and flushing operations.
|
||||||
|
int http_server_process_events(HTTP_Server *server,
|
||||||
|
struct pollfd *polled, int num_polled);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
HTTP_Server *server;
|
||||||
|
uint16_t index;
|
||||||
|
uint16_t gen;
|
||||||
} HTTP_ResponseBuilder;
|
} HTTP_ResponseBuilder;
|
||||||
|
|
||||||
typedef struct HTTP_Server HTTP_Server;
|
// After some I/O events were processes, some requests
|
||||||
|
// may be availabe. This function returns one of the
|
||||||
|
// buffered requests. If a request was available, true
|
||||||
|
// is returned. If no more are avaiable, false is returned.
|
||||||
|
// Note that It's possible to get multiple requests to
|
||||||
|
// respond in batches.
|
||||||
|
// For each request returned by this function, the user
|
||||||
|
// must build a response using the response builder API.
|
||||||
|
bool http_server_next_request(HTTP_Server *server,
|
||||||
|
HTTP_Request **request, HTTP_ResponseBuilder *builder);
|
||||||
|
|
||||||
HTTP_Server *http_server_init(HTTP_String addr, uint16_t port);
|
// This function is called to set the status code of
|
||||||
|
// a request's response. If this function is called
|
||||||
|
// after the other response builder functions, it will
|
||||||
|
// reset the response and set a new status.
|
||||||
|
void http_response_builder_status(HTTP_ResponseBuilder builder, int status);
|
||||||
|
|
||||||
HTTP_Server *http_server_init_ex(HTTP_String addr, uint16_t port,
|
// Append a header to the response. This can only be
|
||||||
uint16_t secure_port, HTTP_String cert_key, HTTP_String private_key);
|
// used after having set the status and before appending
|
||||||
|
// to the body.
|
||||||
|
void http_response_builder_header(HTTP_ResponseBuilder builder, String str);
|
||||||
|
|
||||||
void http_server_free (HTTP_Server *server);
|
// Append some bytes to the response's body
|
||||||
int http_server_wait (HTTP_Server *server, HTTP_Request **req, HTTP_ResponseBuilder *handle);
|
void http_response_builder_body(HTTP_ResponseBuilder builder, String str);
|
||||||
int http_server_add_website (HTTP_Server *server, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file);
|
|
||||||
void http_response_builder_status (HTTP_ResponseBuilder res, int status);
|
|
||||||
void http_response_builder_header (HTTP_ResponseBuilder res, HTTP_String str);
|
|
||||||
void http_response_builder_body (HTTP_ResponseBuilder res, HTTP_String str);
|
|
||||||
void http_response_builder_bodycap (HTTP_ResponseBuilder res, int mincap);
|
|
||||||
char* http_response_builder_bodybuf (HTTP_ResponseBuilder res, int *cap);
|
|
||||||
void http_response_builder_bodyack (HTTP_ResponseBuilder res, int num);
|
|
||||||
void http_response_builder_undo (HTTP_ResponseBuilder res);
|
|
||||||
void http_response_builder_done (HTTP_ResponseBuilder res);
|
|
||||||
|
|
||||||
#endif // HTTP_SERVER_INCLUDED
|
// Mark the response as complete. This will invalidate
|
||||||
|
// the response builder handle.
|
||||||
|
void http_response_builder_send(HTTP_ResponseBuilder builder, String str);
|
||||||
|
|||||||
+777
-644
File diff suppressed because it is too large
Load Diff
+282
-55
@@ -1,75 +1,302 @@
|
|||||||
#ifndef SOCKET_INCLUDED
|
// This file (and its relative .c file) implements an asynchronous TCP/TLS
|
||||||
#define SOCKET_INCLUDED
|
// server and client abstraction.
|
||||||
|
//
|
||||||
|
// It introduces the concept of a "socket manager", which is a pool of
|
||||||
|
// connection sockets and a listener socket. The listener is managed
|
||||||
|
// internally, which means the manager automatically accepts sockets
|
||||||
|
// from it and adds them to the pool.
|
||||||
|
//
|
||||||
|
// If the listener is configured using the function:
|
||||||
|
//
|
||||||
|
// socket_manager_listen_tcp
|
||||||
|
//
|
||||||
|
// the resulting connections will not use TLS. If instead the listener
|
||||||
|
// is configured using:
|
||||||
|
//
|
||||||
|
// socket_manager_listen_tls
|
||||||
|
//
|
||||||
|
// the listener will use TLS. Note that both functions can be used on
|
||||||
|
// the same manager to allow both plaintext and encrypted connections.
|
||||||
|
// Users may enable zero listeners, in which case only outgoing
|
||||||
|
// connections are allowed (more on this later).
|
||||||
|
//
|
||||||
|
// Once the manager is set up, one can wait for events by following
|
||||||
|
// this pattern:
|
||||||
|
//
|
||||||
|
// struct pollfd polled[...];
|
||||||
|
// int num_polled = socket_manager_register_events(sm, polled, max_polled);
|
||||||
|
// poll(polled, num_polled, -1);
|
||||||
|
//
|
||||||
|
// #define MAX_EVENTS ...
|
||||||
|
// SocketEvent events[MAX_EVENTS];
|
||||||
|
// int num_events = socket_manager_translate_events(sm, events, MAX_EVENTS, polled, num_polled);
|
||||||
|
// for (int i = 0; i < num_events; i++) {
|
||||||
|
// ... Here call socket_recv, socket_send, socket_close, ...
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Note that from the user's perspective, there is no difference
|
||||||
|
// between connections that use plain TCP and those that use TCP/TLS.
|
||||||
|
//
|
||||||
|
// Users can also establish outgoing connections by calling the
|
||||||
|
// function:
|
||||||
|
//
|
||||||
|
// socket_connect
|
||||||
|
//
|
||||||
|
// Which allows the creation of a connection towards an host given
|
||||||
|
// its domain, IPv4, IPv6, or an array of them. This can be done both
|
||||||
|
// for TCP and TCP/TLS connection. Note that users that only intend
|
||||||
|
// to establish outgoing connection may omit the configuration of
|
||||||
|
// listeners entirely.
|
||||||
|
|
||||||
// This module implements the socket state machine to encapsulate
|
#ifdef _WIN32
|
||||||
// the complexity of non-blocking TCP and TLS sockets.
|
#define NATIVE_SOCKET SOCKET
|
||||||
//
|
#define NATIVE_SOCKET_INVALID SOCKET_ERROR
|
||||||
// A socket is represented by the "Socket" structure, which may
|
#define CLOSE_NATIVE_SOCKET closesocket
|
||||||
// be in a number of states. As far as an user of the interface
|
#else
|
||||||
// is concerned, the socket may be DIED, READY, or in an internal
|
#define NATIVE_SOCKET int
|
||||||
// state that requires waiting for an event. Therefore, if the
|
#define NATIVE_SOCKET_INVALID -1
|
||||||
// socket is not DIED or READY, the user needs to wait for the
|
#define CLOSE_NATIVE_SOCKET close
|
||||||
// events specified in the [socket->events] field, then call the
|
|
||||||
// socket_update function. At some point the socket will become
|
|
||||||
// either READY or DIED.
|
|
||||||
//
|
|
||||||
// When the socket reaches the DIED state, the user must call
|
|
||||||
// socket_free.
|
|
||||||
//
|
|
||||||
// If the socket is ESTABLISHED_READY, the user may call socket_read,
|
|
||||||
// socket_write, or socket_close on it.
|
|
||||||
|
|
||||||
#ifndef HTTP_AMALGAMATION
|
|
||||||
#include "sec.h"
|
|
||||||
#include "parse.h"
|
|
||||||
#include "socket_raw.h"
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
typedef struct PendingConnect PendingConnect;
|
typedef uint32_t SocketHandle;
|
||||||
|
#define SOCKET_HANDLE_INVALID ((SocketHandle) 0)
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char *ptr;
|
||||||
|
int len;
|
||||||
|
} String;
|
||||||
|
|
||||||
|
typedef uint16_t Port;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint32_t data;
|
||||||
|
} IPv4;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint16_t data[8];
|
||||||
|
} IPv6;
|
||||||
|
|
||||||
// These should only be relevant to socket.c
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
|
SOCKET_EVENT_READY,
|
||||||
|
SOCKET_EVENT_DISCONNECT,
|
||||||
|
} SocketEventType;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
SocketEventType type;
|
||||||
|
SocketHandle handle;
|
||||||
|
void* user;
|
||||||
|
} SocketEvent;
|
||||||
|
|
||||||
|
// Internal use only
|
||||||
|
typedef enum {
|
||||||
|
|
||||||
|
// The Socket struct is unused
|
||||||
SOCKET_STATE_FREE,
|
SOCKET_STATE_FREE,
|
||||||
SOCKET_STATE_DIED,
|
|
||||||
SOCKET_STATE_ESTABLISHED_WAIT,
|
// The state associated to a socket created
|
||||||
SOCKET_STATE_ESTABLISHED_READY,
|
// by a connect operation that hasn't been
|
||||||
|
// processed yet.
|
||||||
SOCKET_STATE_PENDING,
|
SOCKET_STATE_PENDING,
|
||||||
SOCKET_STATE_ACCEPTED,
|
|
||||||
SOCKET_STATE_CONNECTED,
|
// A connect() operation was started but is
|
||||||
|
// still pending.
|
||||||
SOCKET_STATE_CONNECTING,
|
SOCKET_STATE_CONNECTING,
|
||||||
|
|
||||||
|
// The connection was estabished, but a TLS
|
||||||
|
// handshake may need to be performed.
|
||||||
|
SOCKET_STATE_CONNECTED,
|
||||||
|
|
||||||
|
// The connection was esablished, but the user
|
||||||
|
// wants to perform a read or write operation that
|
||||||
|
// would block.
|
||||||
|
SOCKET_STATE_ESTABLISHED_WAIT,
|
||||||
|
|
||||||
|
// The connection was established and it's possible
|
||||||
|
// to perform read or write operations on it without
|
||||||
|
// blocking.
|
||||||
|
SOCKET_STATE_ESTABLISHED_READY,
|
||||||
|
|
||||||
|
// The socket was marked to be closed.
|
||||||
SOCKET_STATE_SHUTDOWN,
|
SOCKET_STATE_SHUTDOWN,
|
||||||
|
|
||||||
|
// The current socket is was closed. The only
|
||||||
|
// valid thing to do here is free its resources.
|
||||||
|
SOCKET_STATE_DIED,
|
||||||
} SocketState;
|
} SocketState;
|
||||||
|
|
||||||
|
// Internal use only
|
||||||
|
typedef struct {
|
||||||
|
union {
|
||||||
|
IPv4 ipv4;
|
||||||
|
IPv6 ipv6;
|
||||||
|
};
|
||||||
|
bool is_ipv4;
|
||||||
|
Port port;
|
||||||
|
} AddressAndPort;
|
||||||
|
|
||||||
|
// Internal use only
|
||||||
typedef struct {
|
typedef struct {
|
||||||
SocketState state;
|
SocketState state;
|
||||||
|
|
||||||
RAW_SOCKET raw;
|
// OS-specific socket type
|
||||||
|
NATIVE_SOCKET sock;
|
||||||
|
|
||||||
|
// Native socket events that need to be monitored
|
||||||
int events;
|
int events;
|
||||||
|
|
||||||
void *user_data;
|
// User-provided context pointer
|
||||||
PendingConnect *pending_connect;
|
void *user;
|
||||||
|
|
||||||
#ifdef HTTPS_ENABLED
|
|
||||||
SSL *ssl;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
SecureContext *sec;
|
|
||||||
|
|
||||||
|
// A single connect operation may involve
|
||||||
|
// trying to establish a connection towards
|
||||||
|
// one of a set of addresses.
|
||||||
|
int num_addr;
|
||||||
|
int next_addr;
|
||||||
|
union {
|
||||||
|
AddressAndPort addr; // When num_addr=1
|
||||||
|
AddressAndPort *addrs; // Dynamically allocated when num_addr>1
|
||||||
|
};
|
||||||
} Socket;
|
} Socket;
|
||||||
|
|
||||||
void socket_connect(Socket *sock, SecureContext *sec, HTTP_String hostname, uint16_t port, void *user_data);
|
// Glorified array of sockets. This structure
|
||||||
void socket_connect_ipv4(Socket *sock, SecureContext *sec, HTTP_IPv4 addr, uint16_t port, void *user_data);
|
// is private to the .c file associated to this
|
||||||
void socket_connect_ipv6(Socket *sock, SecureContext *sec, HTTP_IPv6 addr, uint16_t port, void *user_data);
|
// header.
|
||||||
void socket_accept(Socket *sock, SecureContext *sec, RAW_SOCKET raw);
|
typedef struct {
|
||||||
void socket_update(Socket *sock);
|
|
||||||
void socket_close(Socket *sock);
|
|
||||||
bool socket_ready(Socket *sock);
|
|
||||||
bool socket_died(Socket *sock);
|
|
||||||
int socket_read(Socket *sock, char *dst, int max);
|
|
||||||
int socket_write(Socket *sock, char *src, int len);
|
|
||||||
void socket_free(Socket *sock);
|
|
||||||
bool socket_secure(Socket *sock);
|
|
||||||
void socket_set_user_data(Socket *sock, void *user_data);
|
|
||||||
void* socket_get_user_data(Socket *sock);
|
|
||||||
|
|
||||||
#endif // SOCKET_INCLUDED
|
// This guards access to the main thread using
|
||||||
|
// the manager from other threads calling the
|
||||||
|
// wakeup function.
|
||||||
|
Mutex mutex;
|
||||||
|
|
||||||
|
// TCP listener sockets. The first is intended
|
||||||
|
// for plaintext, while the second is for TLS.
|
||||||
|
// The socket manager will accept and add new
|
||||||
|
// sockets to the pool automatically. Note that
|
||||||
|
// either may be unset. If both are unset, users
|
||||||
|
// can only create outgoing connections.
|
||||||
|
NATIVE_SOCKET plain_sock;
|
||||||
|
NATIVE_SOCKET secure_sock;
|
||||||
|
|
||||||
|
// Handles for the self-pipe trick necessary for
|
||||||
|
// other threads to wake up sockets blocked on
|
||||||
|
// poll().
|
||||||
|
NATIVE_SOCKET wait_sock;
|
||||||
|
NATIVE_SOCKET signal_sock;
|
||||||
|
|
||||||
|
// TLS contexts. One is used for outgoing connections
|
||||||
|
// (the client context) and one for incoming
|
||||||
|
// connections (server). If the secure_sock is
|
||||||
|
// set, the server context is initialized. If at
|
||||||
|
// least one connect was performed using TLS
|
||||||
|
// (and the flag is set), the client context is
|
||||||
|
// initialized.
|
||||||
|
bool at_least_one_secure_connect;
|
||||||
|
ClientSecureContext client_secure_context;
|
||||||
|
ServerSecureContext server_secure_context;
|
||||||
|
|
||||||
|
// Array of sockets. Structs with state FREE
|
||||||
|
// are unused.
|
||||||
|
int num_used;
|
||||||
|
int max_used;
|
||||||
|
Socket *sockets;
|
||||||
|
|
||||||
|
} SocketManager;
|
||||||
|
|
||||||
|
// Instanciate a socket manager. Returns 0 on
|
||||||
|
// success and -1 on error.
|
||||||
|
int socket_manager_init(SocketManager *sm, Socket *socks,
|
||||||
|
int num_socks);
|
||||||
|
|
||||||
|
// Deinitialize a socket manager
|
||||||
|
void socket_manager_free(SocketManager *sm);
|
||||||
|
|
||||||
|
// Configure the socket manager to listen on
|
||||||
|
// the specified interface for TCP connections.
|
||||||
|
// Incoming connections will be automatically
|
||||||
|
// added to the internal pool. This function
|
||||||
|
// can only be used once per manager.
|
||||||
|
// Returns 0 on success, -1 on error.
|
||||||
|
int socket_manager_listen_tcp(SocketManager *sm,
|
||||||
|
String addr, Port port);
|
||||||
|
|
||||||
|
// Same as the previous function, but incoming
|
||||||
|
// connections will be interpreted as TLS. You
|
||||||
|
// can only call this function once per manager,
|
||||||
|
// but you can call this and the plaintext variant
|
||||||
|
// on the same manager to accept both plaintext
|
||||||
|
// and secure connections.
|
||||||
|
// Returns 0 on success, -1 on error.
|
||||||
|
int socket_manager_listen_tls(SocketManager *sm,
|
||||||
|
String addr, Port port, String cert_file_name,
|
||||||
|
String key_file_name);
|
||||||
|
|
||||||
|
// If the socket manager was configures to accept
|
||||||
|
// TLS connections, this adds additional certificates
|
||||||
|
// the client can use to verify the server's
|
||||||
|
// authenticity.
|
||||||
|
// Returns 0 on success, -1 on error.
|
||||||
|
int socket_manager_add_certificate(SocketManager *sm,
|
||||||
|
String domain, String cert_file, String key_file);
|
||||||
|
|
||||||
|
// When a thread is blocked on a poll() call for
|
||||||
|
// descriptors associated to this socket manager,
|
||||||
|
// other threads can call this function to wake
|
||||||
|
// up that blocked thread.
|
||||||
|
// Returns 0 on success, -1 on error.
|
||||||
|
int socket_manager_wakeup(SocketManager *sm);
|
||||||
|
|
||||||
|
// Writes to the polled array the list of sockets
|
||||||
|
// that the socket manager is monitoring and which
|
||||||
|
// events associated to that socket.
|
||||||
|
// Returns the number of items written to the polled
|
||||||
|
// array.
|
||||||
|
int socket_manager_register_events(SocketManager *sm,
|
||||||
|
struct pollfd *polled, int max_polled);
|
||||||
|
|
||||||
|
// After poll() is called on the previously registered
|
||||||
|
// pollfd array and the revents fields are set, this
|
||||||
|
// function processes those events to produce higher-level
|
||||||
|
// socket events. Returns the number of socket events
|
||||||
|
// written to the output array, or -1 on error.
|
||||||
|
int socket_manager_translate_events(SocketManager *sm,
|
||||||
|
SocketEvent *events, int max_events, struct pollfd *polled,
|
||||||
|
int num_polled);
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
CONNECT_TARGET_NAME,
|
||||||
|
CONNECT_TARGET_IPV4,
|
||||||
|
CONNECT_TARGET_IPV6,
|
||||||
|
} ConnectTargetType;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
ConnectTargetType type;
|
||||||
|
Port port;
|
||||||
|
union {
|
||||||
|
IPv4 ipv4;
|
||||||
|
IPv6 ipv6;
|
||||||
|
String name;
|
||||||
|
};
|
||||||
|
} ConnectTarget;
|
||||||
|
|
||||||
|
// Connect to one of the given targets. The socket
|
||||||
|
// manager will try to connecting to addresses until
|
||||||
|
// one succedes. If secure=true, the socket uses TLS.
|
||||||
|
// Returns 0 on success, -1 on error.
|
||||||
|
int socket_connect(SocketManager *sm, int num_targets,
|
||||||
|
ConnectTarget *targets, bool secure, void *user);
|
||||||
|
|
||||||
|
int socket_recv(SocketManager *sm, SocketHandle handle,
|
||||||
|
char *dst, int max);
|
||||||
|
|
||||||
|
int socket_send(SocketManager *sm, SocketHandle handle,
|
||||||
|
char *src, int len);
|
||||||
|
|
||||||
|
void socket_close(SocketManager *sm, SocketHandle handle);
|
||||||
|
|
||||||
|
// Returns -1 on error, 0 if the socket was accepted
|
||||||
|
// from the plaintext listener, or 1 if it was accepted
|
||||||
|
// by the secure listener.
|
||||||
|
int socket_is_secure(SocketManager *sm, SocketHandle handle);
|
||||||
|
|
||||||
|
// Set the user pointer of a socket
|
||||||
|
void socket_set_user(SocketManager *sm, SocketHandle handle);
|
||||||
|
|||||||
@@ -1,357 +0,0 @@
|
|||||||
#include <assert.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
#ifdef __linux__
|
|
||||||
#include <errno.h>
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef HTTP_AMALGAMATION
|
|
||||||
#include "socket_pool.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define SOCKET_HARD_LIMIT (1<<10)
|
|
||||||
#define MAX_CERTS 10
|
|
||||||
|
|
||||||
struct SocketPool {
|
|
||||||
|
|
||||||
SecureContext sec;
|
|
||||||
|
|
||||||
RAW_SOCKET listen_sock;
|
|
||||||
RAW_SOCKET secure_sock;
|
|
||||||
|
|
||||||
int num_socks;
|
|
||||||
int max_socks;
|
|
||||||
Socket socks[];
|
|
||||||
};
|
|
||||||
|
|
||||||
int socket_pool_global_init(void)
|
|
||||||
{
|
|
||||||
int ret = socket_raw_global_init();
|
|
||||||
if (ret < 0)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
secure_context_global_init();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void socket_pool_global_free(void)
|
|
||||||
{
|
|
||||||
secure_context_global_free();
|
|
||||||
socket_raw_global_free();
|
|
||||||
}
|
|
||||||
|
|
||||||
SocketPool *socket_pool_init(HTTP_String addr,
|
|
||||||
uint16_t port, uint16_t secure_port, int max_socks,
|
|
||||||
bool reuse_addr, int backlog, HTTP_String cert_file,
|
|
||||||
HTTP_String key_file)
|
|
||||||
{
|
|
||||||
if (max_socks > SOCKET_HARD_LIMIT)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
SocketPool *pool = malloc(sizeof(SocketPool) + max_socks * sizeof(Socket));
|
|
||||||
if (pool == NULL)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
pool->num_socks = 0;
|
|
||||||
pool->max_socks = max_socks;
|
|
||||||
|
|
||||||
for (int i = 0; i < pool->max_socks; i++)
|
|
||||||
pool->socks[i].state = SOCKET_STATE_FREE;
|
|
||||||
|
|
||||||
if (port == 0)
|
|
||||||
pool->listen_sock = BAD_SOCKET;
|
|
||||||
else {
|
|
||||||
pool->listen_sock = listen_socket(addr, port, reuse_addr, backlog);
|
|
||||||
if (pool->listen_sock == BAD_SOCKET) {
|
|
||||||
free(pool);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (secure_port == 0)
|
|
||||||
pool->secure_sock = BAD_SOCKET;
|
|
||||||
else {
|
|
||||||
#ifndef HTTPS_ENABLED
|
|
||||||
(void) cert_file;
|
|
||||||
(void) key_file;
|
|
||||||
if (pool->listen_sock != BAD_SOCKET)
|
|
||||||
CLOSE_SOCKET(pool->listen_sock);
|
|
||||||
free(pool);
|
|
||||||
return NULL;
|
|
||||||
#else
|
|
||||||
if (secure_context_init_as_server(&pool->sec, cert_file, key_file) < 0) {
|
|
||||||
if (pool->listen_sock != BAD_SOCKET)
|
|
||||||
CLOSE_SOCKET(pool->listen_sock);
|
|
||||||
free(pool);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
pool->secure_sock = listen_socket(addr, secure_port, reuse_addr, backlog);
|
|
||||||
if (pool->secure_sock == BAD_SOCKET) {
|
|
||||||
if (pool->listen_sock != BAD_SOCKET) CLOSE_SOCKET(pool->listen_sock);
|
|
||||||
free(pool);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef HTTPS_ENABLED
|
|
||||||
if (port == 0 && secure_port == 0) {
|
|
||||||
if (secure_context_init_as_client(&pool->sec) < 0) {
|
|
||||||
if (pool->listen_sock != BAD_SOCKET) CLOSE_SOCKET(pool->listen_sock);
|
|
||||||
if (pool->secure_sock != BAD_SOCKET) CLOSE_SOCKET(pool->secure_sock);
|
|
||||||
free(pool);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
for (int i = 0; i < max_socks; i++)
|
|
||||||
pool->socks[i].state = SOCKET_STATE_FREE;
|
|
||||||
|
|
||||||
return pool;
|
|
||||||
}
|
|
||||||
|
|
||||||
void socket_pool_free(SocketPool *pool)
|
|
||||||
{
|
|
||||||
for (int i = 0, j = 0; j < pool->num_socks; i++) {
|
|
||||||
|
|
||||||
Socket *sock = &pool->socks[i];
|
|
||||||
|
|
||||||
if (sock->state == SOCKET_STATE_FREE)
|
|
||||||
continue;
|
|
||||||
j++;
|
|
||||||
|
|
||||||
socket_free(sock);
|
|
||||||
}
|
|
||||||
|
|
||||||
secure_context_free(&pool->sec);
|
|
||||||
|
|
||||||
if (pool->secure_sock != BAD_SOCKET) CLOSE_SOCKET(pool->secure_sock);
|
|
||||||
if (pool->listen_sock != BAD_SOCKET) CLOSE_SOCKET(pool->listen_sock);
|
|
||||||
}
|
|
||||||
|
|
||||||
int socket_pool_add_cert(SocketPool *pool, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file)
|
|
||||||
{
|
|
||||||
return secure_context_add_cert(&pool->sec, domain, cert_file, key_file);
|
|
||||||
}
|
|
||||||
|
|
||||||
void socket_pool_set_user_data(SocketPool *pool, SocketHandle handle, void *user_data)
|
|
||||||
{
|
|
||||||
Socket *sock = &pool->socks[handle];
|
|
||||||
socket_set_user_data(sock, user_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
void socket_pool_close(SocketPool *pool, SocketHandle handle)
|
|
||||||
{
|
|
||||||
Socket *sock = &pool->socks[handle];
|
|
||||||
socket_close(sock);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Socket *find_free_socket(SocketPool *pool)
|
|
||||||
{
|
|
||||||
if (pool->num_socks == pool->max_socks)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
while (pool->socks[i].state != SOCKET_STATE_FREE)
|
|
||||||
i++;
|
|
||||||
|
|
||||||
return &pool->socks[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
int socket_pool_connect(SocketPool *pool, bool secure,
|
|
||||||
HTTP_String addr, uint16_t port, void *user_data)
|
|
||||||
{
|
|
||||||
Socket *sock = find_free_socket(pool);
|
|
||||||
if (sock == NULL)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
socket_connect(sock, secure ? &pool->sec : NULL, addr, port, user_data);
|
|
||||||
|
|
||||||
if (socket_died(sock)) {
|
|
||||||
socket_free(sock);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
pool->num_socks++;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int socket_pool_connect_ipv4(SocketPool *pool, bool secure,
|
|
||||||
HTTP_IPv4 addr, uint16_t port, void *user_data)
|
|
||||||
{
|
|
||||||
Socket *sock = find_free_socket(pool);
|
|
||||||
if (sock == NULL)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
socket_connect_ipv4(sock, secure ? &pool->sec : NULL, addr, port, user_data);
|
|
||||||
|
|
||||||
if (socket_died(sock)) {
|
|
||||||
socket_free(sock);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
pool->num_socks++;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int socket_pool_connect_ipv6(SocketPool *pool, bool secure,
|
|
||||||
HTTP_IPv6 addr, uint16_t port, void *user_data)
|
|
||||||
{
|
|
||||||
Socket *sock = find_free_socket(pool);
|
|
||||||
if (sock == NULL)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
socket_connect_ipv6(sock, secure ? &pool->sec : NULL, addr, port, user_data);
|
|
||||||
|
|
||||||
if (socket_died(sock)) {
|
|
||||||
socket_free(sock);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
pool->num_socks++;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#include <stdio.h> // TODO: remove
|
|
||||||
|
|
||||||
SocketEvent socket_pool_wait(SocketPool *pool)
|
|
||||||
{
|
|
||||||
for (;;) {
|
|
||||||
|
|
||||||
// First, iterate over all sockets to find one that
|
|
||||||
// died or is ready.
|
|
||||||
|
|
||||||
for (int i = 0, j = 0; j < pool->num_socks; i++) {
|
|
||||||
|
|
||||||
Socket *sock = &pool->socks[i];
|
|
||||||
|
|
||||||
if (sock->state == SOCKET_STATE_FREE)
|
|
||||||
continue;
|
|
||||||
j++;
|
|
||||||
|
|
||||||
if (socket_died(sock)) {
|
|
||||||
void *user_data = socket_get_user_data(sock);
|
|
||||||
socket_free(sock);
|
|
||||||
pool->num_socks--;
|
|
||||||
return (SocketEvent) { SOCKET_EVENT_DIED, -1, user_data };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (socket_ready(sock))
|
|
||||||
return (SocketEvent) { SOCKET_EVENT_READY, i, socket_get_user_data(sock) };
|
|
||||||
|
|
||||||
assert(sock->events);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we reached this point, we either have no sockets
|
|
||||||
// or all sockets need to wait for some event. Waiting
|
|
||||||
// when no sockets are available is only allowed when
|
|
||||||
// the pool is in server mode.
|
|
||||||
|
|
||||||
int indices[SOCKET_HARD_LIMIT+2];
|
|
||||||
struct pollfd polled[SOCKET_HARD_LIMIT+2];
|
|
||||||
int num_polled = 0;
|
|
||||||
|
|
||||||
if (pool->num_socks < pool->max_socks) {
|
|
||||||
|
|
||||||
if (pool->listen_sock != BAD_SOCKET) {
|
|
||||||
indices[num_polled] = -1;
|
|
||||||
polled[num_polled].fd = pool->listen_sock;
|
|
||||||
polled[num_polled].events = POLLIN;
|
|
||||||
polled[num_polled].revents = 0;
|
|
||||||
num_polled++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pool->secure_sock != BAD_SOCKET) {
|
|
||||||
indices[num_polled] = -1;
|
|
||||||
polled[num_polled].fd = pool->secure_sock;
|
|
||||||
polled[num_polled].events = POLLIN;
|
|
||||||
polled[num_polled].revents = 0;
|
|
||||||
num_polled++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0, j = 0; j < pool->num_socks; i++) {
|
|
||||||
|
|
||||||
Socket *sock = &pool->socks[i];
|
|
||||||
|
|
||||||
if (sock->state == SOCKET_STATE_FREE)
|
|
||||||
continue;
|
|
||||||
j++;
|
|
||||||
|
|
||||||
indices[num_polled] = i;
|
|
||||||
polled[num_polled].fd = sock->raw;
|
|
||||||
polled[num_polled].events = sock->events;
|
|
||||||
polled[num_polled].revents = 0;
|
|
||||||
num_polled++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (num_polled == 0)
|
|
||||||
return (SocketEvent) { SOCKET_EVENT_ERROR, -1, NULL };
|
|
||||||
|
|
||||||
int ret = POLL(polled, num_polled, -1);
|
|
||||||
if (ret < 0) {
|
|
||||||
|
|
||||||
if (errno == EINTR)
|
|
||||||
return (SocketEvent) { SOCKET_EVENT_SIGNAL, -1, NULL };
|
|
||||||
|
|
||||||
return (SocketEvent) { SOCKET_EVENT_ERROR, -1, NULL };
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < num_polled; i++) {
|
|
||||||
|
|
||||||
Socket *sock;
|
|
||||||
|
|
||||||
if (polled[i].fd == pool->listen_sock || polled[i].fd == pool->secure_sock) {
|
|
||||||
|
|
||||||
bool secure = false;
|
|
||||||
if (polled[i].fd == pool->secure_sock)
|
|
||||||
secure = true;
|
|
||||||
|
|
||||||
Socket *sock = find_free_socket(pool);
|
|
||||||
if (sock == NULL)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
RAW_SOCKET raw = accept(polled[i].fd, NULL, NULL);
|
|
||||||
if (raw == BAD_SOCKET)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
socket_accept(sock, secure ? &pool->sec : NULL, raw);
|
|
||||||
|
|
||||||
if (socket_died(sock)) {
|
|
||||||
socket_free(sock);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
pool->num_socks++;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
int j = indices[i];
|
|
||||||
sock = &pool->socks[j];
|
|
||||||
|
|
||||||
if (polled[i].revents)
|
|
||||||
socket_update(sock);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This branch is unreachable
|
|
||||||
return (SocketEvent) { SOCKET_EVENT_ERROR, -1, NULL };
|
|
||||||
}
|
|
||||||
|
|
||||||
int socket_pool_read(SocketPool *pool, SocketHandle handle, char *dst, int len)
|
|
||||||
{
|
|
||||||
return socket_read(&pool->socks[handle], dst, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
int socket_pool_write(SocketPool *pool, SocketHandle handle, char *src, int len)
|
|
||||||
{
|
|
||||||
return socket_write(&pool->socks[handle], src, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool socket_pool_secure(SocketPool *pool, SocketHandle handle)
|
|
||||||
{
|
|
||||||
return socket_secure(&pool->socks[handle]);
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
#ifndef SOCKET_POOL_INCLUDED
|
|
||||||
#define SOCKET_POOL_INCLUDED
|
|
||||||
|
|
||||||
#ifndef HTTP_AMALGAMATION
|
|
||||||
#include "basic.h"
|
|
||||||
#include "socket.h"
|
|
||||||
#include "socket_raw.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
typedef struct SocketPool SocketPool;
|
|
||||||
|
|
||||||
typedef int SocketHandle;
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
SOCKET_EVENT_DIED,
|
|
||||||
SOCKET_EVENT_READY,
|
|
||||||
SOCKET_EVENT_ERROR,
|
|
||||||
SOCKET_EVENT_SIGNAL,
|
|
||||||
} SocketEventType;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
SocketEventType type;
|
|
||||||
SocketHandle handle;
|
|
||||||
void *user_data;
|
|
||||||
} SocketEvent;
|
|
||||||
|
|
||||||
int socket_pool_global_init(void);
|
|
||||||
void socket_pool_global_free(void);
|
|
||||||
|
|
||||||
SocketPool *socket_pool_init(HTTP_String addr,
|
|
||||||
uint16_t port, uint16_t secure_port, int max_socks,
|
|
||||||
bool reuse_addr, int backlog, HTTP_String cert_file,
|
|
||||||
HTTP_String key_file);
|
|
||||||
|
|
||||||
void socket_pool_free(SocketPool *pool);
|
|
||||||
|
|
||||||
int socket_pool_add_cert(SocketPool *pool, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file);
|
|
||||||
|
|
||||||
SocketEvent socket_pool_wait(SocketPool *pool);
|
|
||||||
|
|
||||||
void socket_pool_set_user_data(SocketPool *pool, SocketHandle handle, void *user_data);
|
|
||||||
|
|
||||||
void socket_pool_close(SocketPool *pool, SocketHandle handle);
|
|
||||||
|
|
||||||
int socket_pool_connect(SocketPool *pool, bool secure,
|
|
||||||
HTTP_String addr, uint16_t port, void *user_data);
|
|
||||||
|
|
||||||
int socket_pool_connect_ipv4(SocketPool *pool, bool secure,
|
|
||||||
HTTP_IPv4 addr, uint16_t port, void *user_data);
|
|
||||||
|
|
||||||
int socket_pool_connect_ipv6(SocketPool *pool, bool secure,
|
|
||||||
HTTP_IPv6 addr, uint16_t port, void *user_data);
|
|
||||||
|
|
||||||
int socket_pool_read(SocketPool *pool, SocketHandle handle, char *dst, int len);
|
|
||||||
|
|
||||||
int socket_pool_write(SocketPool *pool, SocketHandle handle, char *src, int len);
|
|
||||||
|
|
||||||
bool socket_pool_secure(SocketPool *pool, SocketHandle handle);
|
|
||||||
|
|
||||||
#endif // SOCKET_POOL_INCLUDED
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
#include <string.h>
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include <ws2tcpip.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef __linux__
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <arpa/inet.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef HTTP_AMALGAMATION
|
|
||||||
#include "socket_raw.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int socket_raw_global_init(void)
|
|
||||||
{
|
|
||||||
#ifdef _WIN32
|
|
||||||
WSADATA wsaData;
|
|
||||||
int result = WSAStartup(MAKEWORD(2, 2), &wsaData);
|
|
||||||
if (result != 0)
|
|
||||||
return 1;
|
|
||||||
#endif
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void socket_raw_global_free(void)
|
|
||||||
{
|
|
||||||
#ifdef _WIN32
|
|
||||||
WSACleanup();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
int set_socket_blocking(RAW_SOCKET sock, bool value)
|
|
||||||
{
|
|
||||||
#ifdef _WIN32
|
|
||||||
u_long mode = !value;
|
|
||||||
if (ioctlsocket(sock, FIONBIO, &mode) == SOCKET_ERROR)
|
|
||||||
return -1;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef __linux__
|
|
||||||
int flags = fcntl(sock, F_GETFL, 0);
|
|
||||||
if (flags < 0)
|
|
||||||
return -1;
|
|
||||||
if (value) flags &= ~O_NONBLOCK;
|
|
||||||
else flags |= O_NONBLOCK;
|
|
||||||
if (fcntl(sock, F_SETFL, flags) < 0)
|
|
||||||
return -1;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
RAW_SOCKET listen_socket(HTTP_String addr, uint16_t port, bool reuse_addr, int backlog)
|
|
||||||
{
|
|
||||||
RAW_SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
|
|
||||||
if (sock == BAD_SOCKET)
|
|
||||||
return BAD_SOCKET;
|
|
||||||
|
|
||||||
if (set_socket_blocking(sock, false) < 0) {
|
|
||||||
CLOSE_SOCKET(sock);
|
|
||||||
return BAD_SOCKET;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reuse_addr) {
|
|
||||||
int one = 1;
|
|
||||||
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void*) &one, sizeof(one));
|
|
||||||
}
|
|
||||||
|
|
||||||
struct in_addr addr_buf;
|
|
||||||
if (addr.len == 0)
|
|
||||||
addr_buf.s_addr = htonl(INADDR_ANY);
|
|
||||||
else {
|
|
||||||
|
|
||||||
char copy[100];
|
|
||||||
if (addr.len >= (int) sizeof(copy)) {
|
|
||||||
CLOSE_SOCKET(sock);
|
|
||||||
return BAD_SOCKET;
|
|
||||||
}
|
|
||||||
memcpy(copy, addr.ptr, addr.len);
|
|
||||||
copy[addr.len] = '\0';
|
|
||||||
|
|
||||||
if (inet_pton(AF_INET, copy, &addr_buf) < 0) {
|
|
||||||
CLOSE_SOCKET(sock);
|
|
||||||
return BAD_SOCKET;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct sockaddr_in bind_buf;
|
|
||||||
bind_buf.sin_family = AF_INET;
|
|
||||||
bind_buf.sin_addr = addr_buf;
|
|
||||||
bind_buf.sin_port = htons(port);
|
|
||||||
if (bind(sock, (struct sockaddr*) &bind_buf, sizeof(bind_buf)) < 0) { // TODO: how does bind fail on windows?
|
|
||||||
CLOSE_SOCKET(sock);
|
|
||||||
return BAD_SOCKET;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (listen(sock, backlog) < 0) { // TODO: how does listen fail on windows?
|
|
||||||
CLOSE_SOCKET(sock);
|
|
||||||
return BAD_SOCKET;
|
|
||||||
}
|
|
||||||
|
|
||||||
return sock;
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
#ifndef SOCKET_RAW_INCLUDED
|
|
||||||
#define SOCKET_RAW_INCLUDED
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include <winsock2.h>
|
|
||||||
#define RAW_SOCKET SOCKET
|
|
||||||
#define BAD_SOCKET INVALID_SOCKET
|
|
||||||
#define POLL WSAPoll
|
|
||||||
#define CLOSE_SOCKET closesocket
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef __linux__
|
|
||||||
#include <poll.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#define RAW_SOCKET int
|
|
||||||
#define BAD_SOCKET -1
|
|
||||||
#define POLL poll
|
|
||||||
#define CLOSE_SOCKET close
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef HTTP_AMALGAMATION
|
|
||||||
#include "basic.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int socket_raw_global_init(void);
|
|
||||||
void socket_raw_global_free(void);
|
|
||||||
|
|
||||||
int set_socket_blocking(RAW_SOCKET sock, bool value);
|
|
||||||
|
|
||||||
RAW_SOCKET listen_socket(HTTP_String addr, uint16_t port, bool reuse_addr, int backlog);
|
|
||||||
|
|
||||||
#endif // SOCKET_RAW_INCLUDED
|
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
|
||||||
|
int mutex_init(Mutex *mutex)
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
InitializeCriticalSection(mutex); // TODO: mock?
|
||||||
|
return 0;
|
||||||
|
#else
|
||||||
|
if (pthread_mutex_init(mutex, NULL)) // TODO: mock
|
||||||
|
return -1;
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
int mutex_free(Mutex *mutex)
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
DeleteCriticalSection(mutex); // TODO: mock?
|
||||||
|
return 0;
|
||||||
|
#else
|
||||||
|
if (pthread_mutex_destroy(mutex)) // TODO: mock
|
||||||
|
return -1;
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
int mutex_lock(Mutex *mutex)
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
EnterCriticalSection(mutex); // TODO: mock?
|
||||||
|
return 0;
|
||||||
|
#else
|
||||||
|
if (pthread_mutex_lock(mutex)) // TODO: mock
|
||||||
|
return -1;
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
int mutex_unlock(Mutex *mutex)
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
LeaveCriticalSection(mutex); // TODO: mock?
|
||||||
|
return 0;
|
||||||
|
#else
|
||||||
|
if (pthread_mutex_unlock(mutex)) // TODO: mock
|
||||||
|
return -1;
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
|
||||||
|
typedef struct {
|
||||||
|
// TODO
|
||||||
|
} Mutex;
|
||||||
|
|
||||||
|
int mutex_init(Mutex *mutex);
|
||||||
|
int mutex_free(Mutex *mutex);
|
||||||
|
int mutex_lock(Mutex *mutex);
|
||||||
|
int mutex_unlock(Mutex *mutex);
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include "test.h"
|
|
||||||
|
|
||||||
static const char *statestr(HTTP_EngineState state)
|
|
||||||
{
|
|
||||||
switch (state) {
|
|
||||||
case HTTP_ENGINE_STATE_NONE : return "NONE";
|
|
||||||
case HTTP_ENGINE_STATE_CLIENT_PREP_URL : return "CLIENT_PREP_URL";
|
|
||||||
case HTTP_ENGINE_STATE_CLIENT_PREP_HEADER : return "CLIENT_PREP_HEADER";
|
|
||||||
case HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF: return "CLIENT_PREP_BODY_BUF";
|
|
||||||
case HTTP_ENGINE_STATE_CLIENT_PREP_BODY_ACK: return "CLIENT_PREP_BODY_ACK";
|
|
||||||
case HTTP_ENGINE_STATE_CLIENT_PREP_ERROR : return "CLIENT_PREP_ERROR";
|
|
||||||
case HTTP_ENGINE_STATE_CLIENT_SEND_BUF : return "CLIENT_SEND_BUF";
|
|
||||||
case HTTP_ENGINE_STATE_CLIENT_SEND_ACK : return "CLIENT_SEND_ACK";
|
|
||||||
case HTTP_ENGINE_STATE_CLIENT_RECV_BUF : return "CLIENT_RECV_BUF";
|
|
||||||
case HTTP_ENGINE_STATE_CLIENT_RECV_ACK : return "CLIENT_RECV_ACK";
|
|
||||||
case HTTP_ENGINE_STATE_CLIENT_READY : return "CLIENT_READY";
|
|
||||||
case HTTP_ENGINE_STATE_CLIENT_CLOSED : return "CLIENT_CLOSED";
|
|
||||||
case HTTP_ENGINE_STATE_SERVER_RECV_BUF : return "SERVER_RECV_BUF";
|
|
||||||
case HTTP_ENGINE_STATE_SERVER_RECV_ACK : return "SERVER_RECV_ACK";
|
|
||||||
case HTTP_ENGINE_STATE_SERVER_PREP_STATUS : return "SERVER_PREP_STATUS";
|
|
||||||
case HTTP_ENGINE_STATE_SERVER_PREP_HEADER : return "SERVER_PREP_HEADER";
|
|
||||||
case HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF: return "SERVER_PREP_BODY_BUF";
|
|
||||||
case HTTP_ENGINE_STATE_SERVER_PREP_BODY_ACK: return "SERVER_PREP_BODY_ACK";
|
|
||||||
case HTTP_ENGINE_STATE_SERVER_PREP_ERROR : return "SERVER_PREP_ERROR";
|
|
||||||
case HTTP_ENGINE_STATE_SERVER_SEND_BUF : return "SERVER_SEND_BUF";
|
|
||||||
case HTTP_ENGINE_STATE_SERVER_SEND_ACK : return "SERVER_SEND_ACK";
|
|
||||||
case HTTP_ENGINE_STATE_SERVER_CLOSED : return "SERVER_CLOSED";
|
|
||||||
}
|
|
||||||
return "???";
|
|
||||||
}
|
|
||||||
|
|
||||||
void testeq_engstate(HTTP_EngineState l, HTTP_EngineState r, HTTP_String uneval_l, HTTP_String uneval_r, const char *file, int line)
|
|
||||||
{
|
|
||||||
if (l != r) {
|
|
||||||
printf("Test failed at %s:%d\n", file, line);
|
|
||||||
printf(" TEST_EQ(%.*s, %.*s) -> TEST_EQ(%s, %s)\n", HTTP_UNPACK(uneval_l), HTTP_UNPACK(uneval_r), statestr(l), statestr(r));
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void testeq_int(int l, int r, HTTP_String uneval_l, HTTP_String uneval_r, const char *file, int line)
|
|
||||||
{
|
|
||||||
if (l != r) {
|
|
||||||
printf("Test failed at %s:%d\n", file, line);
|
|
||||||
printf(" TEST_EQ(%.*s, %.*s) -> TEST_EQ(%d, %d)\n", HTTP_UNPACK(uneval_l), HTTP_UNPACK(uneval_r), l, r);
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void testeq_str(HTTP_String l, HTTP_String r, HTTP_String uneval_l, HTTP_String uneval_r, const char *file, int line)
|
|
||||||
{
|
|
||||||
if (!http_streq(l, r)) {
|
|
||||||
printf("Test failed at %s:%d\n", file, line);
|
|
||||||
printf(" TEST_EQ(\"%.*s\", \"%.*s\") -> TEST_EQ(%.*s, %.*s)\n", HTTP_UNPACK(uneval_l), HTTP_UNPACK(uneval_r), HTTP_UNPACK(l), HTTP_UNPACK(r));
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void test_branch_coverage_parse(void);
|
|
||||||
void test_branch_coverage_engine(void);
|
|
||||||
void test_fuzz_engine(void);
|
|
||||||
|
|
||||||
int main(void)
|
|
||||||
{
|
|
||||||
char *tests[] = {
|
|
||||||
};
|
|
||||||
|
|
||||||
for (int i = 0; i < sizeof(tests)/sizeof(tests[0]); i++) {
|
|
||||||
HTTP_Request req;
|
|
||||||
int ret = http_parse_request(tests[i], strlen(tests[i]), &req);
|
|
||||||
TEST(ret == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
//test_branch_coverage_parse();
|
|
||||||
test_branch_coverage_engine();
|
|
||||||
//test_fuzz_engine();
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
#include <stdio.h>
|
|
||||||
#include "../tinyhttp.h"
|
|
||||||
|
|
||||||
#define S HTTP_STR
|
|
||||||
|
|
||||||
#define COUNT(X) (int) (sizeof(X)/sizeof((X)[0]))
|
|
||||||
#define TEST(X) {if (!(X)) { fprintf(stderr, "Failed test at %s:%d\n", __FILE__, __LINE__); __builtin_trap(); }}
|
|
||||||
|
|
||||||
void testeq_engstate(HTTP_EngineState l, HTTP_EngineState r, HTTP_String uneval_l, HTTP_String uneval_r, const char *file, int line);
|
|
||||||
void testeq_int(int l, int r, HTTP_String uneval_l, HTTP_String uneval_r, const char *file, int line);
|
|
||||||
void testeq_str(HTTP_String l, HTTP_String r, HTTP_String uneval_l, HTTP_String uneval_r, const char *file, int line);
|
|
||||||
#define TEST_EQ(X, Y) _Generic((X), HTTP_String: testeq_str, int: testeq_int, HTTP_EngineState: testeq_engstate)((X), (Y), S(#X), S(#Y), __FILE__, __LINE__)
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include "test.h"
|
|
||||||
|
|
||||||
static void *memfunc(HTTP_MemoryFuncTag tag,
|
|
||||||
void *ptr, int len, void *data)
|
|
||||||
{
|
|
||||||
(void) data;
|
|
||||||
switch (tag) {
|
|
||||||
|
|
||||||
case HTTP_MEMFUNC_MALLOC:
|
|
||||||
return malloc(len);
|
|
||||||
|
|
||||||
case HTTP_MEMFUNC_FREE:
|
|
||||||
free(ptr);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_engine_server(void)
|
|
||||||
{
|
|
||||||
HTTP_Engine eng;
|
|
||||||
|
|
||||||
{
|
|
||||||
int client = 0;
|
|
||||||
memset(&eng, 0, sizeof(eng));
|
|
||||||
http_engine_init(&eng, client, memfunc, NULL);
|
|
||||||
TEST_EQ((int) http_engine_state(&eng), HTTP_ENGINE_STATE_SERVER_RECV_BUF);
|
|
||||||
http_engine_free(&eng);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
int client = 1;
|
|
||||||
memset(&eng, 0, sizeof(eng));
|
|
||||||
http_engine_init(&eng, client, memfunc, NULL);
|
|
||||||
TEST_EQ((int) http_engine_state(&eng), HTTP_ENGINE_STATE_CLIENT_PREP_URL);
|
|
||||||
http_engine_free(&eng);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int read_from_client_engine(HTTP_Engine *eng, char *dst, int cap)
|
|
||||||
{
|
|
||||||
TEST_EQ(http_engine_state(eng), HTTP_ENGINE_STATE_CLIENT_SEND_BUF);
|
|
||||||
|
|
||||||
int num = 0;
|
|
||||||
do {
|
|
||||||
int len;
|
|
||||||
char *src = http_engine_sendbuf(eng, &len);
|
|
||||||
TEST_EQ(http_engine_state(eng), HTTP_ENGINE_STATE_CLIENT_SEND_ACK);
|
|
||||||
|
|
||||||
TEST(src != NULL);
|
|
||||||
TEST(cap - num >= len);
|
|
||||||
memcpy(dst + num, src, len);
|
|
||||||
|
|
||||||
num += len;
|
|
||||||
http_engine_sendack(eng, len);
|
|
||||||
} while (http_engine_state(eng) == HTTP_ENGINE_STATEBIT_SEND_BUF);
|
|
||||||
|
|
||||||
return num;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void send_into_client_engine(HTTP_Engine *eng, char *src, int len)
|
|
||||||
{
|
|
||||||
int num = 0;
|
|
||||||
while (num < len) {
|
|
||||||
TEST_EQ(http_engine_state(eng), HTTP_ENGINE_STATE_CLIENT_RECV_BUF);
|
|
||||||
|
|
||||||
int cap;
|
|
||||||
char *dst = http_engine_recvbuf(eng, &cap);
|
|
||||||
TEST_EQ(http_engine_state(eng), HTTP_ENGINE_STATE_CLIENT_RECV_ACK);
|
|
||||||
TEST(dst != NULL);
|
|
||||||
TEST(cap > 0);
|
|
||||||
|
|
||||||
int cpy = len - num;
|
|
||||||
if (cpy > cap) cpy = cap;
|
|
||||||
|
|
||||||
memcpy(dst, src + num, cpy);
|
|
||||||
|
|
||||||
num += cpy;
|
|
||||||
http_engine_recvack(eng, cpy);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_engine_client_basic_flow(void)
|
|
||||||
{
|
|
||||||
int client = 1;
|
|
||||||
HTTP_Engine eng;
|
|
||||||
http_engine_init(&eng, client, memfunc, NULL);
|
|
||||||
TEST_EQ(http_engine_state(&eng), HTTP_ENGINE_STATE_CLIENT_PREP_URL);
|
|
||||||
|
|
||||||
http_engine_url(&eng, HTTP_METHOD_GET, "http://some.url.com/some/endpoint", 1);
|
|
||||||
TEST_EQ(http_engine_state(&eng), HTTP_ENGINE_STATE_CLIENT_PREP_HEADER);
|
|
||||||
|
|
||||||
http_engine_header(&eng, "headerA: valueA", -1);
|
|
||||||
TEST_EQ(http_engine_state(&eng), HTTP_ENGINE_STATE_CLIENT_PREP_HEADER);
|
|
||||||
|
|
||||||
http_engine_body(&eng, "Hello, world!", -1);
|
|
||||||
TEST_EQ(http_engine_state(&eng), HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF);
|
|
||||||
|
|
||||||
http_engine_done(&eng);
|
|
||||||
TEST_EQ(http_engine_state(&eng), HTTP_ENGINE_STATE_CLIENT_SEND_BUF);
|
|
||||||
|
|
||||||
{
|
|
||||||
char sendbuf[1<<10];
|
|
||||||
int sendnum;
|
|
||||||
sendnum = read_from_client_engine(&eng, sendbuf, (int) sizeof(sendbuf));
|
|
||||||
|
|
||||||
char expect[] =
|
|
||||||
"GET /some/endpoint HTTP/1.1\r\n"
|
|
||||||
"Host: some.url.com\r\n"
|
|
||||||
"headerA: valueA\r\n"
|
|
||||||
"Connection: Keep-Alive\r\n"
|
|
||||||
"Content-Length: 13 \r\n"
|
|
||||||
"\r\n"
|
|
||||||
"Hello, world!";
|
|
||||||
|
|
||||||
TEST_EQ(sendnum, strlen(expect));
|
|
||||||
TEST_EQ(memcmp(expect, sendbuf, strlen(expect)), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
char response[] =
|
|
||||||
"HTTP/1.1 200 OK\r\n"
|
|
||||||
"\r\n";
|
|
||||||
send_into_client_engine(&eng, response, strlen(response));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_EQ(http_engine_state(&eng), HTTP_ENGINE_STATE_CLIENT_READY);
|
|
||||||
|
|
||||||
http_engine_free(&eng);
|
|
||||||
}
|
|
||||||
|
|
||||||
void test_branch_coverage_engine(void)
|
|
||||||
{
|
|
||||||
test_engine_server();
|
|
||||||
test_engine_client_basic_flow();
|
|
||||||
}
|
|
||||||
@@ -1,485 +0,0 @@
|
|||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include "test.h"
|
|
||||||
|
|
||||||
static void test_branch_coverage_parse_request(void)
|
|
||||||
{
|
|
||||||
struct {
|
|
||||||
int line;
|
|
||||||
int ret;
|
|
||||||
char *str;
|
|
||||||
} error_reqs[] = {
|
|
||||||
{ __LINE__, -1, "G * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "G@ * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "GE * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "GE@ * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, 18, "GET * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "GET@ * HTTP/1.1\r\n\r\n" },
|
|
||||||
|
|
||||||
{ __LINE__, -1, "P * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "P@ * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "PO * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "PO@ * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "POS * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "POS@ * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, 19, "POST * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "POST@ * HTTP/1.1\r\n\r\n" },
|
|
||||||
|
|
||||||
{ __LINE__, -1, "PU * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "PU@ * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, 18, "PUT * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "PUT@ * HTTP/1.1\r\n\r\n" },
|
|
||||||
|
|
||||||
{ __LINE__, -1, "H * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "H@ * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "HE * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "HE@ * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "HEA * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "HEA@ * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, 19, "HEAD * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "HEAD@ * HTTP/1.1\r\n\r\n" },
|
|
||||||
|
|
||||||
{ __LINE__, -1, "D * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "D@ * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "DE * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "DE@ * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "DEL * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "DEL@ * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "DELE * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "DELE@ * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "DELET * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "DELET@ * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, 21, "DELETE * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "DELETE@ * HTTP/1.1\r\n\r\n" },
|
|
||||||
|
|
||||||
{ __LINE__, -1, "C * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "C@ * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "CO * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "CO@ * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "CON * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "CON@ * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "CONN * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "CONN@ * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "CONNE * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "CONNE@ * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "CONNEC * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "CONNEC@ * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, 22, "CONNECT * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "CONNECT@ * HTTP/1.1\r\n\r\n" },
|
|
||||||
|
|
||||||
{ __LINE__, -1, "O * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "O@ * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "PO * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "PO@ * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "OPT * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "OPT@ * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "OPTI * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "OPTI@ * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "OPTIO * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "OPTIO@ * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "OPTION * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "OPTION@ * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, 22, "OPTIONS * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "OPTIONS@ * HTTP/1.1\r\n\r\n" },
|
|
||||||
|
|
||||||
{ __LINE__, -1, "T * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "T@ * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "TR * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "TR@ * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "TRA * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "TRA@ * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "TRAC * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "TRAC@ * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, 20, "TRACE * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "TRACE@ * HTTP/1.1\r\n\r\n" },
|
|
||||||
|
|
||||||
{ __LINE__, -1, "P * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "P@ * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "PA * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "PA@ * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "PAT * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "PAT@ * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "PATC * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "PATC@ * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, 20, "PATCH * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "PATCH@ * HTTP/1.1\r\n\r\n" },
|
|
||||||
|
|
||||||
{ __LINE__, -1, "GET *@\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "GET * @\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "GET * H\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "GET * H@\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "GET * HT\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "GET * HT@\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "GET * HTT\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "GET * HTT@\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "GET * HTTP\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "GET * HTTP@\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "GET * HTTP/\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "GET * HTTP/@\r\n\r\n" },
|
|
||||||
{ __LINE__, 16, "GET * HTTP/1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "GET * HTTP/1@\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "GET * HTTP/1.\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "GET * HTTP/1.@\r\n\r\n" },
|
|
||||||
{ __LINE__, 18, "GET * HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "GET * HTTP/1.1@\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "GET * HTTP/1.1@\nname:\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "GET * HTTP/1.1\r@name:\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "GET * HTTP/1.1\r\n@\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "GET * HTTP/1.1\r\nn@\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "GET * HTTP/1.1\r\nname\r\n\r\n" },
|
|
||||||
{ __LINE__, 25, "GET * HTTP/1.1\r\nname:\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "GET * HTTP/1.1\r\nname:\x1B\r\n\r\n" },
|
|
||||||
{ __LINE__, 30, "GET * HTTP/1.1\r\nname:value\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "GET * HTTP/1.1\r\nname :value\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "GET * HTTP/1.1\r\nname:val\rue\r\n\r\n" },
|
|
||||||
|
|
||||||
{ __LINE__, 0, NULL },
|
|
||||||
};
|
|
||||||
|
|
||||||
for (int i = 0; error_reqs[i].str; i++) {
|
|
||||||
HTTP_Request req;
|
|
||||||
int ret = http_parse_request(error_reqs[i].str, strlen(error_reqs[i].str), &req);
|
|
||||||
if (ret != error_reqs[i].ret) {
|
|
||||||
fprintf(stderr, "Failed test at %s:%d (ret=%d, expected=%d)\n", __FILE__, error_reqs[i].line, ret, error_reqs[i].ret);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
char str[] = "GET * HTTP/1.1\r\n\r\n";
|
|
||||||
HTTP_Request req;
|
|
||||||
int ret = http_parse_request(str, sizeof(str)-1, &req);
|
|
||||||
TEST(ret == 18);
|
|
||||||
TEST(req.method == HTTP_METHOD_GET);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
char str[] = "POST * HTTP/1.1\r\n\r\n";
|
|
||||||
HTTP_Request req;
|
|
||||||
int ret = http_parse_request(str, sizeof(str)-1, &req);
|
|
||||||
TEST(ret == 19);
|
|
||||||
TEST(req.method == HTTP_METHOD_POST);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
char str[] = "PUT * HTTP/1.1\r\n\r\n";
|
|
||||||
HTTP_Request req;
|
|
||||||
int ret = http_parse_request(str, sizeof(str)-1, &req);
|
|
||||||
TEST(ret == 18);
|
|
||||||
TEST(req.method == HTTP_METHOD_PUT);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
char str[] = "HEAD * HTTP/1.1\r\n\r\n";
|
|
||||||
HTTP_Request req;
|
|
||||||
int ret = http_parse_request(str, sizeof(str)-1, &req);
|
|
||||||
TEST(ret == 19);
|
|
||||||
TEST(req.method == HTTP_METHOD_HEAD);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
char str[] = "DELETE * HTTP/1.1\r\n\r\n";
|
|
||||||
HTTP_Request req;
|
|
||||||
int ret = http_parse_request(str, sizeof(str)-1, &req);
|
|
||||||
TEST(ret == 21);
|
|
||||||
TEST(req.method == HTTP_METHOD_DELETE);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
char str[] = "CONNECT * HTTP/1.1\r\n\r\n";
|
|
||||||
HTTP_Request req;
|
|
||||||
int ret = http_parse_request(str, sizeof(str)-1, &req);
|
|
||||||
TEST(ret == 22);
|
|
||||||
TEST(req.method == HTTP_METHOD_CONNECT);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
char str[] = "OPTIONS * HTTP/1.1\r\n\r\n";
|
|
||||||
HTTP_Request req;
|
|
||||||
int ret = http_parse_request(str, sizeof(str)-1, &req);
|
|
||||||
TEST(ret == 22);
|
|
||||||
TEST(req.method == HTTP_METHOD_OPTIONS);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
char str[] = "TRACE * HTTP/1.1\r\n\r\n";
|
|
||||||
HTTP_Request req;
|
|
||||||
int ret = http_parse_request(str, sizeof(str)-1, &req);
|
|
||||||
TEST(ret == 20);
|
|
||||||
TEST(req.method == HTTP_METHOD_TRACE);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
char str[] = "PATCH * HTTP/1.1\r\n\r\n";
|
|
||||||
HTTP_Request req;
|
|
||||||
int ret = http_parse_request(str, sizeof(str)-1, &req);
|
|
||||||
TEST(ret == 20);
|
|
||||||
TEST(req.method == HTTP_METHOD_PATCH);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_branch_coverage_parse_response(void)
|
|
||||||
{
|
|
||||||
struct {
|
|
||||||
int line;
|
|
||||||
int ret;
|
|
||||||
char *str;
|
|
||||||
} error_ress[] = {
|
|
||||||
{ __LINE__, -1, "@\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "H\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "H@\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "HT\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "HT@\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "HTT\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "HTT@\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "HTTP\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "HTTP@\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "HTTP/\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "HTTP/@\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "HTTP/1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "HTTP/1@\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "HTTP/1.\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "HTTP/1.@\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "HTTP/1.1\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "HTTP/1.1@\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "HTTP/1.1 \r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "HTTP/1.1 @\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "HTTP/1.1 4\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "HTTP/1.1 4@\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "HTTP/1.1 40\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "HTTP/1.1 40@\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "HTTP/1.1 404\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "HTTP/1.1 404@\r\n\r\n" },
|
|
||||||
{ __LINE__, 17, "HTTP/1.1 404 \r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "HTTP/1.1 404 \x1B\r\n\r\n" },
|
|
||||||
{ __LINE__, 26, "HTTP/1.1 404 Not Found\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "HTTP/1.1 404 Not Found\x1B\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "HTTP/1.1 404 Not Found\x1B\nname:\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "HTTP/1.1 404 Not Found\r\x1Bname:\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "HTTP/1.1 404 Not Found\r\n@\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "HTTP/1.1 404 Not Found\r\nn@\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "HTTP/1.1 404 Not Found\r\nname\r\n\r\n" },
|
|
||||||
{ __LINE__, 33, "HTTP/1.1 404 Not Found\r\nname:\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "HTTP/1.1 404 Not Found\r\nname:\x1B\r\n\r\n" },
|
|
||||||
{ __LINE__, 38, "HTTP/1.1 404 Not Found\r\nname:value\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "HTTP/1.1 404 Not Found\r\nname :value\r\n\r\n" },
|
|
||||||
{ __LINE__, -1, "HTTP/1.1 404 Not Found\r\nname:val\rue\r\n\r\n" },
|
|
||||||
|
|
||||||
{ __LINE__, 0, NULL },
|
|
||||||
};
|
|
||||||
|
|
||||||
for (int i = 0; error_ress[i].str; i++) {
|
|
||||||
HTTP_Response res;
|
|
||||||
int ret = http_parse_response(error_ress[i].str, strlen(error_ress[i].str), &res);
|
|
||||||
if (ret != error_ress[i].ret) {
|
|
||||||
fprintf(stderr, "Failed test at %s:%d (ret=%d, expected=%d)\n", __FILE__, error_ress[i].line, ret, error_ress[i].ret);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include <winsock2.h>
|
|
||||||
#include <ws2tcpip.h>
|
|
||||||
#else
|
|
||||||
#include <arpa/inet.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
char *buf;
|
|
||||||
int cap;
|
|
||||||
int num;
|
|
||||||
} Buffer;
|
|
||||||
|
|
||||||
static void appendc(Buffer *b, char c)
|
|
||||||
{
|
|
||||||
if (b->num < b->cap)
|
|
||||||
b->buf[b->num] = c;
|
|
||||||
b->num++;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void appends(Buffer *b, HTTP_String s)
|
|
||||||
{
|
|
||||||
if (b->num < b->cap) {
|
|
||||||
int cpy = s.len;
|
|
||||||
if (cpy > b->cap - b->num)
|
|
||||||
cpy = b->cap - b->num;
|
|
||||||
for (int i = 0; i < cpy; i++)
|
|
||||||
b->buf[b->num + i] = s.ptr[i];
|
|
||||||
}
|
|
||||||
b->num += s.len;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void appendi(Buffer *buf, unsigned int num)
|
|
||||||
{
|
|
||||||
char tmp[10];
|
|
||||||
|
|
||||||
tmp[0] = num / 1000000000; num %= 1000000000;
|
|
||||||
tmp[1] = num / 100000000; num %= 100000000;
|
|
||||||
tmp[2] = num / 10000000; num %= 10000000;
|
|
||||||
tmp[3] = num / 1000000; num %= 1000000;
|
|
||||||
tmp[4] = num / 100000; num %= 100000;
|
|
||||||
tmp[5] = num / 10000; num %= 10000;
|
|
||||||
tmp[6] = num / 1000; num %= 1000;
|
|
||||||
tmp[7] = num / 100; num %= 100;
|
|
||||||
tmp[8] = num / 10; num %= 10;
|
|
||||||
tmp[9] = num;
|
|
||||||
|
|
||||||
int leading_zeros = 0;
|
|
||||||
while (leading_zeros < 9 && tmp[leading_zeros] == 0)
|
|
||||||
leading_zeros++;
|
|
||||||
|
|
||||||
for (int i = leading_zeros; i < 10; i++)
|
|
||||||
tmp[i] += '0';
|
|
||||||
|
|
||||||
appends(buf, (HTTP_String) {
|
|
||||||
tmp + leading_zeros,
|
|
||||||
10 - leading_zeros
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static int build_url(
|
|
||||||
HTTP_String scheme,
|
|
||||||
HTTP_String userinfo,
|
|
||||||
HTTP_String host,
|
|
||||||
HTTP_HostMode mode,
|
|
||||||
int port,
|
|
||||||
HTTP_String path,
|
|
||||||
HTTP_String query,
|
|
||||||
HTTP_String fragment,
|
|
||||||
char* dst,
|
|
||||||
int cap)
|
|
||||||
{
|
|
||||||
Buffer buf = {dst, cap, 0};
|
|
||||||
appends(&buf, scheme);
|
|
||||||
appendc(&buf, ':');
|
|
||||||
if (mode != HTTP_HOST_MODE_VOID) {
|
|
||||||
appendc(&buf, '/');
|
|
||||||
appendc(&buf, '/');
|
|
||||||
if (userinfo.len) {
|
|
||||||
appends(&buf, userinfo);
|
|
||||||
appendc(&buf, '@');
|
|
||||||
}
|
|
||||||
if (mode == HTTP_HOST_MODE_IPV6)
|
|
||||||
appendc(&buf, '[');
|
|
||||||
appends(&buf, host);
|
|
||||||
if (mode == HTTP_HOST_MODE_IPV6)
|
|
||||||
appendc(&buf, ']');
|
|
||||||
if (port == -2)
|
|
||||||
appendc(&buf, ':');
|
|
||||||
else if (port != -1) {
|
|
||||||
appendc(&buf, ':');
|
|
||||||
appendi(&buf, port);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
appends(&buf, path);
|
|
||||||
appends(&buf, query);
|
|
||||||
appends(&buf, fragment);
|
|
||||||
return buf.num;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_url(HTTP_String scheme, HTTP_String userinfo,
|
|
||||||
HTTP_String host, HTTP_HostMode mode, int port,
|
|
||||||
HTTP_String path, HTTP_String query, HTTP_String fragment)
|
|
||||||
{
|
|
||||||
char mem[1<<12];
|
|
||||||
int num = build_url(scheme, userinfo, host, mode, port, path, query, fragment, mem, sizeof(mem));
|
|
||||||
TEST(num < sizeof(mem));
|
|
||||||
|
|
||||||
printf("Testing %.*s\n", num, mem);
|
|
||||||
|
|
||||||
HTTP_URL url;
|
|
||||||
int ret = http_parse_url(mem, num, &url);
|
|
||||||
|
|
||||||
TEST_EQ(ret, num);
|
|
||||||
TEST_EQ(url.scheme, scheme);
|
|
||||||
TEST_EQ(url.authority.userinfo, userinfo);
|
|
||||||
if (port < 0)
|
|
||||||
TEST_EQ(url.authority.port, 0);
|
|
||||||
else
|
|
||||||
TEST_EQ(url.authority.port, port);
|
|
||||||
TEST_EQ((int) url.authority.host.mode, (int) mode);
|
|
||||||
if (mode == HTTP_HOST_MODE_IPV4) {
|
|
||||||
char tmp[1<<12];
|
|
||||||
TEST(sizeof(tmp) > host.len);
|
|
||||||
memcpy(tmp, host.ptr, host.len);
|
|
||||||
tmp[host.len] = '\0';
|
|
||||||
HTTP_IPv4 buf;
|
|
||||||
TEST_EQ(inet_pton(AF_INET, tmp, &buf), 1);
|
|
||||||
TEST_EQ((int) buf.data, (int) url.authority.host.ipv4.data);
|
|
||||||
} else if (mode == HTTP_HOST_MODE_IPV6) {
|
|
||||||
char tmp[1<<12];
|
|
||||||
TEST(sizeof(tmp) > host.len);
|
|
||||||
memcpy(tmp, host.ptr, host.len);
|
|
||||||
tmp[host.len] = '\0';
|
|
||||||
HTTP_IPv6 buf;
|
|
||||||
TEST_EQ(inet_pton(AF_INET6, tmp, &buf), 1);
|
|
||||||
TEST(!memcmp(&buf, &url.authority.host.ipv6, sizeof(HTTP_IPv6)));
|
|
||||||
} else if (mode == HTTP_HOST_MODE_NAME) {
|
|
||||||
TEST_EQ(host, url.authority.host.name);
|
|
||||||
}
|
|
||||||
TEST_EQ(url.path, path);
|
|
||||||
TEST_EQ(url.query, query);
|
|
||||||
TEST_EQ(url.fragment, fragment);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_branch_coverage_parse_url(void)
|
|
||||||
{
|
|
||||||
HTTP_String scheme_values[] = { S("http") };
|
|
||||||
|
|
||||||
HTTP_String userinfo_values[] = { S(""), S("xxx:yyy") };
|
|
||||||
|
|
||||||
struct host_values {
|
|
||||||
HTTP_HostMode mode;
|
|
||||||
HTTP_String text;
|
|
||||||
} host_values[] = {
|
|
||||||
{ HTTP_HOST_MODE_VOID, S("") },
|
|
||||||
{ HTTP_HOST_MODE_IPV4, S("1.2.3.4") },
|
|
||||||
{ HTTP_HOST_MODE_IPV6, S("::") },
|
|
||||||
{ HTTP_HOST_MODE_IPV6, S("::1") },
|
|
||||||
{ HTTP_HOST_MODE_IPV6, S("1:2:3:4:A:B:C:D") },
|
|
||||||
{ HTTP_HOST_MODE_NAME, S("example.com") },
|
|
||||||
{ HTTP_HOST_MODE_NAME, S("1.2.3.256") },
|
|
||||||
};
|
|
||||||
|
|
||||||
int port_values[] = { -1, 1, 8080 };
|
|
||||||
|
|
||||||
HTTP_String path_values[] = { S(""), S("/"), S("/some/path.html") };
|
|
||||||
|
|
||||||
HTTP_String query_values[] = { S(""), S("?"), S("?param1=hello¶m2=sup") };
|
|
||||||
|
|
||||||
HTTP_String fragment_values[] = { S(""), S("#"), S("#section0") };
|
|
||||||
|
|
||||||
for (int i = 0; i < COUNT(scheme_values); i++)
|
|
||||||
for (int j = 0; j < COUNT(userinfo_values); j++)
|
|
||||||
for (int k = 0; k < COUNT(host_values); k++)
|
|
||||||
for (int g = 0; g < COUNT(port_values); g++)
|
|
||||||
for (int t = 0; t < COUNT(path_values); t++)
|
|
||||||
for (int p = 0; p < COUNT(query_values); p++)
|
|
||||||
for (int q = 0; q < COUNT(fragment_values); q++) {
|
|
||||||
|
|
||||||
// Don't test URLs where the host isn't specified but the port or userinfo is
|
|
||||||
if (host_values[k].mode == HTTP_HOST_MODE_VOID && (port_values[g] > -1 || userinfo_values[j].len))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// If the authority is missing, the path must not be empty
|
|
||||||
if (host_values[k].mode == HTTP_HOST_MODE_VOID && path_values[t].len == 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
test_url(
|
|
||||||
scheme_values[i],
|
|
||||||
userinfo_values[j],
|
|
||||||
host_values[k].text,
|
|
||||||
host_values[k].mode,
|
|
||||||
port_values[g],
|
|
||||||
path_values[t],
|
|
||||||
query_values[p],
|
|
||||||
fragment_values[q]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void test_branch_coverage_parse(void)
|
|
||||||
{
|
|
||||||
test_branch_coverage_parse_request();
|
|
||||||
test_branch_coverage_parse_response();
|
|
||||||
test_branch_coverage_parse_url();
|
|
||||||
}
|
|
||||||
@@ -1,230 +0,0 @@
|
|||||||
#include <string.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <signal.h>
|
|
||||||
#include "test.h"
|
|
||||||
|
|
||||||
static void *memfunc(HTTP_MemoryFuncTag tag,
|
|
||||||
void *ptr, int len, void *data)
|
|
||||||
{
|
|
||||||
(void) data;
|
|
||||||
switch (tag) {
|
|
||||||
|
|
||||||
case HTTP_MEMFUNC_MALLOC:
|
|
||||||
{
|
|
||||||
void *ptr = NULL;
|
|
||||||
|
|
||||||
int x = rand() & 31;
|
|
||||||
if (x == 0)
|
|
||||||
ptr = malloc(len);
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
case HTTP_MEMFUNC_FREE:
|
|
||||||
free(ptr);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *http_statestr(int state)
|
|
||||||
{
|
|
||||||
switch (state) {
|
|
||||||
case HTTP_ENGINE_STATE_NONE : return "NONE";
|
|
||||||
case HTTP_ENGINE_STATE_CLIENT_PREP_URL : return "CLIENT_PREP_URL";
|
|
||||||
case HTTP_ENGINE_STATE_CLIENT_PREP_HEADER : return "CLIENT_PREP_HEADER";
|
|
||||||
case HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF: return "CLIENT_PREP_BODY_BUF";
|
|
||||||
case HTTP_ENGINE_STATE_CLIENT_PREP_BODY_ACK: return "CLIENT_PREP_BODY_ACK";
|
|
||||||
case HTTP_ENGINE_STATE_CLIENT_PREP_ERROR : return "CLIENT_PREP_ERROR";
|
|
||||||
case HTTP_ENGINE_STATE_CLIENT_SEND_BUF : return "CLIENT_SEND_BUF";
|
|
||||||
case HTTP_ENGINE_STATE_CLIENT_SEND_ACK : return "CLIENT_SEND_ACK";
|
|
||||||
case HTTP_ENGINE_STATE_CLIENT_RECV_BUF : return "CLIENT_RECV_BUF";
|
|
||||||
case HTTP_ENGINE_STATE_CLIENT_RECV_ACK : return "CLIENT_RECV_ACK";
|
|
||||||
case HTTP_ENGINE_STATE_CLIENT_READY : return "CLIENT_READY";
|
|
||||||
case HTTP_ENGINE_STATE_CLIENT_CLOSED : return "CLIENT_CLOSED";
|
|
||||||
case HTTP_ENGINE_STATE_SERVER_RECV_BUF : return "SERVER_RECV_BUF";
|
|
||||||
case HTTP_ENGINE_STATE_SERVER_RECV_ACK : return "SERVER_RECV_ACK";
|
|
||||||
case HTTP_ENGINE_STATE_SERVER_PREP_STATUS : return "SERVER_PREP_STATUS";
|
|
||||||
case HTTP_ENGINE_STATE_SERVER_PREP_HEADER : return "SERVER_PREP_HEADER";
|
|
||||||
case HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF: return "SERVER_PREP_BODY_BUF";
|
|
||||||
case HTTP_ENGINE_STATE_SERVER_PREP_BODY_ACK: return "SERVER_PREP_BODY_ACK";
|
|
||||||
case HTTP_ENGINE_STATE_SERVER_PREP_ERROR : return "SERVER_PREP_ERROR";
|
|
||||||
case HTTP_ENGINE_STATE_SERVER_SEND_BUF : return "SERVER_SEND_BUF";
|
|
||||||
case HTTP_ENGINE_STATE_SERVER_SEND_ACK : return "SERVER_SEND_ACK";
|
|
||||||
case HTTP_ENGINE_STATE_SERVER_CLOSED : return "SERVER_CLOSED";
|
|
||||||
}
|
|
||||||
return "???";
|
|
||||||
}
|
|
||||||
|
|
||||||
static sig_atomic_t stop = 0;
|
|
||||||
|
|
||||||
static void handle_signal(int dummy)
|
|
||||||
{
|
|
||||||
stop = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void test_fuzz_engine(void)
|
|
||||||
{
|
|
||||||
signal(SIGINT, handle_signal);
|
|
||||||
|
|
||||||
char txt1[] = "";
|
|
||||||
char txt2[] = "GET / HTTP/1.1\r\n\r\n";
|
|
||||||
char txt3[] = "GET / HTTP/1.1\n";
|
|
||||||
char txt4[] = "HTTP/1.1 200 OK\r\n";
|
|
||||||
char txt5[] = "HTTP/1.1 200 OK\r\n\r\n";
|
|
||||||
|
|
||||||
int next_recvack = 0;
|
|
||||||
int next_sendack = 0;
|
|
||||||
int next_bodyack = 0;
|
|
||||||
int curr_bodycap = 0;
|
|
||||||
int client = 0;
|
|
||||||
HTTP_Engine eng;
|
|
||||||
http_engine_init(&eng, client, memfunc, NULL);
|
|
||||||
while (!stop) {
|
|
||||||
//printf("%s\n", http_statestr(http_engine_state(&eng)));
|
|
||||||
switch (rand() % 21) {
|
|
||||||
|
|
||||||
int max;
|
|
||||||
char *buf;
|
|
||||||
|
|
||||||
case 0:
|
|
||||||
//printf("http_engine_free/http_engine_init\n");
|
|
||||||
client = !client;
|
|
||||||
http_engine_free(&eng);
|
|
||||||
http_engine_init(&eng, client, memfunc, NULL);
|
|
||||||
next_recvack = 0;
|
|
||||||
next_sendack = 0;
|
|
||||||
next_bodyack = 0;
|
|
||||||
curr_bodycap = 0;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
//printf("http_engine_recvbuf (1)\n");
|
|
||||||
buf = http_engine_recvbuf(&eng, &max);
|
|
||||||
memcpy(buf, txt1, strlen(txt1));
|
|
||||||
next_recvack = strlen(txt1);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 2:
|
|
||||||
//printf("http_engine_recvbuf (2)\n");
|
|
||||||
buf = http_engine_recvbuf(&eng, &max);
|
|
||||||
if (buf) {
|
|
||||||
memcpy(buf, txt2, strlen(txt2));
|
|
||||||
next_recvack = strlen(txt2);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 3:
|
|
||||||
//printf("http_engine_recvbuf (3)\n");
|
|
||||||
buf = http_engine_recvbuf(&eng, &max);
|
|
||||||
if (buf) {
|
|
||||||
memcpy(buf, txt3, strlen(txt3));
|
|
||||||
next_recvack = strlen(txt3);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 4:
|
|
||||||
//printf("http_engine_recvbuf (4)\n");
|
|
||||||
buf = http_engine_recvbuf(&eng, &max);
|
|
||||||
if (buf) {
|
|
||||||
memcpy(buf, txt4, strlen(txt4));
|
|
||||||
next_recvack = strlen(txt4);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 5:
|
|
||||||
//printf("http_engine_recvbuf (5)\n");
|
|
||||||
buf = http_engine_recvbuf(&eng, &max);
|
|
||||||
if (buf) {
|
|
||||||
memcpy(buf, txt5, strlen(txt5));
|
|
||||||
next_recvack = strlen(txt5);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 6:
|
|
||||||
//printf("http_engine_recvack\n");
|
|
||||||
http_engine_recvack(&eng, next_recvack);
|
|
||||||
next_recvack = 0;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 7:
|
|
||||||
//printf("http_engine_sendbuf\n");
|
|
||||||
buf = http_engine_sendbuf(&eng, &max);
|
|
||||||
if (max)
|
|
||||||
next_sendack = rand() % max;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 8:
|
|
||||||
//printf("http_engine_sendack\n");
|
|
||||||
http_engine_sendack(&eng, next_sendack);
|
|
||||||
next_sendack = 0;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 9:
|
|
||||||
//printf("http_engine_getreq\n");
|
|
||||||
http_engine_getreq(&eng);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 10:
|
|
||||||
//printf("http_engine_getres\n");
|
|
||||||
http_engine_getres(&eng);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 11:
|
|
||||||
//printf("http_engine_url\n");
|
|
||||||
http_engine_url(&eng, HTTP_METHOD_GET, "", 0);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 12:
|
|
||||||
//printf("http_engine_url\n");
|
|
||||||
http_engine_url(&eng, HTTP_METHOD_GET, "http://127.0.0.1/hello", 0);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 13:
|
|
||||||
//printf("http_engine_status\n");
|
|
||||||
http_engine_status(&eng, 200);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 14:
|
|
||||||
//printf("http_engine_header\n");
|
|
||||||
http_engine_header(&eng, "x:y", -1);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 15:
|
|
||||||
//printf("http_engine_bodycap\n");
|
|
||||||
http_engine_bodycap(&eng, rand() % 1000);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 16:
|
|
||||||
//printf("http_engine_bodybuf\n");
|
|
||||||
buf = http_engine_bodybuf(&eng, &max);
|
|
||||||
if (buf && max) {
|
|
||||||
max = rand() % max;
|
|
||||||
memset(buf, 0xFF, max);
|
|
||||||
next_bodyack = max;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 17:
|
|
||||||
//printf("http_engine_bodyack\n");
|
|
||||||
http_engine_bodyack(&eng, next_bodyack);
|
|
||||||
next_bodyack = 0;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 18:
|
|
||||||
//printf("http_engine_done\n");
|
|
||||||
http_engine_done(&eng);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 19:
|
|
||||||
//printf("http_engine_undo\n");
|
|
||||||
http_engine_undo(&eng);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 20:
|
|
||||||
//printf("http_engine_close\n");
|
|
||||||
http_engine_close(&eng);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
http_engine_free(&eng);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user