From c6ca4dd37b695424771df0852ac1a99941d3974b Mon Sep 17 00:00:00 2001 From: Zhi Guan Date: Wed, 18 Jan 2023 00:27:35 +0800 Subject: [PATCH] Add HTTP module --- CMakeLists.txt | 2 + include/gmssl/http.h | 29 +++++++ src/http.c | 181 +++++++++++++++++++++++++++++++++++++++++++ tests/httptest.c | 93 ++++++++++++++++++++++ 4 files changed, 305 insertions(+) create mode 100644 include/gmssl/http.h create mode 100644 src/http.c create mode 100644 tests/httptest.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 191f24ed..da1af8f4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -73,6 +73,7 @@ set(src src/tls12.c src/tls13.c src/file.c + src/http.c ) set(tools @@ -154,6 +155,7 @@ set(tests cms tls tls13 + http ) diff --git a/include/gmssl/http.h b/include/gmssl/http.h new file mode 100644 index 00000000..157b71be --- /dev/null +++ b/include/gmssl/http.h @@ -0,0 +1,29 @@ +/* + * Copyright 2014-2023 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + + +#ifndef GMSSL_HTTP_H +#define GMSSL_HTTP_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +int http_parse_uri(const char *uri, char host[128], int *port, char path[256]); +int http_parse_response(uint8_t *buf, size_t buflen, uint8_t **content, size_t *contentlen, size_t *left); +int http_get(const char *uri, uint8_t *buf, size_t buflen, uint8_t **content, size_t *contentlen); + + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/http.c b/src/http.c new file mode 100644 index 00000000..8342fe98 --- /dev/null +++ b/src/http.c @@ -0,0 +1,181 @@ + +/* + * Copyright 2014-2023 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + + +#include +#include +#include +#include +#include +#include +#include +#include + + +int http_parse_uri(const char *uri, char host[128], int *port, char path[256]) +{ + if (!uri || !host || !port || !path) { + error_print(); + return -1; + } + + *host = 0; + *port = 80; + *path++ = '/'; + *path = 0; + + if (sscanf(uri, "http://%127[^:]:%i/%254[^\n]", host, port, path) == 3); + else if (sscanf(uri, "http://%127[^/]/%254[^\n]", host, path) == 2); + else if (sscanf(uri, "http://%127[^:]:%i[^/][^\n]", host, port) == 2); + else if (sscanf(uri, "http://%127[^/][^\n]", host) == 1); + else { + error_print(); + return -1; + } + if (!host[0] || strchr(host, '/') || strchr(host, ':')) { + error_print(); + return -1; + } + if (*port <= 0) { + error_print(); + return -1; + } + + return 1; +} + +static int socket_recv_all(int sock, uint8_t *buf, size_t len) +{ + size_t n; + while (len) { + if ((n = recv(sock, buf, len, 0)) <= 0) { + error_print(); + return -1; + } + buf += n; + len -= n; + } + return 1; +} + +int http_parse_response(uint8_t *buf, size_t buflen, uint8_t **content, size_t *contentlen, size_t *left) +{ + char *ok = "HTTP/1.1 200 OK\r\n"; + char *p; + size_t headerlen; + + if (buflen < strlen(ok) || memcmp(buf, ok, strlen(ok)) != 0) { + error_print(); + return -1; + } + if (!(p = strnstr((char *)buf, "\r\n\r\n", buflen))) { + error_print(); + return -1; + } + *content = (uint8_t *)(p + 4); + headerlen = *content - buf; + + if (!(p = strnstr((char *)buf, "\r\nContent-Length: ", headerlen))) { + error_print(); + return -1; + } + p += strlen("\r\nContent-Length: "); + *contentlen = atoi(p); + if (*contentlen <= 0 || *contentlen > INT_MAX) { + error_print(); + return -1; + } + + buflen -= headerlen; + if (buflen < *contentlen) + *left = *contentlen - buflen; + else *left = 0; + + return 1; +} + + +#define HTTP_GET_TEMPLATE "GET %s HTTP/1.1\r\n" "Host: %s\r\n" "\r\n\r\n" + +int http_get(const char *uri, uint8_t *buf, size_t buflen, + uint8_t **content, size_t *contentlen) +{ + char *uribuf = NULL; + char host[128]; + int port; + char path[256]; + struct hostent *hp; + struct sockaddr_in server; + tls_socket_t sock; + char get[sizeof(HTTP_GET_TEMPLATE) + sizeof(host) + sizeof(path)]; + int getlen; + size_t len; + size_t left; + + // parse uri + if (http_parse_uri(uri, host, &port, path) != 1) { + error_print(); + return -1; + } + + // connect + if (!(hp = gethostbyname(host))) { + error_print(); + return -1; + } + server.sin_addr = *((struct in_addr *)hp->h_addr_list[0]); + server.sin_family = AF_INET; + server.sin_port = htons(port); + + if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + error_print(); + return -1; + } + if (connect(sock, (struct sockaddr *)&server , sizeof(server)) < 0) { + error_print(); + return -1; + } + + // request + if ((getlen = snprintf(get, sizeof(get), + "GET %s HTTP/1.1\r\n" + "Host: %s\r\n" + "\r\n\r\n", path, host)) <= 0) { + error_print(); + return -1; + } + if (send(sock, get, strlen(get), 0) != getlen) { + error_print(); + return -1; + } + + // response + if ((len = recv(sock, buf, buflen, 0)) <= 0) { + error_print(); + return -1; + } + if (http_parse_response(buf, len, content, contentlen, &left) != 1) { + error_print(); + return -1; + } + if (left) { + if (len + left > buflen) { + error_print(); // buf is not enough + return -1; + } + if (socket_recv_all(sock, buf + len, left) != 1) { + error_print(); + return -1; + } + } + + return 1; +} + diff --git a/tests/httptest.c b/tests/httptest.c new file mode 100644 index 00000000..680f3628 --- /dev/null +++ b/tests/httptest.c @@ -0,0 +1,93 @@ +/* + * Copyright 2014-2022 The GmSSL Project. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); you may + * not use this file except in compliance with the License. + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + + +#include +#include +#include +#include +#include + + +static int test_http_parse_uri(void) +{ + char *tests[] = { + "http://www.example.com:8080/ca/ca2023.crl", + "http://www.example.com:80/ca/ca2023.crl", + "http://www.example.com/ca/ca2023.crl", + "http://www.example.com/ca2023.crl", + "http://www.example.com:8080/", + "http://www.example.com:8080", + "http://www.example.com/", + "http://www.example.com", + }; + size_t i; + + char host[128]; + int port; + char path[256]; + + for (i = 0; i < sizeof(tests)/sizeof(tests[0]); i++) { + if (http_parse_uri(tests[i], host, &port, path) != 1) { + fprintf(stderr, "error: tests[%zu]: %s\n", i, tests[i]); + error_print(); + return -1; + } + printf("%s: host = %s, port = %d, path = %s\n", tests[i], host, port, path); + } + + printf("%s() ok\n", __FUNCTION__); + return 1; +} + +static int test_http_parse_uri_bad(void) +{ + char *tests[] = { + "ldap://www.example.com:8080/ca/ca2023.crl", + "http://www.example.com::8080/ca/ca2023.crl", + "http://www.example.com:8080:/ca/ca2023.crl", + "http://www.example.com:-100/ca/ca2023.crl", + "http://www.example.com:/ca/ca2023.crl", + "http:///ca2023.crl", + "http:///", + "http://", + }; + size_t i; + + char host[128]; + int port; + char path[256]; + + for (i = 0; i < sizeof(tests)/sizeof(tests[0]); i++) { + if (http_parse_uri(tests[i], host, &port, path) != -1) { + fprintf(stderr, "error: tests[%zu]: %s\n", i, tests[i]); + printf("%s: host = %s, port = %d, path = %s\n", tests[i], host, port, path); + error_print(); + return -1; + } + } + + printf("%s() ok\n", __FUNCTION__); + return 1; +} + + + + + + + + +int main(void) +{ + if (test_http_parse_uri() != 1) { error_print(); return -1; } + if (test_http_parse_uri_bad() != 1) { error_print(); return -1; } + printf("%s all tests passed\n", __FILE__); + return 0; +}