diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..f9eccf1 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.21) +project(http_client_c C) + +set(CMAKE_C_STANDARD 11) +add_compile_options(-fms-extensions) + +include_directories(${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/src/encoding) + +#file(GLOB SOURCES +# ${SOURCES}/src/encoding/* +# ) + +add_executable(http_client_c + ${CMAKE_SOURCE_DIR}/test/main.c + ) + +#add_executable(http_chunked_c +# ${CMAKE_SOURCE_DIR}/test/chunked.c +# ) + + +target_link_libraries(http_client_c m) +#target_link_libraries(http_chunked_c m) \ No newline at end of file diff --git a/gpl-3.0.txt b/LICENSE.md similarity index 100% rename from gpl-3.0.txt rename to LICENSE.md diff --git a/README.md b/README.md index 42a6839..3bd395d 100644 --- a/README.md +++ b/README.md @@ -10,98 +10,239 @@ in C, it can be used in C++ code as well. Basic Usage =============== -http_response + +```c + #include "http/client.h" + + http_request *request = http_request_new(); + + http_request_option(request, HTTP_OPTION_URL, argv[1], 0); + // http_request_option(request, HTTP_OPTION_METHOD, "POST"); + // http_request_option(request, HTTP_OPTION_BODY, "a=1&b=2"); + http_request_option(request, HTTP_OPTION_REQUEST_TIMEOUT, "5", 0); + http_request_option(request, HTTP_OPTION_REQUEST_HEADER_CALLBACK, request_header_cb, 0); + http_request_option(request, HTTP_OPTION_RESPONSE_HEADER_CALLBACK, response_header_cb, 0); + http_request_option(request, HTTP_OPTION_RESPONSE_BODY_CALLBACK, response_body_cb, 0); + // add an HTTP header, replace existing + http_request_header_set(request, "User-Agent", "Firefox 12"); + // http_request_header_set(request, "Authorization", "Bearer "); + // add an HTTP header, keep existing + http_request_header_add(request, "Accept-Language", "en-US;q=0.6,en;q=0.4"); + + // execute the request + http_response *response = http_request_exec(request); + + http_request_free(request); + http_response_free(response); +``` + +## http_request + +the request structure. + +```c + +typedef struct http_request { + http_header *headers; + size_t body_len; + char *body; + char *request_uri; + int max_redirect; + char *method; + http_header_cb_ptr *request_header_cb; + http_header_cb_ptr *response_header_cb; + http_response_body_cb_ptr *response_body_cb; + struct timeval *request_timeout; +} http_request; + +``` + +## create a request struct pointer. + +```c +http_request *request = http_request_new(); +``` + +## configure the request options + +```c + +// http_request *hreq - the request struct pointer +// http_option option - the option to configure +// const void *val - option payload +// size_t len - the payload length, only needed if the payload is a binary string, otherwise pass 0 +// void http_request_option(http_request *hreq, http_option option, const void *val, size_t len) + + + http_request_option(request, HTTP_OPTION_URL, argv[1], 0); +// http_request_option(request, HTTP_OPTION_METHOD, "POST"); +// http_request_option(request, HTTP_OPTION_BODY, "a=1&b=2"); + http_request_option(request, HTTP_OPTION_REQUEST_TIMEOUT, "5", 0); + http_request_option(request, HTTP_OPTION_REQUEST_HEADER_CALLBACK, request_header_cb, 0); + http_request_option(request, HTTP_OPTION_RESPONSE_HEADER_CALLBACK, response_header_cb, 0); + http_request_option(request, HTTP_OPTION_RESPONSE_BODY_CALLBACK, response_body_cb, 0); + http_request_header_set(request, "User-Agent", "Firefox 12"); +// http_request_header_set(request, "Authorization", "Bearer "); + http_request_header_add(request, "Accept-Language", "en-US;q=0.6,en;q=0.4"); +``` + +### http option flags +- HTTP_OPTION_URL: pass the url +- HTTP_OPTION_HEADER: pass an HTTP header as a _struct http_header_ pointer +- HTTP_OPTION_BODY: pass the request payload +- HTTP_OPTION_METHOD: pass the HTTP method +- HTTP_OPTION_REQUEST_TIMEOUT: set the request timeout as a numeric string +- HTTP_OPTION_REQUEST_HEADER_CALLBACK: pass a request header callback (see ./test/main.c for an example) +- HTTP_OPTION_RESPONSE_HEADER_CALLBACK: pass a response header callback (see ./test/main.c for an example) +- HTTP_OPTION_RESPONSE_BODY_CALLBACK: pass a response body callback (see ./test/main.c for an example) + +http_response ------------- -http_response is a structure that is returned by all http_* methods, it contains information about the requse and the response. -Please note that all functions return a pointer to an insance of http_response. The structure is as following: - - struct http_response - { - struct parsed_url *request_uri; - char *body; - char *status_code; - int status_code_int; - char *status_text; - char *request_headers; - char *response_headers; - }; - -#####*request_uri -This is an instance of the parsed_url structure, this contains the request URL and all information about the request -URL. Look up parsed_url for more information. - -#####*body -This contains the response BODY (usually HTML). - -#####*status_code -This contains the HTTP Status code returned by the server in plain text format. - -#####status_code_int -This returns the same as status_code but as an integer. - -#####*status_text +http_response is a structure that is returned by _http_request_exec_ method, it contains information about the response. +Please note that this function returns a pointer to an instance of http_response. The structure is as following: + +```c + +typedef struct http_response { + http_header *headers; + char *body; + size_t body_len; + char *redirect_uri; + int redirect_count; + int status_code; + char *status_text; +} http_response; +``` + +##### *redirect_ui +The last redirected url. + +##### *redirect_count +the HTTP redirect count + +##### *body +The response body. always NULL if you set a response body callback + +##### body_len +the response body length + +##### status_code +the response HTTP status code + +##### *status_text This returns the text associated with the status code. For status code 200, OK will be returned. -#####*request_headers -This contains the HTTP headers that were used to make the request. +##### *headers +response HTTP headers as _struct http_header_ pointer. -#####*response_headers -Contains the HTTP headers returned by the server. +## HTTP response callback -http_req() -------------- -http_req is the basis for all other http_* methodes and makes and HTTP request and returns an instance of the http_response structure. +Provide an HTTP response body callback to handle large HTTP response payload efficiently. +this will avoid allocating memory to hold the response. -The prototype for this function is: +## full example - struct http_response* http_req(char *http_headers, struct parsed_url *purl) - -A simple example is: - - struct parsed_url *purl = parse_url("http://www.google.com/"); - struct http_response *hrep = http_req("GET / HTTP/1.1\r\nHostname:www.google.com\r\nConnection:close\r\n\r\n", purl); +```c -Please note that http_req does not handle redirects. (Status code 300-399) +#include "http/client.h" -http_get() -------------- -Makes an HTTP GET request to the specified URL. This function makes use of the http_req function. It specifies -the minimal headers required, in the second parameter you can specify extra headers. - -The prototype for this function is: - - struct http_response* http_get(char *url, char *custom_headers) - -A simple example is: - - struct http_response *hresp = http_get("http://www.google.com", "User-agent:MyUserAgent\r\n"); - -http_get does handle redirects automaticly. The basic headers used in this method: - - GET / HTTP/1.1 - Hostname:www.google.com - Connection:close - -http_post ------------- -Makes an HTTP POST request to the specified URL. This function makes use of the http_req function. It specifies -the minimal headers required, in the second parameter you can specify extra headers. In the third parameter -the post data can be specified. - -The prototype for this function is: - - struct http_response* http_post(char *url, char *custom_headers, char *post_data) - -A simple example is: - - struct http_response *hresp = http_post("http://mywebsite.com/login.php", "User-agent:MyuserAgent\r\n", "username=Kirk&password=lol123"); - -http_post does handle redirects automaticly. The basic headers used in this method: - - POST /login.php HTTP/1.1 - Hostname:mywebsite.com - Connection:close - - username=Kirk&password=lol123 - +FILE *fp; + +/** + * response header callback + * @param headers + */ +void response_header_cb(http_header *headers) { + + http_header *header = headers; + + fprintf(stderr, "headers received:\n"); + + char *printed = http_header_print(headers); + + fprintf(stderr, "%s\r\n", printed); + + fwrite(printed, strlen(printed), 1, fp); + fwrite("\r\n", 2, 1, fp); + free(printed); +} + +/** + * request header callback + * @param headers + */ +void request_header_cb(http_header *headers) { + + http_header *header = headers; + + fprintf(stderr, "headers sent:\n"); + + char *printed = http_header_print(headers); + + fprintf(stderr, "%s\r\n", printed); + + fwrite(printed, strlen(printed), 1, fp); + fwrite("\r\n", 2, 1, fp); + free(printed); +} + +/** + * response body callback + * @param chunk + * @param chunk_len + * @param headers + */ +void response_body_cb(const char *chunk, size_t chunk_len, http_header *headers) { + + if (chunk_len > 0) { + + fwrite(chunk, 1, chunk_len, fp); + + http_header *content_type = http_header_get(headers, "Content-Type"); + + // if text content, dump to stderr + if (content_type != NULL && strstr(content_type->value, "text/") != NULL) { + + fwrite(chunk, chunk_len, 1, stderr); + } + + http_header_free(content_type); + } +} + +int main(int argc, char *argv[]) { + + if (argc <= 2) { + + fprintf(stderr, "Usage: \n$ %s URL DEST_FILE\n", argv[0]); + exit(1); + } + + char *filename = argc > 2 ? argv[2] : ""; + fprintf(stderr, "opening %s ...\n", filename); + + http_request *request = http_request_new(); + + http_request_option(request, HTTP_OPTION_URL, argv[1], 0); + // http_request_option(request, HTTP_OPTION_METHOD, "POST"); + // http_request_option(request, HTTP_OPTION_BODY, "a=1&b=2"); + http_request_option(request, HTTP_OPTION_REQUEST_TIMEOUT, "5", 0); + http_request_option(request, HTTP_OPTION_REQUEST_HEADER_CALLBACK, request_header_cb, 0); + http_request_option(request, HTTP_OPTION_RESPONSE_HEADER_CALLBACK, response_header_cb, 0); + http_request_option(request, HTTP_OPTION_RESPONSE_BODY_CALLBACK, response_body_cb, 0); + http_request_header_set(request, "User-Agent", "Firevox"); + // http_request_header_set(request, "Authorization", "Bearer "); + http_request_header_add(request, "Accept-Language", "en-US;q=0.6,en;q=0.4"); + + fp = fopen(filename, "wb"); + + http_response *response = http_request_exec(request); + + fclose(fp); + + http_request_free(request); + http_response_free(response); + return 0; +} +``` \ No newline at end of file diff --git a/src/base64.h b/src/base64.h new file mode 100644 index 0000000..2ed5eca --- /dev/null +++ b/src/base64.h @@ -0,0 +1,168 @@ +#include +#include + +#ifndef BASE64_DEF_H +#define BASE64_DEF_H + +char *base64_encode(const unsigned char *in, size_t size); + +char *base64_decode(const char *in, size_t *size); + +// from https://nachtimwald.com/2017/11/18/base64-encode-and-decode-in-c/ + +const char b64chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +int b64invs[] = {62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, + 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, + 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, + 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, + 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, + 43, 44, 45, 46, 47, 48, 49, 50, 51}; + +/* + * generate int b64invs[]; +void b64_generate_decode_table() +{ + int inv[80]; + size_t i; + + memset(inv, -1, sizeof(inv)); + for (i=0; i 0;) { + if (in[i] == '=') { + ret--; + } else { + break; + } + } + + return ret; +} + +int b64_isvalidchar(char c) { + if (c >= '0' && c <= '9') + return 1; + if (c >= 'A' && c <= 'Z') + return 1; + if (c >= 'a' && c <= 'z') + return 1; + if (c == '+' || c == '/' || c == '=') + return 1; + return 0; +} + +/* + Encodes a string with Base64 +*/ +char *base64_encode(const unsigned char *in, size_t len) { + char *out; + size_t elen; + size_t i; + size_t j; + size_t v; + + if (in == NULL || len == 0) + return NULL; + + elen = b64_encoded_size(len); + out = malloc(elen + 1); + out[elen] = '\0'; + + for (i = 0, j = 0; i < len; i += 3, j += 4) { + v = in[i]; + v = i + 1 < len ? v << 8 | in[i + 1] : v << 8; + v = i + 2 < len ? v << 8 | in[i + 2] : v << 8; + + out[j] = b64chars[(v >> 18) & 0x3F]; + out[j + 1] = b64chars[(v >> 12) & 0x3F]; + if (i + 1 < len) { + out[j + 2] = b64chars[(v >> 6) & 0x3F]; + } else { + out[j + 2] = '='; + } + if (i + 2 < len) { + out[j + 3] = b64chars[v & 0x3F]; + } else { + out[j + 3] = '='; + } + } + + return out; +} + +char *base64_decode(const char *in, size_t *size) { + size_t len; + size_t i; + size_t j; + int v; + size_t outlen = b64_decoded_size(in); + char *out = (char *) malloc(sizeof(char) * outlen + 1); + char *outpstr = out; + + memset(out, 0, outlen); + + if (in == NULL || out == NULL) + return 0; + + len = strlen(in); + if (outlen < b64_decoded_size(in) || len % 4 != 0) + return 0; + + for (i = 0; i < len; i++) { + if (!b64_isvalidchar(in[i])) { + return 0; + } + } + + for (i = 0, j = 0; i < len; i += 4, j += 3) { + v = b64invs[in[i] - 43]; + v = (v << 6) | b64invs[in[i + 1] - 43]; + v = in[i + 2] == '=' ? v << 6 : (v << 6) | b64invs[in[i + 2] - 43]; + v = in[i + 3] == '=' ? v << 6 : (v << 6) | b64invs[in[i + 3] - 43]; + + out[j] = (v >> 16) & 0xFF; + outpstr = &out[j]; + + if (in[i + 2] != '=') { + + out[j + 1] = (v >> 8) & 0xFF; + outpstr = &out[j + 1]; + } + if (in[i + 3] != '=') { + + out[j + 2] = v & 0xFF; + outpstr = &out[j + 2]; + } + } + + *size = outpstr - out + 1; + return out; +} + +#endif diff --git a/src/charset/utf8.h b/src/charset/utf8.h new file mode 100644 index 0000000..553496e --- /dev/null +++ b/src/charset/utf8.h @@ -0,0 +1,147 @@ +// https://dev.to/rdentato/utf-8-strings-in-c-1-3-42a4 + +#include +#include "stdint.h" + +typedef uint32_t u8chr_t; + +static uint8_t const u8_length[] = { +// 0 1 2 3 4 5 6 7 8 9 A B C D E F + 1,1,1,1,1,1,1,1,0,0,0,0,2,2,3,4 +}; + +#define u8length(s) u8_length[(((uint8_t *)(s))[0] & 0xFF) >> 4]; + +/** + * valid utf-8 char + * @param c + * @return + */ +int u8chrisvalid(u8chr_t c) +{ + if (c <= 0x7F) return 1; // [1] + + if (0xC280 <= c && c <= 0xDFBF) // [2] + return ((c & 0xE0C0) == 0xC080); + + if (0xEDA080 <= c && c <= 0xEDBFBF) // [3] + return 0; // Reject UTF-16 surrogates + + if (0xE0A080 <= c && c <= 0xEFBFBF) // [4] + return ((c & 0xF0C0C0) == 0xE08080); + + if (0xF0908080 <= c && c <= 0xF48FBFBF) // [5] + return ((c & 0xF8C0C0C0) == 0xF0808080); + + return 0; +} + +/** + * next utf-8 char + * @param txt + * @param ch + * @return + */ +size_t u8next(unsigned char *txt, u8chr_t *ch) +{ + size_t len; + u8chr_t encoding = 0; + + len = u8length(txt); + + for (int i=0; i0) && ((k-i) < 3) && ((dest[i] & 0xC0) == 0x80); i--) ; + switch(k-i) { + case 0: dest[i] = '\0'; break; + case 1: if ( (dest[i] & 0xE0) != 0xC0) dest[i] = '\0'; break; + case 2: if ( (dest[i] & 0xF0) != 0xE0) dest[i] = '\0'; break; + case 3: if ( (dest[i] & 0xF8) != 0xF0) dest[i] = '\0'; break; + } + } + } + return dest; +} + +// from UTF-8 encoding to Unicode Codepoint +uint32_t u8decode(u8chr_t c) +{ + uint32_t mask; + + if (c > 0x7F) { + mask = (c <= 0x00EFBFBF)? 0x000F0000 : 0x003F0000 ; + c = ((c & 0x07000000) >> 6) | + ((c & mask ) >> 4) | + ((c & 0x00003F00) >> 2) | + (c & 0x0000003F); + } + + return c; +} + +// From Unicode Codepoint to UTF-8 encoding +u8chr_t u8encode(uint32_t codepoint) +{ + u8chr_t c = codepoint; + + if (codepoint > 0x7F) { + c = (codepoint & 0x000003F) + | (codepoint & 0x0000FC0) << 2 + | (codepoint & 0x003F000) << 4 + | (codepoint & 0x01C0000) << 6; + + if (codepoint < 0x0000800) c |= 0x0000C080; + else if (codepoint < 0x0010000) c |= 0x00E08080; + else c |= 0xF0808080; + } + return c; +} diff --git a/src/encoding/chunked.h b/src/encoding/chunked.h new file mode 100644 index 0000000..296b279 --- /dev/null +++ b/src/encoding/chunked.h @@ -0,0 +1,199 @@ +#ifndef HTTP_CLIENT_C_CHUNKED_H +#define HTTP_CLIENT_C_CHUNKED_H + +#include "encoding/struct.h" +#include "error.h" +#include "stringx.h" +#include "charset/utf8.h" + +int http_chunked_transfer_block_info(const char *buf, long buf_len, size_t *offset, size_t *data_len); + +int http_chunked_transfer_decode(int sock, char *buf, size_t data_len, size_t offset, http_request *, http_response *); + +void handle_response_body(const http_request *hreq, http_response *hresp, const unsigned char *mb_str, size_t _len); + +/** + * + * @param buf buffer + * @param buf_len buffer length + * @param offset data offset + * @return + */ +http_client_errors http_chunked_transfer_block_info(const char *buf, long buf_len, size_t *offset, size_t *data_len) { + + long pos = -1; + + *data_len = 0; + *offset = 0; + + while (pos++ < buf_len) { + + if (buf[pos] == '\r' && pos + 1 < buf_len && buf[pos + 1] == '\n') { + + break; + } + } + + if (pos >= 1) { + + char chunk_size[pos + 1]; + + memcpy(chunk_size, buf, pos); + chunk_size[pos] = '\0'; + + *offset = pos + 2; + *data_len = hex2dec(chunk_size, pos); + } + + return HTTP_CLIENT_ERROR_OK; +} + +char *u8block_info(char *data, size_t bytes_read, size_t *mb_len) { + + char *block = (char *) calloc(bytes_read + 1, 1); + + u8strncpy(block, data, bytes_read); + *mb_len = u8strlen(block); + + return block; +} + +int http_chunked_transfer_decode(int sock, char *buf, size_t buf_len, size_t offset, http_request *hreq, + http_response *hresp) { + + size_t block_size = 0; + size_t block_offset = 0; + + // multibyte string length + size_t mb_len; + char *mb_str = NULL; + ssize_t received_len = buf_len; + + int status; + + if (received_len - 1 > offset) { + + status = http_chunked_transfer_block_info(&buf[offset], buf_len - offset, &block_offset, &block_size); + + if (status < 0) { + + return HTTP_CLIENT_ERROR_RECV; + } + + if (block_size == 0) { + + return HTTP_CLIENT_ERROR_OK; + } + + while (block_size > 0) { + + partial_read: + + offset += block_offset; + mb_str = u8block_info(&buf[offset], block_size, &mb_len); + + size_t _len = strlen(mb_str); + + if (_len == 0) { + + char *end = strstr(&buf[offset], "\r\n"); + + if (end != NULL) { + + block_size = end - &buf[offset]; + handle_response_body(hreq, hresp, &buf[offset], block_size); + + offset += block_size; + goto block_info; + } + + handle_response_body(hreq, hresp, &buf[offset], received_len - offset); + return HTTP_CLIENT_ERROR_OK; + } + + handle_response_body(hreq, hresp, mb_str, _len); + + hresp->body_len += _len; + offset += _len; + block_size -= mb_len; + block_offset = 0; + + free(mb_str); + mb_str = NULL; + + if (offset < received_len) { + + if (block_size > 0) { + + goto partial_read; + } + + if (block_size == 0) { + + block_info: + offset += 2; + status = http_chunked_transfer_block_info(&buf[offset], received_len - offset, &block_offset, + &block_size); + + if (status < 0) { + + return HTTP_CLIENT_ERROR_RECV; + } + + if (block_size == 0) { + + return HTTP_CLIENT_ERROR_OK; + } + + goto partial_read; + } + } + + received_len = recv(sock, (void *) buf, BUF_READ - 1, 0); + offset = 0; + + if (received_len > 0) { + + buf[received_len] = '\0'; + + goto partial_read; + } + + if (received_len == 0) { + + return HTTP_CLIENT_ERROR_OK; + } + + return HTTP_CLIENT_ERROR_RECV; + } + } + + return HTTP_CLIENT_ERROR_OK; +} + +void handle_response_body(const http_request *hreq, http_response *hresp, const unsigned char *mb_str, size_t _len) { + if (hreq->response_body_cb != NULL) { + + hreq->response_body_cb(mb_str, _len, hresp->headers); + } else { + + if (hresp->body == NULL) { + + hresp->body = strdup(mb_str); + } else { + + hresp->body = (char *) realloc(hresp->body, hresp->body_len + _len + 1); + memcpy(&hresp->body[hresp->body_len], mb_str, _len); + + hresp->body[hresp->body_len + _len] = '\0'; + } + } +} + + +#endif + + + + + diff --git a/src/encoding/decode.h b/src/encoding/decode.h new file mode 100644 index 0000000..c0a8196 --- /dev/null +++ b/src/encoding/decode.h @@ -0,0 +1,68 @@ + +#ifndef HTTP_CLIENT_C_ENCODING_DECODE_H +#define HTTP_CLIENT_C_ENCODING_DECODE_H + +#include "encoding/struct.h" +#include "encoding/chunked.h" +#include "http/struct.h" + +http_client_errors http_transfer_decode(http_transfer_encoding *te, int sock, char *buf, size_t buf_len, size_t offset, http_request *, http_response *); + +http_client_errors http_transfer_decode(http_transfer_encoding *te, int sock, char *buf, size_t buf_len, size_t offset, http_request *hreq, http_response *hresp) { + + if (te != NULL) { + + if(strcmp(te->value, "chunked") == 0) { + + return http_chunked_transfer_decode(sock, buf, buf_len, offset, hreq, hresp); + } + + return HTTP_CLIENT_ERROR_TRANSFER_ENCODING; + } + + size_t body_len = 0; + + if (hreq->response_body_cb != NULL) { + + fprintf(stderr, "%s", &buf[offset]); + hreq->response_body_cb(&buf[offset], buf_len - offset, hresp->headers); + } + else { + + body_len = buf_len - offset; + hresp->body = malloc(body_len + 1); + memcpy(hresp->body, &buf[offset], body_len); + } + + size_t received_len = 0; + + while ((received_len = recv(sock, (void *) buf, BUF_READ - 1, 0)) > 0) { + + if (hreq->response_body_cb != NULL) { + + hreq->response_body_cb(buf, received_len, hresp->headers); + } + else { + + hresp->body = realloc(hresp->body, body_len + received_len + 1); + memcpy(&hresp->body[body_len], buf, received_len); + body_len += received_len; + } + } + + // TODO: if the content type is text - get wide_char_len instead of bytes_len + if (hreq->response_body_cb == NULL) { + + hresp->body[body_len] = '\0'; + hresp->body_len = body_len; + } + + if (received_len < 0) { + + return HTTP_CLIENT_ERROR_DATA; + } + + return 0; +} + +#endif diff --git a/src/encoding/struct.h b/src/encoding/struct.h new file mode 100644 index 0000000..fa0a8f0 --- /dev/null +++ b/src/encoding/struct.h @@ -0,0 +1,130 @@ + + +#ifndef HTTP_CLIENT_C_ENCODING_STRUCT_H +#define HTTP_CLIENT_C_ENCODING_STRUCT_H + +//#include +//#include +//#include "stdio.h" +#include "string.h" +#include "error.h" + +typedef struct http_transfer_encoding { + + char *value; + struct http_transfer_encoding *next; + +} http_transfer_encoding; + +http_transfer_encoding *http_transfer_encoding_new(); +http_transfer_encoding *http_transfer_encoding_parse(const char *); +void http_transfer_encoding_set_value(http_transfer_encoding *, const char *); +void http_transfer_encoding_free(http_transfer_encoding *); + +//http_client_errors http_transfer_decode(http_transfer_encoding *te, int socket, const char *buf, size_t buf_len, http_response_body_cb_ptr *); + +// +http_transfer_encoding *http_transfer_encoding_new() { + + http_transfer_encoding *te = (http_transfer_encoding *) calloc(sizeof (http_transfer_encoding), 1); + + return te; +} + +http_transfer_encoding *http_transfer_encoding_parse(const char *transfer_encoding) { + + http_transfer_encoding *te, *tmp; + + te = tmp = NULL; + + char *encode = strdup(transfer_encoding); + + char *token = NULL; + + while ((token = strsep(&encode, " \t\r\n,")) != NULL) { + + if (strcmp(token, "") == 0) { + + continue; + } + + tmp = http_transfer_encoding_new(); + http_transfer_encoding_set_value(tmp, token); + + if (te == NULL) { + + te = tmp; + } + + else { + + tmp->next = te; + te = tmp; + } + } + + return te; +} + +http_transfer_encoding *http_transfer_encoding_clone(const http_transfer_encoding *transfer_encoding) { + + http_transfer_encoding *te = NULL, *tmp3, *tmp2, *tmp = (http_transfer_encoding *) transfer_encoding; + + while (tmp != NULL) { + + if (te == NULL) { + + tmp2 = te = http_transfer_encoding_new(); + http_transfer_encoding_set_value(te, tmp->value); + } + + else { + + tmp3 = http_transfer_encoding_new(); + http_transfer_encoding_set_value(tmp3, tmp->value); + + tmp2->next = tmp3; + tmp2 = tmp2->next; + } + + tmp = tmp->next; + } + + return te; +} + +void http_transfer_encoding_set_value(http_transfer_encoding *te, const char *value) { + + if (te->value != NULL) { + + free(te->value); + te->value = NULL; + } + + if(value != NULL) { + + te->value = strdup(value); + } +} + +void http_transfer_encoding_free(http_transfer_encoding *te) { + + if (te != NULL) { + + if (te->value != NULL) { + + free(te->value); + te->value = NULL; + } + + if (te->next != NULL) { + + http_transfer_encoding_free(te->next); + te->next = NULL; + } + + free(te); + } +} + +#endif diff --git a/src/error.h b/src/error.h new file mode 100644 index 0000000..5d16d81 --- /dev/null +++ b/src/error.h @@ -0,0 +1,56 @@ + + +#ifndef HTTP_CLIENT_C_ERROR_H +#define HTTP_CLIENT_C_ERROR_H + +typedef enum { + + HTTP_CLIENT_ERROR_OK = 0, + HTTP_CLIENT_ERROR_CONNECT = -1, + HTTP_CLIENT_ERROR_DNS = -2, + HTTP_CLIENT_ERROR_HOST = -3, + HTTP_CLIENT_ERROR_DATA = -4, + HTTP_CLIENT_ERROR_RECV = -5, + HTTP_CLIENT_ERROR_TRANSFER_ENCODING = -6, + HTTP_CLIENT_PROTO = -7 +} http_client_errors; + +char *http_client_error(http_client_errors); + +char *http_client_error(http_client_errors err) { + + switch (err) { + + case HTTP_CLIENT_ERROR_CONNECT: + + return "could not connect"; + + case HTTP_CLIENT_ERROR_DNS: + + return "could not resolve dns"; + + case HTTP_CLIENT_ERROR_HOST: + + return "invalid host"; + + case HTTP_CLIENT_ERROR_DATA: + + return "unsupported encoding (content-encoding or transfer-encoding)"; + + case HTTP_CLIENT_ERROR_RECV: + + return "error receiving data"; + + case HTTP_CLIENT_ERROR_TRANSFER_ENCODING: + + return "unsupported transfer encoding"; + + case HTTP_CLIENT_PROTO: + + return "unsupported protocol"; + } + + return ""; +} + +#endif \ No newline at end of file diff --git a/src/http-client-c.h b/src/http-client-c.h deleted file mode 100644 index 2ce5042..0000000 --- a/src/http-client-c.h +++ /dev/null @@ -1,613 +0,0 @@ -/* - http-client-c - Copyright (C) 2012-2013 Swen Kooij - - This file is part of http-client-c. - - http-client-c is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - http-client-c is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with http-client-c. If not, see . - - Warning: - This library does not tend to work that stable nor does it fully implent the - standards described by IETF. For more information on the precise implentation of the - Hyper Text Transfer Protocol: - - http://www.ietf.org/rfc/rfc2616.txt -*/ - -#pragma GCC diagnostic ignored "-Wwrite-strings" -#include -#include -#include -#include -#ifdef _WIN32 - #include - #include - #include - #pragma comment(lib, "Ws2_32.lib") -#elif _LINUX - #include -#elif __FreeBSD__ - #include - #include - #include - #include -#else - #error Platform not suppoted. -#endif - -#include -#include "stringx.h"; -#include "urlparser.h" - -/* - Prototype functions -*/ -struct http_response* http_req(char *http_headers, struct parsed_url *purl); -struct http_response* http_get(char *url, char *custom_headers); -struct http_response* http_head(char *url, char *custom_headers); -struct http_response* http_post(char *url, char *custom_headers, char *post_data); - - -/* - Represents an HTTP html response -*/ -struct http_response -{ - struct parsed_url *request_uri; - char *body; - char *status_code; - int status_code_int; - char *status_text; - char *request_headers; - char *response_headers; -}; - -/* - Handles redirect if needed for get requests -*/ -struct http_response* handle_redirect_get(struct http_response* hresp, char* custom_headers) -{ - if(hresp->status_code_int > 300 && hresp->status_code_int < 399) - { - char *token = strtok(hresp->response_headers, "\r\n"); - while(token != NULL) - { - if(str_contains(token, "Location:")) - { - /* Extract url */ - char *location = str_replace("Location: ", "", token); - return http_get(location, custom_headers); - } - token = strtok(NULL, "\r\n"); - } - } - else - { - /* We're not dealing with a redirect, just return the same structure */ - return hresp; - } -} - -/* - Handles redirect if needed for head requests -*/ -struct http_response* handle_redirect_head(struct http_response* hresp, char* custom_headers) -{ - if(hresp->status_code_int > 300 && hresp->status_code_int < 399) - { - char *token = strtok(hresp->response_headers, "\r\n"); - while(token != NULL) - { - if(str_contains(token, "Location:")) - { - /* Extract url */ - char *location = str_replace("Location: ", "", token); - return http_head(location, custom_headers); - } - token = strtok(NULL, "\r\n"); - } - } - else - { - /* We're not dealing with a redirect, just return the same structure */ - return hresp; - } -} - -/* - Handles redirect if needed for post requests -*/ -struct http_response* handle_redirect_post(struct http_response* hresp, char* custom_headers, char *post_data) -{ - if(hresp->status_code_int > 300 && hresp->status_code_int < 399) - { - char *token = strtok(hresp->response_headers, "\r\n"); - while(token != NULL) - { - if(str_contains(token, "Location:")) - { - /* Extract url */ - char *location = str_replace("Location: ", "", token); - return http_post(location, custom_headers, post_data); - } - token = strtok(NULL, "\r\n"); - } - } - else - { - /* We're not dealing with a redirect, just return the same structure */ - return hresp; - } -} - -/* - Makes a HTTP request and returns the response -*/ -struct http_response* http_req(char *http_headers, struct parsed_url *purl) -{ - /* Parse url */ - if(purl == NULL) - { - printf("Unable to parse url"); - return NULL; - } - - /* Declare variable */ - int sock; - int tmpres; - char buf[BUFSIZ+1]; - struct sockaddr_in *remote; - - /* Allocate memeory for htmlcontent */ - struct http_response *hresp = (struct http_response*)malloc(sizeof(struct http_response)); - if(hresp == NULL) - { - printf("Unable to allocate memory for htmlcontent."); - return NULL; - } - hresp->body = NULL; - hresp->request_headers = NULL; - hresp->response_headers = NULL; - hresp->status_code = NULL; - hresp->status_text = NULL; - - /* Create TCP socket */ - if((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) - { - printf("Can't create TCP socket"); - return NULL; - } - - /* Set remote->sin_addr.s_addr */ - remote = (struct sockaddr_in *)malloc(sizeof(struct sockaddr_in *)); - remote->sin_family = AF_INET; - tmpres = inet_pton(AF_INET, purl->ip, (void *)(&(remote->sin_addr.s_addr))); - if( tmpres < 0) - { - printf("Can't set remote->sin_addr.s_addr"); - return NULL; - } - else if(tmpres == 0) - { - printf("Not a valid IP"); - return NULL; - } - remote->sin_port = htons(atoi(purl->port)); - - /* Connect */ - if(connect(sock, (struct sockaddr *)remote, sizeof(struct sockaddr)) < 0) - { - printf("Could not connect"); - return NULL; - } - - /* Send headers to server */ - int sent = 0; - while(sent < strlen(http_headers)) - { - tmpres = send(sock, http_headers+sent, strlen(http_headers)-sent, 0); - if(tmpres == -1) - { - printf("Can't send headers"); - return NULL; - } - sent += tmpres; - } - - /* Recieve into response*/ - char *response = (char*)malloc(0); - char BUF[BUFSIZ]; - size_t recived_len = 0; - while((recived_len = recv(sock, BUF, BUFSIZ-1, 0)) > 0) - { - BUF[recived_len] = '\0'; - response = (char*)realloc(response, strlen(response) + strlen(BUF) + 1); - sprintf(response, "%s%s", response, BUF); - } - if (recived_len < 0) - { - free(http_headers); - #ifdef _WIN32 - closesocket(sock); - #else - close(sock); - #endif - printf("Unabel to recieve"); - return NULL; - } - - /* Reallocate response */ - response = (char*)realloc(response, strlen(response) + 1); - - /* Close socket */ - #ifdef _WIN32 - closesocket(sock); - #else - close(sock); - #endif - - /* Parse status code and text */ - char *status_line = get_until(response, "\r\n"); - status_line = str_replace("HTTP/1.1 ", "", status_line); - char *status_code = str_ndup(status_line, 4); - status_code = str_replace(" ", "", status_code); - char *status_text = str_replace(status_code, "", status_line); - status_text = str_replace(" ", "", status_text); - hresp->status_code = status_code; - hresp->status_code_int = atoi(status_code); - hresp->status_text = status_text; - - /* Parse response headers */ - char *headers = get_until(response, "\r\n\r\n"); - hresp->response_headers = headers; - - /* Assign request headers */ - hresp->request_headers = http_headers; - - /* Assign request url */ - hresp->request_uri = purl; - - /* Parse body */ - char *body = strstr(response, "\r\n\r\n"); - body = str_replace("\r\n\r\n", "", body); - hresp->body = body; - - /* Return response */ - return hresp; -} - -/* - Makes a HTTP GET request to the given url -*/ -struct http_response* http_get(char *url, char *custom_headers) -{ - /* Parse url */ - struct parsed_url *purl = parse_url(url); - if(purl == NULL) - { - printf("Unable to parse url"); - return NULL; - } - - /* Declare variable */ - char *http_headers = (char*)malloc(1024); - - /* Build query/headers */ - if(purl->path != NULL) - { - if(purl->query != NULL) - { - sprintf(http_headers, "GET /%s?%s HTTP/1.1\r\nHost:%s\r\nConnection:close\r\n", purl->path, purl->query, purl->host); - } - else - { - sprintf(http_headers, "GET /%s HTTP/1.1\r\nHost:%s\r\nConnection:close\r\n", purl->path, purl->host); - } - } - else - { - if(purl->query != NULL) - { - sprintf(http_headers, "GET /?%s HTTP/1.1\r\nHost:%s\r\nConnection:close\r\n", purl->query, purl->host); - } - else - { - sprintf(http_headers, "GET / HTTP/1.1\r\nHost:%s\r\nConnection:close\r\n", purl->host); - } - } - - /* Handle authorisation if needed */ - if(purl->username != NULL) - { - /* Format username:password pair */ - char *upwd = (char*)malloc(1024); - sprintf(upwd, "%s:%s", purl->username, purl->password); - upwd = (char*)realloc(upwd, strlen(upwd) + 1); - - /* Base64 encode */ - char *base64 = base64_encode(upwd); - - /* Form header */ - char *auth_header = (char*)malloc(1024); - sprintf(auth_header, "Authorization: Basic %s\r\n", base64); - auth_header = (char*)realloc(auth_header, strlen(auth_header) + 1); - - /* Add to header */ - http_headers = (char*)realloc(http_headers, strlen(http_headers) + strlen(auth_header) + 2); - sprintf(http_headers, "%s%s", http_headers, auth_header); - } - - /* Add custom headers, and close */ - if(custom_headers != NULL) - { - sprintf(http_headers, "%s%s\r\n", http_headers, custom_headers); - } - else - { - sprintf(http_headers, "%s\r\n", http_headers); - } - http_headers = (char*)realloc(http_headers, strlen(http_headers) + 1); - - /* Make request and return response */ - struct http_response *hresp = http_req(http_headers, purl); - - /* Handle redirect */ - return handle_redirect_get(hresp, custom_headers); -} - -/* - Makes a HTTP POST request to the given url -*/ -struct http_response* http_post(char *url, char *custom_headers, char *post_data) -{ - /* Parse url */ - struct parsed_url *purl = parse_url(url); - if(purl == NULL) - { - printf("Unable to parse url"); - return NULL; - } - - /* Declare variable */ - char *http_headers = (char*)malloc(1024); - - /* Build query/headers */ - if(purl->path != NULL) - { - if(purl->query != NULL) - { - sprintf(http_headers, "POST /%s?%s HTTP/1.1\r\nHost:%s\r\nConnection:close\r\nContent-Length:%zu\r\nContent-Type:application/x-www-form-urlencoded\r\n", purl->path, purl->query, purl->host, strlen(post_data)); - } - else - { - sprintf(http_headers, "POST /%s HTTP/1.1\r\nHost:%s\r\nConnection:close\r\nContent-Length:%zu\r\nContent-Type:application/x-www-form-urlencoded\r\n", purl->path, purl->host, strlen(post_data)); - } - } - else - { - if(purl->query != NULL) - { - sprintf(http_headers, "POST /?%s HTTP/1.1\r\nHost:%s\r\nConnection:close\r\nContent-Length:%zu\r\nContent-Type:application/x-www-form-urlencoded\r\n", purl->query, purl->host, strlen(post_data)); - } - else - { - sprintf(http_headers, "POST / HTTP/1.1\r\nHost:%s\r\nConnection:close\r\nContent-Length:%zu\r\nContent-Type:application/x-www-form-urlencoded\r\n", purl->host, strlen(post_data)); - } - } - - /* Handle authorisation if needed */ - if(purl->username != NULL) - { - /* Format username:password pair */ - char *upwd = (char*)malloc(1024); - sprintf(upwd, "%s:%s", purl->username, purl->password); - upwd = (char*)realloc(upwd, strlen(upwd) + 1); - - /* Base64 encode */ - char *base64 = base64_encode(upwd); - - /* Form header */ - char *auth_header = (char*)malloc(1024); - sprintf(auth_header, "Authorization: Basic %s\r\n", base64); - auth_header = (char*)realloc(auth_header, strlen(auth_header) + 1); - - /* Add to header */ - http_headers = (char*)realloc(http_headers, strlen(http_headers) + strlen(auth_header) + 2); - sprintf(http_headers, "%s%s", http_headers, auth_header); - } - - if(custom_headers != NULL) - { - sprintf(http_headers, "%s%s\r\n", http_headers, custom_headers); - sprintf(http_headers, "%s\r\n%s", http_headers, post_data); - } - else - { - sprintf(http_headers, "%s\r\n%s", http_headers, post_data); - } - http_headers = (char*)realloc(http_headers, strlen(http_headers) + 1); - - /* Make request and return response */ - struct http_response *hresp = http_req(http_headers, purl); - - /* Handle redirect */ - return handle_redirect_post(hresp, custom_headers, post_data); -} - -/* - Makes a HTTP HEAD request to the given url -*/ -struct http_response* http_head(char *url, char *custom_headers) -{ - /* Parse url */ - struct parsed_url *purl = parse_url(url); - if(purl == NULL) - { - printf("Unable to parse url"); - return NULL; - } - - /* Declare variable */ - char *http_headers = (char*)malloc(1024); - - /* Build query/headers */ - if(purl->path != NULL) - { - if(purl->query != NULL) - { - sprintf(http_headers, "HEAD /%s?%s HTTP/1.1\r\nHost:%s\r\nConnection:close\r\n", purl->path, purl->query, purl->host); - } - else - { - sprintf(http_headers, "HEAD /%s HTTP/1.1\r\nHost:%s\r\nConnection:close\r\n", purl->path, purl->host); - } - } - else - { - if(purl->query != NULL) - { - sprintf(http_headers, "HEAD/?%s HTTP/1.1\r\nHost:%s\r\nConnection:close\r\n", purl->query, purl->host); - } - else - { - sprintf(http_headers, "HEAD / HTTP/1.1\r\nHost:%s\r\nConnection:close\r\n", purl->host); - } - } - - /* Handle authorisation if needed */ - if(purl->username != NULL) - { - /* Format username:password pair */ - char *upwd = (char*)malloc(1024); - sprintf(upwd, "%s:%s", purl->username, purl->password); - upwd = (char*)realloc(upwd, strlen(upwd) + 1); - - /* Base64 encode */ - char *base64 = base64_encode(upwd); - - /* Form header */ - char *auth_header = (char*)malloc(1024); - sprintf(auth_header, "Authorization: Basic %s\r\n", base64); - auth_header = (char*)realloc(auth_header, strlen(auth_header) + 1); - - /* Add to header */ - http_headers = (char*)realloc(http_headers, strlen(http_headers) + strlen(auth_header) + 2); - sprintf(http_headers, "%s%s", http_headers, auth_header); - } - - if(custom_headers != NULL) - { - sprintf(http_headers, "%s%s\r\n", http_headers, custom_headers); - } - else - { - sprintf(http_headers, "%s\r\n", http_headers); - } - http_headers = (char*)realloc(http_headers, strlen(http_headers) + 1); - - /* Make request and return response */ - struct http_response *hresp = http_req(http_headers, purl); - - /* Handle redirect */ - return handle_redirect_head(hresp, custom_headers); -} - -/* - Do HTTP OPTIONs requests -*/ -struct http_response* http_options(char *url) -{ - /* Parse url */ - struct parsed_url *purl = parse_url(url); - if(purl == NULL) - { - printf("Unable to parse url"); - return NULL; - } - - /* Declare variable */ - char *http_headers = (char*)malloc(1024); - - /* Build query/headers */ - if(purl->path != NULL) - { - if(purl->query != NULL) - { - sprintf(http_headers, "OPTIONS /%s?%s HTTP/1.1\r\nHost:%s\r\nConnection:close\r\n", purl->path, purl->query, purl->host); - } - else - { - sprintf(http_headers, "OPTIONS /%s HTTP/1.1\r\nHost:%s\r\nConnection:close\r\n", purl->path, purl->host); - } - } - else - { - if(purl->query != NULL) - { - sprintf(http_headers, "OPTIONS/?%s HTTP/1.1\r\nHost:%s\r\nConnection:close\r\n", purl->query, purl->host); - } - else - { - sprintf(http_headers, "OPTIONS / HTTP/1.1\r\nHost:%s\r\nConnection:close\r\n", purl->host); - } - } - - /* Handle authorisation if needed */ - if(purl->username != NULL) - { - /* Format username:password pair */ - char *upwd = (char*)malloc(1024); - sprintf(upwd, "%s:%s", purl->username, purl->password); - upwd = (char*)realloc(upwd, strlen(upwd) + 1); - - /* Base64 encode */ - char *base64 = base64_encode(upwd); - - /* Form header */ - char *auth_header = (char*)malloc(1024); - sprintf(auth_header, "Authorization: Basic %s\r\n", base64); - auth_header = (char*)realloc(auth_header, strlen(auth_header) + 1); - - /* Add to header */ - http_headers = (char*)realloc(http_headers, strlen(http_headers) + strlen(auth_header) + 2); - sprintf(http_headers, "%s%s", http_headers, auth_header); - } - - /* Build headers */ - sprintf(http_headers, "%s\r\n", http_headers); - http_headers = (char*)realloc(http_headers, strlen(http_headers) + 1); - - /* Make request and return response */ - struct http_response *hresp = http_req(http_headers, purl); - - /* Handle redirect */ - return hresp; -} - -/* - Free memory of http_response -*/ -void http_response_free(struct http_response *hresp) -{ - if(hresp != NULL) - { - if(hresp->request_uri != NULL) parsed_url_free(hresp->request_uri); - if(hresp->body != NULL) free(hresp->body); - if(hresp->status_code != NULL) free(hresp->status_code); - if(hresp->status_text != NULL) free(hresp->status_text); - if(hresp->request_headers != NULL) free(hresp->request_headers); - if(hresp->response_headers != NULL) free(hresp->response_headers); - free(hresp); - } -} diff --git a/src/http/client.h b/src/http/client.h new file mode 100644 index 0000000..12343c3 --- /dev/null +++ b/src/http/client.h @@ -0,0 +1,634 @@ +/* + http-client-c + Copyright (C) 2012-2013 Swen Kooij + + This file is part of http-client-c. + + http-client-c is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + http-client-c is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with http-client-c. If not, see . + + Warning: + This library does not tend to work that stable nor does it fully implement the + standards described by IETF. For more information on the precise implementation of the + Hyper Text Transfer Protocol: + + http://www.ietf.org/rfc/rfc2616.txt +*/ + +#ifndef HTTP_CLIENT_C_HTTP_CLIENT_H +#define HTTP_CLIENT_C_HTTP_CLIENT_H + +//#pragma GCC diagnostic ignored "-Wwrite-strings" +#include +#include +#include +#include +#include +#include +#include "base64.h" +#include "url/parser.h" +#include "header.h" +#include "http/struct.h" +#include "encoding/decode.h" +#include "error.h" +#include "stringx.h" +#include "struct.h" +#include "iconv.h" + + +#ifdef _WIN32 +#include +#include +#include +#pragma comment(lib, "Ws2_32.lib") +#elif __linux__ + +#include + +#elif __FreeBSD__ +#include +#include +#include +#include +#else +#error Platform not suppoted. +#endif + +void http_request_header_set(http_request *hreq, const char *name, const char *value); + +void http_request_header_add(http_request *hreq, const char *name, const char *value); + +void http_request_header_unset(http_request *hreq, const char *name); + +http_response *http_request_exec(http_request *hreq); + +char *http_request_serialize(http_header *headers, const char *method, http_url *purl, char *body, + size_t body_len, size_t *len); + +int http_request_send(http_response *hresp, http_request *hreq, char *request, size_t request_len, http_url *purl); + +void http_request_option(http_request *hreq, http_option option, const void *val, size_t len); + + +void http_request_header_set(http_request *hreq, const char *name, const char *value) { + + http_header *header = hreq->headers; + + if (header == NULL) { + + hreq->headers = http_header_new(); + + http_header_set_name(hreq->headers, (char *) name); + http_header_set_value(hreq->headers, (char *) value); + return; + } + + if (strcasecmp(header->name, name) == 0) { + + http_header_set_value(header, (char *) value); + return; + } + + // replace existing header + while (header->next != NULL) { + + header = header->next; + + if (strcasecmp(header->name, name) == 0) { + + http_header_set_value(header, (char *) value); + return; + } + } + + header->next = http_header_new(); + + http_header_set_name(header->next, (char *) name); + http_header_set_value(header->next, (char *) value); +} + +void http_request_header_add(http_request *hreq, const char *name, const char *value) { + + http_header *header = hreq->headers; + + if (header == NULL) { + + hreq->headers = http_header_new(); + + http_header_set_name(hreq->headers, (char *) name); + http_header_set_value(hreq->headers, (char *) value); + return; + } + + // replace existing header + while (header->next != NULL) { + + header = header->next; + } + + header->next = http_header_new(); + + http_header_set_name(header->next, (char *) name); + http_header_set_value(header->next, (char *) value); +} + +void http_request_header_unset(http_request *hreq, const char *name) { + + http_header *head = hreq->headers; + http_header *tmp; + + while (head != NULL && strcasecmp(head->name, name) == 0) { + + tmp = head; + head = hreq->headers = head->next; + + http_header_free(tmp); + } + + while (head != NULL && head->next != NULL) { + + if (strcasecmp(head->next->name, name) == 0) { + + tmp = head->next; + head->next = head->next->next; + http_header_free(tmp); + continue; + } + + head = head->next; + } +} + +/* + Makes a HTTP request and returns the response +*/ +http_response *http_request_exec(http_request *hreq) { + + const char *request_uri = hreq->request_uri; + + if (request_uri == NULL) { + + return NULL; + } + + if (hreq->max_redirect == 0) { + + hreq->max_redirect = HTTP_CLIENT_C_HTTP_MAX_REDIRECT; + } + + if (hreq->method == NULL) { + + hreq->method = strdup("GET"); + } else { + + str_to_upper(hreq->method, strlen(hreq->method)); + } + + if (strcasecmp(hreq->method, "GET") == 0 && hreq->body_len > 0) { + + fprintf(stderr, "converting HTTP method from GET tp POST"); + + free(hreq->method); + hreq->method = strdup("POST"); + } + + http_url *purl = http_url_parse(hreq->request_uri); + + if (purl == NULL) { + + printf("Unable to parse url"); + return NULL; + } + + if (hreq->body_len > 0) { + + char body_len[20]; + + memset(body_len, 0, 20); + sprintf(body_len, "%lu", hreq->body_len); + + http_request_header_set(hreq, "Content-Length", body_len); + + if (!http_header_exists(hreq->headers, "Content-Type")) { + + http_request_header_set(hreq, "Content-Type", "application/x-www-form-urlencoded"); + } + } + + if (!http_header_exists(hreq->headers, "Accept-Encoding")) { + + // disable encoding please please + http_request_header_set(hreq, "Accept-Encoding", "identity"); + } + + if (!http_header_exists(hreq->headers, "User-Agent")) { + + http_request_header_set(hreq, "User-Agent", "http-client-c"); + } + + http_request_header_set(hreq, "Connection", "close"); + + http_response *hresp = http_response_new(); + + if (hresp == NULL) { + + printf("Unable to allocate memory for HTTP response."); + http_url_free(purl); + return NULL; + } + + size_t request_len = 0; + http_header *headers = http_header_clone(hreq->headers); + char *request = http_request_serialize(headers, hreq->method, purl, hreq->body, hreq->body_len, &request_len); + + http_header_free(headers); + + do { + + if (strcasecmp(purl->scheme, "http") != 0) { + + if (request != NULL) { + + free(request); + } + + fprintf(stderr, "error: %s: '%s'\n", http_client_error(HTTP_CLIENT_PROTO), purl->scheme); + http_url_free(purl); + return NULL; + } + + if (request_len < 0) { + + free(request); + http_url_free(purl); + + fprintf(stderr, "error: %s\n", http_client_error(request_len)); + return NULL; + } + + int result = http_request_send(hresp, hreq, request, request_len, purl); + + free(request); + request = NULL; + + if (result < 0) { + + if (result == HTTP_CLIENT_ERROR_TRANSFER_ENCODING) { + + http_header *te = http_header_get(hreq->headers, "Transfer-Encoding"); + + if (te != NULL) { + + fprintf(stderr, "error: %s: '%s'\n", http_client_error(result), te->value); + http_header_free(te); + } + } else { + + fprintf(stderr, "error: %s\n", http_client_error(result)); + } + + http_url_free(purl); + http_response_free(hresp); + + return NULL; + } + + if (strcasecmp(hreq->method, "OPTIONS") == 0 || + hresp->status_code == 304 || + hresp->status_code < 300 || hresp->status_code > 399 || + hreq->max_redirect == hresp->redirect_count) { + + break; + } + + hresp->redirect_count++; + http_header *location = http_header_get(hresp->headers, "Location"); + + hresp->redirect_uri = strdup(location->value); + purl = http_url_parse(location->value); + http_header_free(hresp->headers); + hresp->headers = NULL; + + // change HTTP method? + const char *method = hresp->status_code == 307 ? hreq->method : (strcasecmp(hreq->method, "GET") == 0 || + strcasecmp(hreq->method, "HEAD") == 0 + ? hreq->method : "GET"); + + // copy request headers + headers = http_header_clone(hreq->headers); + + // cookies? + if (strcasecmp(hreq->method, method) != 0) { + + fprintf(stderr, "switching HTTP method from %s to %s\n", hreq->method, method); + + if (hreq->body_len > 0 && hresp->redirect_count == 1) { + + fprintf(stderr, "ignoring %s payload\n", hreq->method); + } + + request = http_request_serialize(headers, method, purl, NULL, 0, &request_len); + } else { + + request = http_request_serialize(headers, method, purl, hreq->body, hreq->body_len, &request_len); + } + + free(headers); + headers = NULL; + + + } while (hreq->max_redirect > hresp->redirect_count); + + http_url_free(purl); + + /* Return response */ + return hresp; +} + +char *http_request_serialize(http_header *headers, const char *method, http_url *purl, char *body, + size_t body_len, size_t *len) { + + if (purl->username != NULL) { + + /* Format username:password pair */ + size_t pwd_len = snprintf(NULL, 0, "%s:%s", purl->username, purl->password); + char upwd[pwd_len]; //(char *) malloc(1024); + memset(upwd, 0, pwd_len); + sprintf(upwd, "%s:%s", purl->username, purl->password); + + /* Base64 encode */ + char *base64 = base64_encode((const unsigned char *) upwd, strlen(upwd)); + pwd_len = strlen(base64); + char auth_header[pwd_len + 7]; // = realloc(base64, pwd_len + 7); + + sprintf(auth_header, "Basic %s", base64); + + http_header *auth = http_header_get(headers, "Authorization"); + + if (auth == NULL) { + + auth = http_header_new(); + http_header_set_name(auth, "Authorization"); + + http_header *header = headers; + + while (header->next != NULL) { + + header = header->next; + } + + header->next = auth; + } + + http_header_set_value(auth, auth_header); + free(base64); + } + + char *buff = calloc(1024 * sizeof(char), 1); + size_t pos = strlen(buff); + size_t buf_len; + + buf_len = snprintf(NULL, 0, "%s /%s%s%s HTTP/1.1\r\nHost:%s\r\n", method, purl->path == NULL ? "" : purl->path, + purl->query == NULL ? "" : "?", purl->query == NULL ? "" : purl->query, purl->host); + + sprintf(&buff[pos], "%s /%s%s%s HTTP/1.1\r\nHost:%s\r\n", method, purl->path == NULL ? "" : purl->path, + purl->query == NULL ? "" : "?", + purl->query == NULL ? "" : purl->query, purl->host); + pos += buf_len; + + http_header *head = headers; + + while (head != NULL) { + + buf_len = snprintf(NULL, 0, "%s: %s\r\n", head->name, head->value); + buff = (char *) realloc(buff, pos + buf_len + 1); + + sprintf(&buff[pos], "%s: %s\r\n", head->name, head->value); + + pos += buf_len; + head = head->next; + } + + buf_len = 2; + buff = realloc(buff, pos + buf_len + 1); + sprintf(&buff[pos], "\r\n"); + pos += buf_len; + + if (body_len > 0) { + + buf_len = body_len; + buff = realloc(buff, pos + buf_len + 1); + memcpy(&buff[pos], body, buf_len); + pos += buf_len; + } + + *len = pos; + buff[pos] = '\0'; + + return buff; +} + +http_client_errors http_request_send(http_response *hresp, http_request *hreq, char *request, size_t request_len, http_url *purl) { + + /* Declare variable */ + int sock; + http_client_errors error_reason = HTTP_CLIENT_ERROR_OK; + size_t tmpres; + struct sockaddr_in *remote = NULL; + http_transfer_encoding *te = NULL; + + /* Create TCP socket */ + if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0 + || setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, hreq->request_timeout, sizeof *hreq->request_timeout) < 0 + || setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, hreq->request_timeout, sizeof *hreq->request_timeout) < 0 + ) { + + error_reason = HTTP_CLIENT_ERROR_CONNECT; + goto exit; + } + + /* Set remote->sin_addr.s_addr */ + remote = (struct sockaddr_in *) malloc(sizeof(struct sockaddr_in *)); + remote->sin_family = AF_INET; + tmpres = inet_pton(AF_INET, purl->ip, (void *) (&(remote->sin_addr.s_addr))); + + if (tmpres < 0) { + + error_reason = HTTP_CLIENT_ERROR_DNS; + goto exit; + } else if (tmpres == 0) { + + error_reason = HTTP_CLIENT_ERROR_HOST; + goto exit; + } + + remote->sin_port = htons(atoi((const char *) purl->port)); + + /* Connect */ + if (connect(sock, (struct sockaddr *) remote, sizeof(struct sockaddr)) < 0) { + + error_reason = HTTP_CLIENT_ERROR_CONNECT; + goto exit; + } + + if (hreq->request_header_cb != NULL) { + + hreq->request_header_cb(hreq->headers); + } + + /* Send headers to server */ + size_t sent = 0; + while (sent < request_len) { + + tmpres = send(sock, &request[sent], request_len - sent, 0); + if (tmpres == -1) { + + error_reason = HTTP_CLIENT_ERROR_CONNECT; + goto exit; + } + sent += tmpres; + } + + /* Receive into response*/ + char BUF[BUF_READ]; + + ssize_t received_len; + + if ((received_len = recv(sock, (void *) BUF, BUF_READ - 1, 0)) > 0) { + + BUF[received_len] = '\0'; + + char *body_end = strstr(BUF, "\r\n\r\n"); + + if (body_end != NULL) { + + char *first_line = strstr(BUF, "\r\n"); + + if (first_line != NULL) { + + size_t status_len = first_line - BUF; + char status_line[status_len]; + + memcpy(status_line, BUF, status_len - 1); + status_line[status_len] = '\0'; + + char *status_text = strstr(status_line, " "); + + hresp->status_code = atoi(status_text); + hresp->status_text = strdup(&status_text[1]); + } + + size_t headers_len = 0; + hresp->headers = http_header_parse(BUF, &headers_len); + + if (hreq->response_header_cb != NULL) { + + hreq->response_header_cb(hresp->headers); + } + + if (hresp->status_code > 299 && hresp->status_code < 400) { + + fprintf(stderr, "R%d ignoring response body\n", hresp->status_code); + goto exit; + } + + http_header *teh = http_header_get(hresp->headers, "Transfer-Encoding"); + + if (teh != NULL) { + + te = http_transfer_encoding_parse(teh->value); + } + + error_reason = http_transfer_decode(te, sock, (char *) BUF, received_len, (body_end - BUF) + 4, hreq, + hresp); + goto exit; + } + } + + if (received_len < 0) { + + error_reason = HTTP_CLIENT_ERROR_RECV; + goto exit; + } + + exit: + + if (remote != NULL) { + + free(remote); + } + + if (te != NULL) { + + http_transfer_encoding_free(te); + } + +#ifdef _WIN32 + if (sock != -1) { + + closesocket(sock); + } +#else + if (sock != -1) { + + close(sock); + } +#endif + return error_reason; +} + +void http_request_option(http_request *hreq, http_option option, const void *val, size_t len) { + + http_header *header; + + switch (option) { + + case HTTP_OPTION_URL: + + hreq->request_uri = strdup((char *) val); + break; + + case HTTP_OPTION_HEADER: + + header = (http_header *) val; + http_request_header_set(hreq, header->name, header->value); + break; + + case HTTP_OPTION_BODY: + hreq->body = (char *) val; + hreq->body_len = len == 0 ? strlen(hreq->body) : len; + break; + + case HTTP_OPTION_REQUEST_TIMEOUT: + hreq->request_timeout->tv_sec = atol(val); + break; + + case HTTP_OPTION_REQUEST_HEADER_CALLBACK: + hreq->request_header_cb = (http_header_cb_ptr *) val; + break; + + case HTTP_OPTION_RESPONSE_HEADER_CALLBACK: + hreq->response_header_cb = (http_header_cb_ptr *) val; + break; + + case HTTP_OPTION_RESPONSE_BODY_CALLBACK: + hreq->response_body_cb = (http_response_body_cb_ptr *) val; + break; + + case HTTP_OPTION_METHOD: + hreq->method = strdup(val); + break; + } +} + +#endif \ No newline at end of file diff --git a/src/http/header.h b/src/http/header.h new file mode 100644 index 0000000..3fff192 --- /dev/null +++ b/src/http/header.h @@ -0,0 +1,525 @@ +// +// Created by tbela on 2022-04-08. +// + +#ifndef HTTP_CLIENT_C_HEADER_H +#define HTTP_CLIENT_C_HEADER_H + +#include +#include "stringx.h" + +#define HTTP_HEADER_ATTR_TYPE_STRING 0 +#define HTTP_HEADER_ATTR_TYPE_BOOL 1 + +typedef struct http_header_attribute { + + uint8_t type; + char *name; + char *value; + + struct http_header_attribute *next; +} http_header_attribute; + +typedef struct http_header { + + char *name; + char *value; + http_header_attribute *attribute; + struct http_header *next; +} http_header; + +http_header *http_header_new(); +http_header_attribute *http_header_attribute_new(); + +http_header *http_header_clone(http_header *); +http_header_attribute *http_header_attribute_clone(http_header_attribute *); +http_header_attribute *http_header_attribute_deep_clone(http_header_attribute *); + +int http_header_exists(http_header *header, const char *name); + +http_header *http_header_get(http_header *header, const char *name); +http_header_attribute *http_header_attribute_get(http_header_attribute *, const char *); + +void http_header_set_name(http_header *header, char *name); + +void http_header_set_value(http_header *header, char *value); + +http_header *http_header_parse(const char *, size_t *); +http_header_attribute *http_header_attribute_parse(const char *); + +char *http_header_print(http_header *); +char *http_header_attribute_print(http_header_attribute *); + +void http_header_free(http_header *header); +void http_header_attribute_free(http_header_attribute *attr); + +// def +http_header *http_header_new() { + + http_header *header = (http_header *) calloc(sizeof(http_header), 1); + return header; +} + +http_header_attribute *http_header_attribute_new() { + + http_header_attribute *attr = (http_header_attribute *) calloc(sizeof(http_header_attribute), 1); + return attr; +} + +http_header *http_header_clone(http_header *headers) { + + if (headers == NULL) { + + return NULL; + } + + http_header *header = http_header_new(); + http_header *next = header; + http_header *current = headers; + + while (current != NULL) { + + http_header_set_name(next, current->name); + http_header_set_value(next, current->value); + + current = current->next; + + if (current != NULL) { + + next->next = http_header_new(); + next = next->next; + } + } + + return header; +} + +http_header_attribute *http_header_attribute_clone(http_header_attribute *attr) { + + http_header_attribute *clone = http_header_attribute_new(); + + clone->type = attr->type; + clone->name = strdup(attr->name); + + if (attr->value != NULL) { + + clone->value = strdup(attr->value); + } + + return clone; +} + +http_header_attribute *http_header_attribute_deep_clone(http_header_attribute *attr) { + + http_header_attribute *tmp, *curr = attr; + http_header_attribute *clone = NULL; + + while (curr != NULL) { + + if (clone == NULL) { + + clone = tmp = http_header_attribute_new(); + } else { + + tmp->next = http_header_attribute_new(); + tmp = tmp->next; + } + + tmp->type = curr->type; + tmp->name = strdup(curr->name); + + if (curr->value != NULL) { + + tmp->value = strdup(curr->value); + } + + curr = curr->next; + } + + return clone; +} + +int http_header_exists(http_header *header, const char *name) { + + http_header *head = header; + + while (head != NULL) { + + if (strcasecmp(head->name, name) == 0) { + + return 1; + } + + head = head->next; + } + + return 0; +} + +http_header *http_header_get(http_header *header, const char *name) { + + http_header *head = header; + http_header *result = NULL; + http_header *tmp; + + while (head != NULL) { + + if (strcasecmp(head->name, name) == 0) { + + if (result == NULL) { + + tmp = result = http_header_new(); + } else { + + tmp->next = http_header_new(); + tmp = tmp->next; + } + + http_header_set_name(tmp, head->name); + http_header_set_value(tmp, head->value); + + if (head->attribute != NULL) { + + tmp->attribute = http_header_attribute_deep_clone(head->attribute); + } + } + + head = head->next; + } + + return result; +} + +http_header_attribute *http_header_attribute_get(http_header_attribute *attribute, const char *name) { + + http_header_attribute *attr = attribute; + + while (attr != NULL) { + + if (strcasecmp(attr->name, name) == 0) { + + return http_header_attribute_clone(attr); + } + + attr = attr->next; + } + + return NULL; +} + +void http_header_set_name(http_header *header, char *name) { + + if (header->name != NULL) { + + free(header->name); + header->name = NULL; + } + + if(name != NULL) { + + header->name = strdup(name); + } +} + +void http_header_set_value(http_header *header, char *value) { + + if (header->value != NULL) { + + free(header->value); + header->value = NULL; + } + + if (value == NULL) { + + return; + } + + char* context = NULL; + char *search = strdup(value); + char* token = strtok_r(search, ";", &context); + + if (header->attribute != NULL) { + + http_header_attribute_free(header->attribute); + header->attribute = NULL; + } + + if (strstr(token, "=") == NULL) { + + header->value = strdup(token); + token = strtok_r(NULL, ";", &context); + } + + if (token != NULL) { + + header->attribute = http_header_attribute_parse(&value[token - search]); + } + + free(search); +} + +http_header *http_header_parse(const char *http_headers, size_t *len) { + + http_header *_hd = NULL; + http_header *_next = NULL; + char *str = strstr(http_headers, "\r\n\r\n"); + size_t header_len = 0; + + if (str == NULL) { + + *len = header_len = strlen(http_headers); + } + else { + + *len = str - http_headers + 4; + header_len = *len - 4; + } + + char header[*len - 3]; + memcpy(header, http_headers, header_len); + + header[header_len] = '\0'; + + const char *delim = "\r\n"; + char *token = strtok(header, delim); + + while (token != NULL) { + + char *split = strstr(token, ":"); + + if (split != NULL) { + + if (_hd == NULL) { + + _hd = _next = http_header_new(); + } else { + + _next->next = http_header_new(); + _next = _next->next; + } + + size_t _len = strlen(token) - strlen(split); + char name[_len + 1]; + +// memset(name, 0, _len); + memcpy(name, token, _len); + name[_len] = '\0'; + + http_header_set_name(_next, (char *) name); + + while (token[++_len] == ' '); + + size_t val_len = strlen(token) - _len; + + char value[val_len + 1]; + +// memset(value, 0, val_len - 1); + memcpy(value, &token[_len], val_len); + + value[val_len] = '\0'; + http_header_set_value(_next, (char *) value); + } + + token = strtok(NULL, delim); + } + + return _hd; +} + +http_header_attribute *http_header_attribute_parse(const char *http_headers) { + + http_header_attribute *attr = NULL; + http_header_attribute *root = NULL; + + char* headers = strdup(http_headers); + char* context = NULL; + char* token = strtok_r(headers, ";", &context); + + if (token != NULL) { + + do { + + token = ltrim(token); + + if (attr == NULL) { + + attr = root = http_header_attribute_new(); + } + + else { + + attr->next = http_header_attribute_new(); + attr = attr->next; + } + + char *attr_val = strstr(token, "=") ; + + if (attr_val == NULL) { + + attr->name = strdup(token); + attr->type = HTTP_HEADER_ATTR_TYPE_BOOL; + + free(token); + continue; + } + + attr->type = HTTP_HEADER_ATTR_TYPE_STRING; + + size_t attr_len = attr_val - token; + attr->name = malloc(attr_len + 1); + + memcpy(attr->name, token, attr_len); + attr->name[attr_len] = '\0'; + attr->value = ltrim(&attr_val[1]); + + free(token); + } + + while ((token = strtok_r(NULL, ";", &context)) != NULL); + } + + free(headers); + return root; +} + +char *http_header_print(http_header *headers) { + + char *_h = (char *) malloc(1); + size_t size, curr = 0; + http_header *header = headers; + + _h[0] = '\0'; + + while (header != NULL) { + + char *attr = header->attribute == NULL ? "" : http_header_attribute_print(header->attribute); + char *sep = header->value == NULL || header->attribute == NULL ? "" : "; "; + + size = snprintf(NULL, 0, "%s: %s%s%s\r\n", header->name, header->value == NULL ? "" : header->value, sep, attr); + + _h = (char *) realloc(_h, curr + size + 1); + sprintf(&_h[curr], "%s: %s%s%s\r\n", header->name, header->value == NULL ? "" : header->value, sep, attr); + curr += size; + + if (header->attribute != NULL) { + + free(attr); + } + + header = header->next; + } + + _h[curr] = '\0'; + return _h; +} + +char *http_header_attribute_print(http_header_attribute *attribute) { + + char *_h = (char *) malloc(1); + size_t size, curr = 0; + http_header_attribute *attr = attribute; + + _h[0] = '\0'; + + if (attr == NULL) { + + return _h; + } + + if (attr->type == HTTP_HEADER_ATTR_TYPE_BOOL) { + + size = snprintf(NULL, 0, "%s", attr->name); + + _h = (char *) realloc(_h, curr + size + 1); + sprintf(&_h[curr], " %s", attr->name); + curr += size; + } + + else { + + size = snprintf(NULL, 0, "%s=%s", attr->name, attr->value); + _h = (char *) realloc(_h, curr + size + 1); + sprintf(&_h[curr], "%s=%s", attr->name, attr->value); + curr += size; + } + + attr = attr->next; + + while (attr != NULL) { + + if (attr->type == HTTP_HEADER_ATTR_TYPE_BOOL) { + + size = snprintf(NULL, 0, "; %s", attr->name); + _h = (char *) realloc(_h, curr + size + 1); + sprintf(&_h[curr], "; %s", attr->name); + curr += size; + } + + else { + + size = snprintf(NULL, 0, "; %s=%s", attr->name, attr->value); + _h = (char *) realloc(_h, curr + size + 1); + sprintf(&_h[curr], "; %s=%s", attr->name, attr->value); + curr += size; + } + + attr = attr->next; + } + + _h[curr] = '\0'; + return _h; +} + +void http_header_free(http_header *header) { + + if (header != NULL) { + + if (header->next != NULL) { + + http_header_free(header->next); + } + + if (header->name != NULL) { + + free(header->name); + } + + if (header->value != NULL) { + + free(header->value); + } + + if (header->attribute != NULL) { + + http_header_attribute_free(header->attribute); + header->attribute = NULL; + } + + free(header); + } +} + +void http_header_attribute_free(http_header_attribute *attr) { + + if (attr != NULL) { + + if (attr->type == HTTP_HEADER_ATTR_TYPE_STRING && attr->value != NULL) { + + free(attr->value); + } + + if (attr->name != NULL) { + + free(attr->name); + } + + if (attr->next != NULL) { + + http_header_attribute_free(attr->next); + } + + free(attr); + } +} + +#endif //HTTP_CLIENT_C_HEADER_H diff --git a/src/http/struct.h b/src/http/struct.h new file mode 100644 index 0000000..628422c --- /dev/null +++ b/src/http/struct.h @@ -0,0 +1,144 @@ +// +// Created by tbela on 2022-04-08. +// + +#ifndef HTTP_CLIENT_C_HTTP_STRUCT_H +#define HTTP_CLIENT_C_HTTP_STRUCT_H + +#define BUF_READ 8192 +#define HTTP_CLIENT_C_HTTP_MAX_REDIRECT 50; + +#include "http/header.h" + +typedef struct http_request http_request; + +typedef void (http_header_cb_ptr)(http_header *); +typedef void (http_response_body_cb_ptr)(const unsigned char*, size_t, http_header *); + +typedef enum { + HTTP_OPTION_URL, + HTTP_OPTION_HEADER, + HTTP_OPTION_BODY, + HTTP_OPTION_METHOD, + HTTP_OPTION_REQUEST_TIMEOUT, + HTTP_OPTION_REQUEST_HEADER_CALLBACK, + HTTP_OPTION_RESPONSE_HEADER_CALLBACK, + HTTP_OPTION_RESPONSE_BODY_CALLBACK +} http_option; + +typedef struct http_struct { + + http_header *headers; + char *body; + size_t body_len; +} http_struct; + + +typedef struct http_request { + struct http_struct; + char *request_uri; + int max_redirect; + char *method; + http_header_cb_ptr *request_header_cb; + http_header_cb_ptr *response_header_cb; + http_response_body_cb_ptr *response_body_cb; + struct timeval *request_timeout; +} http_request; + +/* + Represents an HTTP response +*/ +typedef struct http_response { + struct http_struct; + char *redirect_uri; + int redirect_count; + int status_code; + char *status_text; +} http_response; + +void http_response_free(http_response *hresp); + +http_request *http_request_new() { + + http_request *hreq = (http_request *) calloc(sizeof(http_request), 1); + + if (hreq != NULL) { + + hreq->max_redirect = HTTP_CLIENT_C_HTTP_MAX_REDIRECT; + hreq->request_timeout = (struct timeval *) calloc(1, sizeof (struct timeval)); + hreq->request_timeout->tv_sec = 10; + hreq->request_timeout->tv_usec = 0; + } + + return hreq; +} + +http_response *http_response_new() { + + http_response *hresp = (http_response *) calloc(sizeof(http_response), 1); + + return hresp; +} + +/* + Free memory of http_response +*/ +void http_request_free(http_request *hreq) { + + if (hreq != NULL) { + + if (hreq->headers != NULL) { + + http_header_free(hreq->headers); + } + + if (hreq->request_uri != NULL) { + + free(hreq->request_uri); + } + + if (hreq->body != NULL) { + + free(hreq->body); + } + + if (hreq->method != NULL) { + + free(hreq->method); + } + + free(hreq->request_timeout); + free(hreq); + } +} + +void http_response_free(http_response *hresp) { + + if (hresp != NULL) { + + + if (hresp->status_text != NULL) { + + free(hresp->status_text); + } + + if (hresp->redirect_uri != NULL) { + + free(hresp->redirect_uri); + } + + if (hresp->body != NULL) { + + free(hresp->body); + } + + if (hresp->headers != NULL) { + + http_header_free(hresp->headers); + } + + free(hresp); + } +} + +#endif //HTTP_CLIENT_C_STRUCT_H diff --git a/src/stringx.h b/src/stringx.h index c2d30ed..ca38497 100644 --- a/src/stringx.h +++ b/src/stringx.h @@ -1,270 +1,109 @@ -/* - http-client-c - Copyright (C) 2012-2013 Swen Kooij - - This file is part of http-client-c. - - http-client-c is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - http-client-c is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with http-client-c. If not, see . - - Warning: - This library does not tend to work that stable nor does it fully implent the - standards described by IETF. For more information on the precise implentation of the - Hyper Text Transfer Protocol: - - http://www.ietf.org/rfc/rfc2616.txt -*/ - -#include -#include -#include - -#ifdef __cplusplus - #include -#endif -/* - Gets the offset of one string in another string -*/ -int str_index_of(const char *a, char *b) -{ - char *offset = (char*)strstr(a, b); - return offset - a; -} -/* - Checks if one string contains another string -*/ -int str_contains(const char *haystack, const char *needle) -{ - char *pos = (char*)strstr(haystack, needle); - if(pos) - return 1; - else - return 0; -} +#ifndef HTTP_CLIENT_C_HTTP_CLIENT_HEX_H +#define HTTP_CLIENT_C_HTTP_CLIENT_HEX_H -/* - Removes last character from string -*/ -char* trim_end(char *string, char to_trim) -{ - char last_char = string[strlen(string) -1]; - if(last_char == to_trim) - { - char *new_string = string; - new_string[strlen(string) - 1] = 0; - return new_string; - } - else - { - return string; - } -} +#include "stddef.h" +#include "string.h" +#include -/* - Concecates two strings, a wrapper for strcat from string.h, handles the resizing and copying -*/ -char* str_cat(char *a, char *b) -{ - char *target = (char*)malloc(strlen(a) + strlen(b) + 1); - strcpy(target, a); - strcat(target, b); - return target; -} +size_t hex2dec(const char *, size_t len); +void str_to_upper(char *, size_t); -/* - Converts an integer value to its hex character -*/ -char to_hex(char code) -{ - static char hex[] = "0123456789abcdef"; - return hex[code & 15]; -} +char *str2hex(unsigned char *str, size_t len) { -/* - URL encodes a string -*/ -char *urlencode(char *str) -{ - char *pstr = str, *buf = (char*)malloc(strlen(str) * 3 + 1), *pbuf = buf; - while (*pstr) - { - if (isalnum(*pstr) || *pstr == '-' || *pstr == '_' || *pstr == '.' || *pstr == '~') - *pbuf++ = *pstr; - else if (*pstr == ' ') - *pbuf++ = '+'; - else - *pbuf++ = '%', *pbuf++ = to_hex(*pstr >> 4), *pbuf++ = to_hex(*pstr & 15); - pstr++; - } - *pbuf = '\0'; - return buf; -} + char *result = malloc(2 * len + 1); + + for (size_t i = 0; i < len; ++i) { -/* - Replacement for the string.h strndup, fixes a bug -*/ -char *str_ndup (const char *str, size_t max) -{ - size_t len = strnlen (str, max); - char *res = (char*)malloc (len + 1); - if (res) - { - memcpy (res, str, len); - res[len] = '\0'; + sprintf(&result[2 * i], "%02X", str[i]); } - return res; -} -/* - Replacement for the string.h strdup, fixes a bug -*/ -char *str_dup(const char *src) -{ - char *tmp = (char*)malloc(strlen(src) + 1); - if(tmp) - strcpy(tmp, src); - return tmp; + result[2 * len] = '\0'; + return result; } -/* - Search and replace a string with another string , in a string -*/ -char *str_replace(char *search , char *replace , char *subject) -{ - char *p = NULL , *old = NULL , *new_subject = NULL ; - int c = 0 , search_size; - search_size = strlen(search); - for(p = strstr(subject , search) ; p != NULL ; p = strstr(p + search_size , search)) - { - c++; - } - c = ( strlen(replace) - search_size )*c + strlen(subject); - new_subject = (char*)malloc( c ); - strcpy(new_subject , ""); - old = subject; - for(p = strstr(subject , search) ; p != NULL ; p = strstr(p + search_size , search)) - { - strncpy(new_subject + strlen(new_subject) , old , p - old); - strcpy(new_subject + strlen(new_subject) , replace); - old = p + search_size; - } - strcpy(new_subject + strlen(new_subject) , old); - return new_subject; -} +char *hex2str(char *str, size_t *len) { -/* - Get's all characters until '*until' has been found -*/ -char* get_until(char *haystack, char *until) -{ - int offset = str_index_of(haystack, until); - return str_ndup(haystack, offset); -} + size_t counter = 0; + size_t size = strlen(str); + char *result = malloc(*len / 2 + 1); + + for (size_t i = 0; i < size; i += 2) { + result[counter++] = (char) hex2dec(&str[i], 2); + } -/* decodeblock - decode 4 '6-bit' characters into 3 8-bit binary bytes */ -void decodeblock(unsigned char in[], char *clrstr) -{ - unsigned char out[4]; - out[0] = in[0] << 2 | in[1] >> 4; - out[1] = in[1] << 4 | in[2] >> 2; - out[2] = in[2] << 6 | in[3] >> 0; - out[3] = '\0'; - strncat((char *)clrstr, (char *)out, sizeof(out)); + *len = counter - 1; + result[counter] = '\0'; + return result; } -/* - Decodes a Base64 string -*/ -char* base64_decode(char *b64src) -{ - char *clrdst = (char*)malloc( ((strlen(b64src) - 1) / 3 ) * 4 + 4 + 50); - char b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - int c, phase, i; - unsigned char in[4]; - char *p; - clrdst[0] = '\0'; - phase = 0; i=0; - while(b64src[i]) - { - c = (int) b64src[i]; - if(c == '=') - { - decodeblock(in, clrdst); - break; - } - p = strchr(b64, c); - if(p) - { - in[phase] = p - b64; - phase = (phase + 1) % 4; - if(phase == 0) - { - decodeblock(in, clrdst); - in[0]=in[1]=in[2]=in[3]=0; - } - } - i++; - } - clrdst = (char*)realloc(clrdst, strlen(clrdst) + 1); - return clrdst; +char *ltrim(char *str) { + + char *trim = str; + + while (trim) { + + if (trim[0] == ' ' || + trim[0] == '\n'|| + trim[0] == '\r'|| + trim[0] == '\t') { + + trim++; + continue; + } + + break; + } + + return strdup(trim == NULL ? "" : trim); } -/* encodeblock - encode 3 8-bit binary bytes as 4 '6-bit' characters */ -void encodeblock( unsigned char in[], char b64str[], int len ) -{ - char b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - unsigned char out[5]; - out[0] = b64[ in[0] >> 2 ]; - out[1] = b64[ ((in[0] & 0x03) << 4) | ((in[1] & 0xf0) >> 4) ]; - out[2] = (unsigned char) (len > 1 ? b64[ ((in[1] & 0x0f) << 2) | - ((in[2] & 0xc0) >> 6) ] : '='); - out[3] = (unsigned char) (len > 2 ? b64[ in[2] & 0x3f ] : '='); - out[4] = '\0'; - strncat((char *)b64str, (char *)out, sizeof(out)); +size_t hex2dec(const char *hex, size_t len) { + + size_t i = len; + size_t j = i - 1; + + size_t result = 0; + + while (i-- > 0) { + + char ch = hex[i]; + + if (ch >= '0' && ch <= '9') { + + result += (ch - '0') * pow(16, j - i); + } else { + + if (ch >= 'a') { + + ch -= 32; + } + + if (ch >= 'A' && ch <= 'F') { + + result += (ch - 'A' + 10) * pow(16, j - i); + } + } + } + + return result; } -/* - Encodes a string with Base64 -*/ -char* base64_encode(char *clrstr) -{ - char *b64dst = (char*)malloc(strlen(clrstr) + 50); - char b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - unsigned char in[3]; - int i, len = 0; - int j = 0; - - b64dst[0] = '\0'; - while(clrstr[j]) - { - len = 0; - for(i=0; i<3; i++) - { - in[i] = (unsigned char) clrstr[j]; - if(clrstr[j]) - { - len++; j++; - } - else in[i] = 0; - } - if( len ) - { - encodeblock( in, b64dst, len ); - } - } - b64dst = (char*)realloc(b64dst, strlen(b64dst) + 1); - return b64dst; +void str_to_upper(char *str, size_t len) { + + char c; + + for (int i = 0; i < len; i++) { + + c = str[i]; + + if (c >= 'a' && c <= 'z') { + + str[i] = c - 32; + } + } } + +#endif diff --git a/src/urlparser.h b/src/url/parser.h similarity index 51% rename from src/urlparser.h rename to src/url/parser.h index 7399356..679a531 100644 --- a/src/urlparser.h +++ b/src/url/parser.h @@ -25,38 +25,41 @@ http://www.ietf.org/rfc/rfc2616.txt */ +#include +#include + +#ifndef HTTP_CLIENT_C_HTTP_URLPARSER_H +#define HTTP_CLIENT_C_HTTP_URLPARSER_H + /* Represents an url */ -struct parsed_url -{ - char *uri; /* mandatory */ +typedef struct http_url { + char *uri; /* mandatory */ char *scheme; /* mandatory */ char *host; /* mandatory */ - char *ip; /* mandatory */ + char *ip; /* mandatory */ char *port; /* optional */ char *path; /* optional */ char *query; /* optional */ char *fragment; /* optional */ char *username; /* optional */ char *password; /* optional */ -}; +} http_url; /* Free memory of parsed url */ -void parsed_url_free(struct parsed_url *purl) -{ - if ( NULL != purl ) - { - if ( NULL != purl->scheme ) free(purl->scheme); - if ( NULL != purl->host ) free(purl->host); - if ( NULL != purl->port ) free(purl->port); - if ( NULL != purl->path ) free(purl->path); - if ( NULL != purl->query ) free(purl->query); - if ( NULL != purl->fragment ) free(purl->fragment); - if ( NULL != purl->username ) free(purl->username); - if ( NULL != purl->password ) free(purl->password); +void http_url_free(http_url *purl) { + if (NULL != purl) { + if (NULL != purl->scheme) free(purl->scheme); + if (NULL != purl->host) free(purl->host); + if (NULL != purl->port) free(purl->port); + if (NULL != purl->path) free(purl->path); + if (NULL != purl->query) free(purl->query); + if (NULL != purl->fragment) free(purl->fragment); + if (NULL != purl->username) free(purl->username); + if (NULL != purl->password) free(purl->password); free(purl); } } @@ -64,58 +67,47 @@ void parsed_url_free(struct parsed_url *purl) /* Retrieves the IP adress of a hostname */ -char* hostname_to_ip(char *hostname) -{ - char *ip = "0.0.0.0"; - struct hostent *h; - if ((h=gethostbyname(hostname)) == NULL) - { - printf("gethostbyname"); - return NULL; - } - return inet_ntoa(*((struct in_addr *)h->h_addr)); +char *hostname_to_ip(char *hostname) { +// char *ip = "0.0.0.0"; + struct hostent *h; + if ((h = gethostbyname(hostname)) == NULL) { + printf("gethostbyname"); + return NULL; + } + return inet_ntoa(*((struct in_addr *) h->h_addr)); } /* Check whether the character is permitted in scheme string */ -int is_scheme_char(int c) -{ +int is_scheme_char(int c) { return (!isalpha(c) && '+' != c && '-' != c && '.' != c) ? 0 : 1; } /* - Parses a specified URL and returns the structure named 'parsed_url' + Parses a specified URL and returns the structure named 'http_url' Implented according to: RFC 1738 - http://www.ietf.org/rfc/rfc1738.txt RFC 3986 - http://www.ietf.org/rfc/rfc3986.txt */ -struct parsed_url *parse_url(const char *url) -{ - - /* Define variable */ - struct parsed_url *purl; +http_url *http_url_parse(const char *url) { + + /* Define variable */ + http_url *purl; const char *tmpstr; const char *curstr; - int len; + size_t len; int i; int userpass_flag; int bracket_flag; /* Allocate the parsed url storage */ - purl = (struct parsed_url*)malloc(sizeof(struct parsed_url)); - if ( NULL == purl ) - { + purl = (http_url *) calloc(1, sizeof(http_url)); + + if (NULL == purl) { return NULL; } - purl->scheme = NULL; - purl->host = NULL; - purl->port = NULL; - purl->path = NULL; - purl->query = NULL; - purl->fragment = NULL; - purl->username = NULL; - purl->password = NULL; + curstr = url; /* @@ -125,10 +117,10 @@ struct parsed_url *parse_url(const char *url) */ /* Read scheme */ tmpstr = strchr(curstr, ':'); - if ( NULL == tmpstr ) - { - parsed_url_free(purl); fprintf(stderr, "Error on line %d (%s)\n", __LINE__, __FILE__); - + if (NULL == tmpstr) { + http_url_free(purl); + fprintf(stderr, "Error on line %d (%s)\n", __LINE__, __FILE__); + return NULL; } @@ -136,30 +128,28 @@ struct parsed_url *parse_url(const char *url) len = tmpstr - curstr; /* Check restrictions */ - for ( i = 0; i < len; i++ ) - { - if (is_scheme_char(curstr[i]) == 0) - { + for (i = 0; i < len; i++) { + if (is_scheme_char(curstr[i]) == 0) { /* Invalid format */ - parsed_url_free(purl); fprintf(stderr, "Error on line %d (%s)\n", __LINE__, __FILE__); + http_url_free(purl); + fprintf(stderr, "Error on line %d (%s)\n", __LINE__, __FILE__); return NULL; } } /* Copy the scheme to the storage */ - purl->scheme = (char*)malloc(sizeof(char) * (len + 1)); - if ( NULL == purl->scheme ) - { - parsed_url_free(purl); fprintf(stderr, "Error on line %d (%s)\n", __LINE__, __FILE__); - + purl->scheme = (char *) malloc(sizeof(char) * (len + 1)); + if (NULL == purl->scheme) { + http_url_free(purl); + fprintf(stderr, "Error on line %d (%s)\n", __LINE__, __FILE__); + return NULL; } - (void)strncpy(purl->scheme, curstr, len); + (void) strncpy(purl->scheme, curstr, len); purl->scheme[len] = '\0'; /* Make the character to lower if it is upper case. */ - for ( i = 0; i < len; i++ ) - { + for (i = 0; i < len; i++) { purl->scheme[i] = tolower(purl->scheme[i]); } @@ -172,11 +162,10 @@ struct parsed_url *parse_url(const char *url) * Any ":", "@" and "/" must be encoded. */ /* Eat "//" */ - for ( i = 0; i < 2; i++ ) - { - if ( '/' != *curstr ) - { - parsed_url_free(purl); fprintf(stderr, "Error on line %d (%s)\n", __LINE__, __FILE__); + for (i = 0; i < 2; i++) { + if ('/' != *curstr) { + http_url_free(purl); + fprintf(stderr, "Error on line %d (%s)\n", __LINE__, __FILE__); return NULL; } curstr++; @@ -185,16 +174,12 @@ struct parsed_url *parse_url(const char *url) /* Check if the user (and password) are specified. */ userpass_flag = 0; tmpstr = curstr; - while ( '\0' != *tmpstr ) - { - if ( '@' == *tmpstr ) - { + while ('\0' != *tmpstr) { + if ('@' == *tmpstr) { /* Username and password are specified */ userpass_flag = 1; break; - } - else if ( '/' == *tmpstr ) - { + } else if ('/' == *tmpstr) { /* End of : specification */ userpass_flag = 0; break; @@ -204,199 +189,182 @@ struct parsed_url *parse_url(const char *url) /* User and password specification */ tmpstr = curstr; - if ( userpass_flag ) - { + if (userpass_flag) { /* Read username */ - while ( '\0' != *tmpstr && ':' != *tmpstr && '@' != *tmpstr ) - { + while ('\0' != *tmpstr && ':' != *tmpstr && '@' != *tmpstr) { tmpstr++; } len = tmpstr - curstr; - purl->username = (char*)malloc(sizeof(char) * (len + 1)); - if ( NULL == purl->username ) - { - parsed_url_free(purl); fprintf(stderr, "Error on line %d (%s)\n", __LINE__, __FILE__); + purl->username = (char *) malloc(sizeof(char) * (len + 1)); + if (NULL == purl->username) { + http_url_free(purl); + fprintf(stderr, "Error on line %d (%s)\n", __LINE__, __FILE__); return NULL; } - (void)strncpy(purl->username, curstr, len); + (void) strncpy(purl->username, curstr, len); purl->username[len] = '\0'; /* Proceed current pointer */ curstr = tmpstr; - if ( ':' == *curstr ) - { + if (':' == *curstr) { /* Skip ':' */ curstr++; - + /* Read password */ tmpstr = curstr; - while ( '\0' != *tmpstr && '@' != *tmpstr ) - { + while ('\0' != *tmpstr && '@' != *tmpstr) { tmpstr++; } len = tmpstr - curstr; - purl->password = (char*)malloc(sizeof(char) * (len + 1)); - if ( NULL == purl->password ) - { - parsed_url_free(purl); fprintf(stderr, "Error on line %d (%s)\n", __LINE__, __FILE__); + purl->password = (char *) malloc(sizeof(char) * (len + 1)); + if (NULL == purl->password) { + http_url_free(purl); + fprintf(stderr, "Error on line %d (%s)\n", __LINE__, __FILE__); return NULL; } - (void)strncpy(purl->password, curstr, len); + (void) strncpy(purl->password, curstr, len); purl->password[len] = '\0'; curstr = tmpstr; } /* Skip '@' */ - if ( '@' != *curstr ) - { - parsed_url_free(purl); fprintf(stderr, "Error on line %d (%s)\n", __LINE__, __FILE__); + if ('@' != *curstr) { + http_url_free(purl); + fprintf(stderr, "Error on line %d (%s)\n", __LINE__, __FILE__); return NULL; } curstr++; } - if ( '[' == *curstr ) - { + if ('[' == *curstr) { bracket_flag = 1; - } - else - { + } else { bracket_flag = 0; } /* Proceed on by delimiters with reading host */ tmpstr = curstr; - while ( '\0' != *tmpstr ) { - if ( bracket_flag && ']' == *tmpstr ) - { + while ('\0' != *tmpstr) { + if (bracket_flag && ']' == *tmpstr) { /* End of IPv6 address. */ tmpstr++; break; - } - else if ( !bracket_flag && (':' == *tmpstr || '/' == *tmpstr) ) - { + } else if (!bracket_flag && (':' == *tmpstr || '/' == *tmpstr)) { /* Port number is specified. */ break; } tmpstr++; } len = tmpstr - curstr; - purl->host = (char*)malloc(sizeof(char) * (len + 1)); - if ( NULL == purl->host || len <= 0 ) - { - parsed_url_free(purl); fprintf(stderr, "Error on line %d (%s)\n", __LINE__, __FILE__); + purl->host = (char *) malloc(sizeof(char) * (len + 1)); + if (NULL == purl->host || len <= 0) { + http_url_free(purl); + fprintf(stderr, "Error on line %d (%s)\n", __LINE__, __FILE__); return NULL; } - (void)strncpy(purl->host, curstr, len); + (void) strncpy(purl->host, curstr, len); purl->host[len] = '\0'; curstr = tmpstr; /* Is port number specified? */ - if ( ':' == *curstr ) - { + if (':' == *curstr) { curstr++; /* Read port number */ tmpstr = curstr; - while ( '\0' != *tmpstr && '/' != *tmpstr ) - { + while ('\0' != *tmpstr && '/' != *tmpstr) { tmpstr++; } len = tmpstr - curstr; - purl->port = (char*)malloc(sizeof(char) * (len + 1)); - if ( NULL == purl->port ) - { - parsed_url_free(purl); fprintf(stderr, "Error on line %d (%s)\n", __LINE__, __FILE__); + purl->port = (char *) malloc(sizeof(char) * (len + 1)); + if (NULL == purl->port) { + http_url_free(purl); + fprintf(stderr, "Error on line %d (%s)\n", __LINE__, __FILE__); return NULL; } - (void)strncpy(purl->port, curstr, len); + (void) strncpy(purl->port, curstr, len); purl->port[len] = '\0'; curstr = tmpstr; + } else { + + purl->port = calloc(sizeof (char), 3); + sprintf(purl->port, "80"); } - else - { - purl->port = "80"; - } - - /* Get ip */ - char *ip = hostname_to_ip(purl->host); - purl->ip = ip; - - /* Set uri */ - purl->uri = (char*)url; + + /* Get ip */ + purl->ip = hostname_to_ip(purl->host); + + /* Set uri */ + purl->uri = (char *) url; /* End of the string */ - if ( '\0' == *curstr ) - { + if ('\0' == *curstr) { return purl; } /* Skip '/' */ - if ( '/' != *curstr ) - { - parsed_url_free(purl); fprintf(stderr, "Error on line %d (%s)\n", __LINE__, __FILE__); + if ('/' != *curstr) { + http_url_free(purl); + fprintf(stderr, "Error on line %d (%s)\n", __LINE__, __FILE__); return NULL; } curstr++; /* Parse path */ tmpstr = curstr; - while ( '\0' != *tmpstr && '#' != *tmpstr && '?' != *tmpstr ) - { + while ('\0' != *tmpstr && '#' != *tmpstr && '?' != *tmpstr) { tmpstr++; } len = tmpstr - curstr; - purl->path = (char*)malloc(sizeof(char) * (len + 1)); - if ( NULL == purl->path ) - { - parsed_url_free(purl); fprintf(stderr, "Error on line %d (%s)\n", __LINE__, __FILE__); + purl->path = (char *) malloc(sizeof(char) * (len + 1)); + if (NULL == purl->path) { + http_url_free(purl); + fprintf(stderr, "Error on line %d (%s)\n", __LINE__, __FILE__); return NULL; } - (void)strncpy(purl->path, curstr, len); + (void) strncpy(purl->path, curstr, len); purl->path[len] = '\0'; curstr = tmpstr; /* Is query specified? */ - if ( '?' == *curstr ) - { + if ('?' == *curstr) { /* Skip '?' */ curstr++; /* Read query */ tmpstr = curstr; - while ( '\0' != *tmpstr && '#' != *tmpstr ) - { + while ('\0' != *tmpstr && '#' != *tmpstr) { tmpstr++; } len = tmpstr - curstr; - purl->query = (char*)malloc(sizeof(char) * (len + 1)); - if ( NULL == purl->query ) - { - parsed_url_free(purl); fprintf(stderr, "Error on line %d (%s)\n", __LINE__, __FILE__); + purl->query = (char *) malloc(sizeof(char) * (len + 1)); + if (NULL == purl->query) { + http_url_free(purl); + fprintf(stderr, "Error on line %d (%s)\n", __LINE__, __FILE__); return NULL; } - (void)strncpy(purl->query, curstr, len); + (void) strncpy(purl->query, curstr, len); purl->query[len] = '\0'; curstr = tmpstr; } /* Is fragment specified? */ - if ( '#' == *curstr ) - { + if ('#' == *curstr) { /* Skip '#' */ curstr++; /* Read fragment */ tmpstr = curstr; - while ( '\0' != *tmpstr ) - { + while ('\0' != *tmpstr) { tmpstr++; } len = tmpstr - curstr; - purl->fragment = (char*)malloc(sizeof(char) * (len + 1)); - if ( NULL == purl->fragment ) - { - parsed_url_free(purl); fprintf(stderr, "Error on line %d (%s)\n", __LINE__, __FILE__); + purl->fragment = (char *) malloc(sizeof(char) * (len + 1)); + if (NULL == purl->fragment) { + http_url_free(purl); + fprintf(stderr, "Error on line %d (%s)\n", __LINE__, __FILE__); return NULL; } - (void)strncpy(purl->fragment, curstr, len); + (void) strncpy(purl->fragment, curstr, len); purl->fragment[len] = '\0'; curstr = tmpstr; } - return purl; -} \ No newline at end of file + return purl; +} + +#endif \ No newline at end of file diff --git a/test/main.c b/test/main.c new file mode 100644 index 0000000..0f2bb37 --- /dev/null +++ b/test/main.c @@ -0,0 +1,127 @@ +// +// Created by tbela on 2022-04-07. +// +#include "http/client.h" + +FILE *fp = NULL; +char *charset = NULL; +char *filename = NULL; + +/** + * response header callback + * @param headers + */ +void response_header_cb(http_header *headers) { + + if (fp == NULL) { + + // check content charset +// http_header *header = http_header_get(headers, "Content-Type"); +// +// if (header != NULL) { +// +// if (header->attribute != NULL) { +// +// http_header_attribute *cs = http_header_attribute_get(header->attribute, "charset"); +// +// if (cs != NULL) { +// +// fprintf(stderr, "\n\ncharset = '%s'\n\n", cs->value); +// charset = strdup(cs->value); +// } +// } +// +// http_header_free(header); +// header = NULL; +// } + + fp = fopen(filename, "wb"); + } + + fprintf(stderr, "headers received:\n"); + + char *printed = http_header_print(headers); + + fprintf(stderr, "%s\r\n", printed); + free(printed); +} + +/** + * request header callback + * @param headers + */ +void request_header_cb(http_header *headers) { + + fprintf(stderr, "headers sent:\n"); + + char *printed = http_header_print(headers); + + fprintf(stderr, "%s\r\n", printed); + free(printed); +} + +/** + * response body callback + * @param chunk + * @param chunk_len + * @param headers + */ +void response_body_cb(const unsigned char *chunk, size_t chunk_len, http_header *headers) { + + if (chunk_len > 0) { + + fwrite(chunk, 1, chunk_len, fp); + + http_header *content_type = http_header_get(headers, "Content-Type"); + + // if text content, dump to stderr + if (content_type != NULL && strstr(content_type->value, "text/") != NULL) { + + fwrite(chunk, chunk_len, 1, stderr); + } + + http_header_free(content_type); + } +} + +int main(int argc, char *argv[]) { + + if (argc <= 2) { + + fprintf(stderr, "Usage: \n$ %s URL DEST_FILE\n", argv[0]); + exit(1); + } + + filename = argc > 2 ? argv[2] : ""; + fprintf(stderr, "opening %s ...\n", filename); + + http_request *request = http_request_new(); + + http_request_option(request, HTTP_OPTION_URL, argv[1], 0); +// http_request_option(request, HTTP_OPTION_METHOD, "POST"); +// http_request_option(request, HTTP_OPTION_BODY, "a=1&b=2"); + http_request_option(request, HTTP_OPTION_REQUEST_TIMEOUT, "5", 0); + http_request_option(request, HTTP_OPTION_REQUEST_HEADER_CALLBACK, request_header_cb, 0); + http_request_option(request, HTTP_OPTION_RESPONSE_HEADER_CALLBACK, response_header_cb, 0); + http_request_option(request, HTTP_OPTION_RESPONSE_BODY_CALLBACK, response_body_cb, 0); + http_request_header_set(request, "User-Agent", "Bluefox"); +// http_request_header_set(request, "Authorization", "Bearer "); +// http_request_header_add(request, "Accept-Language", "zh-CN;q=0.9;en-US;q=0.6,en;q=0.4"); + + http_response *response = http_request_exec(request); + + if (fp != NULL) { + + fclose(fp); + } + + if (charset != NULL) { + + free(charset); + } + + http_request_free(request); + http_response_free(response); + + return 0; +} \ No newline at end of file