diff --git a/TODO.txt b/TODO.txt index 5654603..26239d5 100644 --- a/TODO.txt +++ b/TODO.txt @@ -15,7 +15,10 @@ Milestone: v0.3.0 [ ] TLS [ ] SOCKS [ ] Multi-output -[ ] HTTP Client API +[~] HTTP Client API + [ ] Document HttpParseHeaders() + [ ] HttpClient man page + [ ] Test on other platforms [ ] Option to pretty-print Json [ ] Simple command line tool to make matrix requests diff --git a/src/Http.c b/src/Http.c index 7ac9ead..653de82 100644 --- a/src/Http.c +++ b/src/Http.c @@ -26,9 +26,11 @@ #include #include #include +#include #include #include +#include #ifndef TELODENDRIA_STRING_CHUNK #define TELODENDRIA_STRING_CHUNK 64 @@ -532,3 +534,105 @@ HttpParamEncode(HashMap * params) return out; } + +HashMap * +HttpParseHeaders(FILE * fp) +{ + HashMap *headers; + + char *line; + ssize_t lineLen; + size_t lineSize; + + char *headerKey; + char *headerValue; + + if (!fp) + { + return NULL; + } + + + headers = HashMapCreate(); + if (!headers) + { + return NULL; + } + + line = NULL; + lineLen = 0; + + while ((lineLen = UtilGetLine(&line, &lineSize, fp)) != -1) + { + char *headerPtr; + + ssize_t i; + + if (strcmp(line, "\r\n") == 0 || strcmp(line, "\n") == 0) + { + break; + } + + for (i = 0; i < lineLen; i++) + { + if (line[i] == ':') + { + line[i] = '\0'; + break; + } + + line[i] = tolower((unsigned char) line[i]); + } + + headerKey = Malloc((i + 1) * sizeof(char)); + if (!headerKey) + { + goto error; + } + + strcpy(headerKey, line); + + headerPtr = line + i + 1; + + while (isspace((unsigned char) *headerPtr)) + { + headerPtr++; + } + + for (i = lineLen - 1; i > (line + lineLen) - headerPtr; i--) + { + if (!isspace((unsigned char) line[i])) + { + break; + } + line[i] = '\0'; + } + + headerValue = Malloc(strlen(headerPtr) + 1); + if (!headerValue) + { + Free(headerKey); + goto error; + } + + strcpy(headerValue, headerPtr); + + HashMapSet(headers, headerKey, headerValue); + Free(headerKey); + } + + Free(line); + return headers; + +error: + Free(line); + + while (HashMapIterate(headers, &headerKey, (void **) &headerValue)) + { + Free(headerValue); + } + + HashMapFree(headers); + + return NULL; +} diff --git a/src/HttpClient.c b/src/HttpClient.c new file mode 100644 index 0000000..c6ca49f --- /dev/null +++ b/src/HttpClient.c @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * 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 portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +struct HttpClientContext +{ + HashMap *responseHeaders; + FILE *stream; +}; + +HttpClientContext * +HttpRequest(HttpRequestMethod method, int flags, char *host, char *path, char *port) +{ + HttpClientContext *context; + + int sd; + struct addrinfo hints, *res, *res0; + int error; + + if (!method || !host || !path) + { + return NULL; + } + + /* TODO: Not supported yet */ + if (flags & HTTP_TLS) + { + return NULL; + } + + context = Malloc(sizeof(HttpClientContext)); + if (!context) + { + return NULL; + } + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + error = getaddrinfo(host, port, &hints, &res0); + + if (error) + { + Free(context); + return NULL; + } + + for (res = res0; res; res = res->ai_next) + { + sd = socket(res->ai_family, res->ai_socktype, + res->ai_protocol); + + if (sd < 0) + { + continue; + } + + if (connect(sd, res->ai_addr, res->ai_addrlen) < 0) + { + close(sd); + sd = -1; + continue; + } + + break; + } + + if (sd < 0) + { + Free(context); + return NULL; + } + + freeaddrinfo(res0); + + context->stream = fdopen(sd, "r+"); + if (!context->stream) + { + Free(context); + close(sd); + return NULL; + } + + fprintf(context->stream, "%s %s HTTP/1.0\r\n", + HttpRequestMethodToString(method), path); + + HttpRequestHeader(context, "Connection", "close"); + HttpRequestHeader(context, "User-Agent", "Telodendria/" TELODENDRIA_VERSION); + + return context; +} + +void +HttpRequestHeader(HttpClientContext * context, char *key, char *val) +{ + if (!context || !key || !val) + { + return; + } + + fprintf(context->stream, "%s: %s\r\n", key, val); +} + +HttpStatus +HttpRequestSend(HttpClientContext * context) +{ + HttpStatus status; + + char *line = NULL; + ssize_t lineLen; + size_t lineSize = 0; + char *tmp; + + if (!context) + { + return 0; + } + + fprintf(context->stream, "\r\n"); + fflush(context->stream); + + lineLen = UtilGetLine(&line, &lineSize, context->stream); + + /* Line must contain at least "HTTP/x.x xxx" */ + if (lineLen < 12) + { + return 0; + } + + if (!(strncmp(line, "HTTP/1.0", 8) == 0 || + strncmp(line, "HTTP/1.1", 8) == 0)) + { + return 0; + } + + tmp = line + 9; + + while (isspace(*tmp) && *tmp != '\0') + { + tmp++; + } + + if (!*tmp) + { + return 0; + } + + status = atoi(tmp); + + if (!status) + { + return 0; + } + + context->responseHeaders = HttpParseHeaders(context->stream); + if (!context->responseHeaders) + { + return 0; + } + + return status; +} + +HashMap * +HttpResponseHeaders(HttpClientContext * context) +{ + if (!context) + { + return NULL; + } + + return context->responseHeaders; +} + +FILE * +HttpClientStream(HttpClientContext * context) +{ + if (!context) + { + return NULL; + } + + return context->stream; +} + +void +HttpClientContextFree(HttpClientContext * context) +{ + char *key; + void *val; + + if (!context) + { + return; + } + + while (HashMapIterate(context->responseHeaders, &key, &val)) + { + Free(val); + } + + HashMapFree(context->responseHeaders); + + fclose(context->stream); + Free(context); +} diff --git a/src/HttpServer.c b/src/HttpServer.c index 475fe58..fcdf2e9 100644 --- a/src/HttpServer.c +++ b/src/HttpServer.c @@ -96,13 +96,6 @@ HttpServerContextCreate(HttpRequestMethod requestMethod, return NULL; } - c->requestHeaders = HashMapCreate(); - if (!c->requestHeaders) - { - Free(c); - return NULL; - } - c->responseHeaders = HashMapCreate(); if (!c->responseHeaders) { @@ -515,6 +508,7 @@ HttpServerWorkerThread(void *args) if (strcmp(requestProtocol, "HTTP/1.1") != 0 && strcmp(requestProtocol, "HTTP/1.0") != 0) { + Free(requestPath); goto bad_request; } @@ -533,65 +527,14 @@ HttpServerWorkerThread(void *args) context = HttpServerContextCreate(requestMethod, requestPath, requestParams, fp); if (!context) { + Free(requestPath); goto internal_error; } - while ((lineLen = UtilGetLine(&line, &lineSize, fp)) != -1) + context->requestHeaders = HttpParseHeaders(fp); + if (!context->requestHeaders) { - char *headerKey; - char *headerValue; - char *headerPtr; - - if (strcmp(line, "\r\n") == 0) - { - break; - } - - for (i = 0; i < lineLen; i++) - { - if (line[i] == ':') - { - line[i] = '\0'; - break; - } - - line[i] = tolower((unsigned char) line[i]); - } - - headerKey = Malloc((i + 1) * sizeof(char)); - if (!headerKey) - { - goto internal_error; - } - - strcpy(headerKey, line); - - headerPtr = line + i + 1; - - while (isspace((unsigned char) *headerPtr)) - { - headerPtr++; - } - - for (i = lineLen - 1; i > (line + lineLen) - headerPtr; i--) - { - if (!isspace((unsigned char) line[i])) - { - break; - } - line[i] = '\0'; - } - - headerValue = Malloc(strlen(headerPtr) + 1); - if (!headerValue) - { - goto internal_error; - } - - strcpy(headerValue, headerPtr); - - HashMapSet(context->requestHeaders, headerKey, headerValue); - Free(headerKey); + goto internal_error; } server->requestHandler(context, server->handlerArgs); diff --git a/src/include/Http.h b/src/include/Http.h index 491aedf..3a0036b 100644 --- a/src/include/Http.h +++ b/src/include/Http.h @@ -125,4 +125,7 @@ extern HashMap * extern char * HttpParamEncode(HashMap *); +extern HashMap * + HttpParseHeaders(FILE *); + #endif diff --git a/src/include/HttpClient.h b/src/include/HttpClient.h new file mode 100644 index 0000000..c99679b --- /dev/null +++ b/src/include/HttpClient.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * 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 portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef TELODENDRIA_HTTPCLIENT_H +#define TELODENDRIA_HTTPCLIENT_H + +#include + +#include +#include + +#define HTTP_NONE 0 +#define HTTP_TLS (1 << 0) + +typedef struct HttpClientContext HttpClientContext; + +extern HttpClientContext * + HttpRequest(HttpRequestMethod, int, char *, char *, char *); + +extern void + HttpRequestHeader(HttpClientContext *, char *, char *); + +extern HttpStatus + HttpRequestSend(HttpClientContext *); + +extern HashMap * + HttpResponseHeaders(HttpClientContext *); + +extern FILE * + HttpClientStream(HttpClientContext *); + +extern void + HttpClientContextFree(HttpClientContext *); + +#endif /* TELODENDRIA_HTTPCLIENT_H */