#ifndef KIT_HTTP1_H #define KIT_HTTP1_H #include "types.h" #include "string_builder.h" #include "sockets.h" #ifdef __cplusplus extern "C" { #endif // TODO // - Add source to README // https://github.com/csachs/picohttpclient/tree/master // // FIXME // Rewrite in C. // #define KIT_HTTP1_CONTENT_TYPE \ "Content-Type: text/plain; version=0.0.4; charset=utf-8"; #define KIT_HTTP1_NEWLINE "\r\n" #define KIT_HTTP1_SPACE " " #define KIT_HTTP1_HEADER_SEPARATOR ": " enum { KIT_HTTP1_OPTIONS, KIT_HTTP1_GET, KIT_HTTP1_HEAD, KIT_HTTP1_POST, KIT_HTTP1_PUT, KIT_HTTP1_DELETE, KIT_HTTP1_TRACE, KIT_HTTP1_CONNECT, }; typedef struct { kit_str_builder_t key; kit_str_builder_t value; } kit_http1_str_pair_t; typedef KIT_DA(kit_http1_str_pair_t) kit_http1_str_map_t; typedef struct { kit_str_builder_t str; i64 position; } kit_http1_tok_t; typedef struct { kit_str_builder_t protocol; kit_str_builder_t host; kit_str_builder_t port; kit_str_builder_t address; kit_str_builder_t query_string; kit_str_builder_t hash; kit_http1_str_map_t parameters; } kit_http1_uri_t; typedef struct { i8 success; kit_str_builder_t protocol; kit_str_builder_t response; kit_str_builder_t response_str; kit_http1_str_map_t header; kit_str_builder_t body; } kit_http1_response_t; static kit_str_builder_t kit_http1_tok_next(kit_http1_tok_t *tok, kit_str_t search, i8 return_tail) { i64 hit = str.find(search, position); if (hit == string::npos) { if (return_tail) { return tail(); } else { return ""; } } i64 previous_position = position; position = hit + search.length(); return str.substr(previous_position, hit - previous_position); } static kit_str_builder_t kit_http1_tok_tail(kit_http1_tok_t *tok) { i64 previous_position = position; position = str.length(); return str.substr(previous_position); } static void kit_http1_uri_parse_params(kit_http1_uri_t *uri) { kit_http1_tok_t qt(query_string); for (;;) { kit_str_builder_t key = qt.next("=", 0); if (key == "") break; parameters[key] = qt.next("&", 1); } } static void kit_http1_uri_init(kit_http1_uri_t *uri, kit_str_builder_t input, i8 should_use_params) { kit_http1_tok_t t = kit_http1_tok_t(input); protocol = t.next("://", 0); kit_str_builder_t host_port_str = t.next("/", 0); kit_http1_tok_t host_port(host_port_str); host = host_port.next(host_port_str[0] == '[' ? "]:" : ":", 1); if (host[0] == '[') host = host.substr(1, host.size() - 1); port = host_port.tail(); if (port.empty()) port = "80"; address = t.next("?", 1); query_string = t.next("#", 1); hash = t.tail(); if (should_use_params) { kit_http1_uri_parse_params(); } } static str_t kit_http1_method_to_str(i32 method) { static str_t methods[] = { SZ("OPTIONS"), SZ("GET"), SZ("HEAD"), SZ("POST"), SZ("PUT"), SZ("DELETE"), SZ("TRACE"), SZ("CONNECT") }; return methods[method]; } static socket_t kit_http1_connect_to_uri(kit_http1_uri_t *uri) { struct addrinfo hints, *result, *rp; memset(&hints, 0, sizeof(addrinfo)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; i32 getaddrinfo_result = getaddrinfo( uri.host.c_str(), uri.port.c_str(), &hints, &result); if (getaddrinfo_result != 0) return INVALID_SOCKET; socket_t fd = INVALID_SOCKET; for (rp = result; rp != nullptr; rp = rp->ai_next) { fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (fd == INVALID_SOCKET) continue; i32 connect_result = connect(fd, rp->ai_addr, (socklen_t) rp->ai_addrlen); if (connect_result == -1) { closesocket(fd); fd = INVALID_SOCKET; continue; } break; } freeaddrinfo(result); return fd; } static kit_str_builder_t kit_http1_buffered_read(socket_t fd) { i64 initial_factor = 4, buffer_increment_size = 8192, buffer_size = 0, bytes_read = 0; kit_str_builder_t buffer; buffer.resize(initial_factor * buffer_increment_size); bytes_read = recv( fd, ((char *) buffer.c_str()) + buffer_size, static_cast(buffer.size() - buffer_size), 0); buffer_size += bytes_read; buffer.resize(buffer_size); return buffer; } static kit_http1_response_t kit_http1_request( i32 method, kit_http1_uri_t *uri, kit_str_builder_t *body) { socket_t fd = kit_http1_connect_to_uri(uri); if (fd == INVALID_SOCKET) { return { .success = 0 }; } kit_str_builder_t request = string(kit_http1_method_to_str(method)) + string(" /") + uri.address + ((uri.query_string == "") ? "" : "?") + uri.query_string + " HTTP/1.1" + KIT_HTTP1_NEWLINE + "Host: " + uri.host + ":" + uri.port + KIT_HTTP1_NEWLINE + "Accept: */*" + KIT_HTTP1_NEWLINE + content_type + KIT_HTTP1_NEWLINE + "Content-Length: " + to_string(body.size()) + KIT_HTTP1_NEWLINE + KIT_HTTP1_NEWLINE + body; send(fd, request.c_str(), static_cast(request.size()), 0); kit_str_builder_t buffer = kit_http1_buffered_read(fd); closesocket(fd); kit_http1_response_t result = { .success = 1 }; kit_http1_tok_t bt(buffer); result.protocol = bt.next(KIT_HTTP1_SPACE, 0); result.response = bt.next(KIT_HTTP1_SPACE, 0); result.response_str = bt.next(KIT_HTTP1_NEWLINE, 0); kit_str_builder_t header = bt.next( KIT_HTTP1_NEWLINE KIT_HTTP1_NEWLINE, 0); result.body = bt.tail(); kit_http1_tok_t ht(header); do { kit_str_builder_t key = ht.next(KIT_HTTP1_HEADER_SEPARATOR, 0); if (key.size == 0) break; result.header[key] = ht.next(KIT_HTTP1_NEWLINE, 1); } while (1); return result; } #ifdef __cplusplus } #endif #endif