From 57f548d6c8e54f78ca8564fc0f0600b529f999a4 Mon Sep 17 00:00:00 2001 From: Thierry Bela Date: Thu, 7 Apr 2022 19:07:34 -0400 Subject: [PATCH 01/12] enable safe binary data transfer and fixed memory leaks --- CMakeLists.txt | 15 ++++ README.md | 16 +++-- src/base64.h | 171 ++++++++++++++++++++++++++++++++++++++++++++ src/http-client-c.h | 147 ++++++++++++++++++++++--------------- src/stringx.h | 103 +++++--------------------- src/urlparser.h | 6 +- test/main.c | 56 +++++++++++++++ 7 files changed, 366 insertions(+), 148 deletions(-) create mode 100644 CMakeLists.txt create mode 100644 src/base64.h create mode 100644 test/main.c diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..c33237d --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,15 @@ +MATH(EXPR stack_size "512") # 16 Mb +cmake_minimum_required(VERSION 3.21) +project(http_client_c C) + +set(CMAKE_C_STANDARD 11) + + +include_directories(src) + +add_executable(http_client_c + src/base64.h + src/http-client-c.h + src/stringx.h + src/urlparser.h + test/main.c) diff --git a/README.md b/README.md index 42a6839..1a6be4d 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ Please note that all functions return a pointer to an insance of http_response. { struct parsed_url *request_uri; char *body; + size_t body_len; char *status_code; int status_code_int; char *status_text; @@ -26,26 +27,29 @@ Please note that all functions return a pointer to an insance of http_response. char *response_headers; }; -#####*request_uri +##### *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 +##### *body This contains the response BODY (usually HTML). -#####*status_code +#####body_len +This contains the length of the response. Useful to deal with binary data. + +##### *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 +##### *status_text This returns the text associated with the status code. For status code 200, OK will be returned. -#####*request_headers +##### *request_headers This contains the HTTP headers that were used to make the request. -#####*response_headers +##### *response_headers Contains the HTTP headers returned by the server. http_req() diff --git a/src/base64.h b/src/base64.h new file mode 100644 index 0000000..1e43acc --- /dev/null +++ b/src/base64.h @@ -0,0 +1,171 @@ +#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; i0; ) { + 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> 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> 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/http-client-c.h b/src/http-client-c.h index 2ce5042..711f381 100644 --- a/src/http-client-c.h +++ b/src/http-client-c.h @@ -18,24 +18,25 @@ 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 + 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 */ -#pragma GCC diagnostic ignored "-Wwrite-strings" +//#pragma GCC diagnostic ignored "-Wwrite-strings" #include #include #include #include +#include "base64.h" #ifdef _WIN32 #include #include #include #pragma comment(lib, "Ws2_32.lib") -#elif _LINUX +#elif __linux__ #include #elif __FreeBSD__ #include @@ -47,16 +48,17 @@ #endif #include -#include "stringx.h"; +#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_req(char *http_headers, struct parsed_url *purl, unsigned long i); 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); +struct http_response *http_post(char *url, char *custom_headers, char *post_data, size_t i); /* @@ -66,6 +68,7 @@ struct http_response { struct parsed_url *request_uri; char *body; + size_t body_len; char *status_code; int status_code_int; char *status_text; @@ -128,7 +131,7 @@ struct http_response* handle_redirect_head(struct http_response* hresp, char* cu /* Handles redirect if needed for post requests */ -struct http_response* handle_redirect_post(struct http_response* hresp, char* custom_headers, char *post_data) +struct http_response* handle_redirect_post(struct http_response* hresp, char* custom_headers, char *post_data, size_t len) { if(hresp->status_code_int > 300 && hresp->status_code_int < 399) { @@ -139,7 +142,7 @@ struct http_response* handle_redirect_post(struct http_response* hresp, char* cu { /* Extract url */ char *location = str_replace("Location: ", "", token); - return http_post(location, custom_headers, post_data); + return http_post(location, custom_headers, post_data, len); } token = strtok(NULL, "\r\n"); } @@ -154,7 +157,7 @@ struct http_response* handle_redirect_post(struct http_response* hresp, char* cu /* Makes a HTTP request and returns the response */ -struct http_response* http_req(char *http_headers, struct parsed_url *purl) +struct http_response* http_req(char *http_headers, struct parsed_url *purl, size_t header_length) { /* Parse url */ if(purl == NULL) @@ -177,6 +180,7 @@ struct http_response* http_req(char *http_headers, struct parsed_url *purl) return NULL; } hresp->body = NULL; + hresp->body_len = 0; hresp->request_headers = NULL; hresp->response_headers = NULL; hresp->status_code = NULL; @@ -195,30 +199,35 @@ struct http_response* http_req(char *http_headers, struct parsed_url *purl) tmpres = inet_pton(AF_INET, purl->ip, (void *)(&(remote->sin_addr.s_addr))); if( tmpres < 0) { + free(remote); printf("Can't set remote->sin_addr.s_addr"); return NULL; } else if(tmpres == 0) { + free(remote); 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) { + free(remote); printf("Could not connect"); return NULL; } /* Send headers to server */ int sent = 0; - while(sent < strlen(http_headers)) + while(sent < header_length) { - tmpres = send(sock, http_headers+sent, strlen(http_headers)-sent, 0); + tmpres = send(sock, &http_headers[sent], header_length-sent, 0); if(tmpres == -1) { + free(remote); printf("Can't send headers"); return NULL; } @@ -228,27 +237,33 @@ struct http_response* http_req(char *http_headers, struct parsed_url *purl) /* 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) + size_t received_len = 0; + size_t response_len = 0; + while((received_len = recv(sock, BUF, BUFSIZ - 1, 0)) > 0) + { + BUF[received_len] = '\0'; + response = (char*)realloc(response, response_len + received_len + 1); + memcpy(&response[response_len], BUF, received_len); + response_len += received_len; + response[response_len] = '\0'; + } + + if (received_len < 0) { - free(http_headers); + free(remote); + free(response); + free(http_headers); #ifdef _WIN32 closesocket(sock); #else close(sock); #endif - printf("Unabel to recieve"); + fprintf(stderr,"Unable to receive"); return NULL; } /* Reallocate response */ - response = (char*)realloc(response, strlen(response) + 1); +// response = (char*)realloc(response, strlen(response) + 1); /* Close socket */ #ifdef _WIN32 @@ -258,16 +273,27 @@ struct http_response* http_req(char *http_headers, struct parsed_url *purl) #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; + char *status = get_until(response, "\r\n"); + char *status_line = str_replace("HTTP/1.1 ", "", status); + + free(status); + + status = strndup(status_line, 4); + char *status_code = str_replace(" ", "", status); + + free(status); + + status = str_replace(status_code, "", status_line); + char *status_text = str_replace(" ", "", status); + + free(status); + free(status_line); + + 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; @@ -279,9 +305,14 @@ struct http_response* http_req(char *http_headers, struct parsed_url *purl) 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; + hresp->body_len = response_len - strlen(headers) - 4; +// char *body = strstr(response, "\r\n\r\n"); +// body = str_replace("\r\n\r\n", "", body); + hresp->body = malloc(hresp->body_len + 1); + memcpy(hresp->body, &response[strlen(headers) + 4], hresp->body_len); + + free(remote); + free(response); /* Return response */ return hresp; @@ -302,6 +333,7 @@ struct http_response* http_get(char *url, char *custom_headers) /* Declare variable */ char *http_headers = (char*)malloc(1024); + memset(http_headers, 0, 1024); /* Build query/headers */ if(purl->path != NULL) @@ -336,7 +368,7 @@ struct http_response* http_get(char *url, char *custom_headers) upwd = (char*)realloc(upwd, strlen(upwd) + 1); /* Base64 encode */ - char *base64 = base64_encode(upwd); + char *base64 = base64_encode(upwd, strlen(upwd)); /* Form header */ char *auth_header = (char*)malloc(1024); @@ -351,16 +383,18 @@ struct http_response* http_get(char *url, char *custom_headers) /* Add custom headers, and close */ if(custom_headers != NULL) { - sprintf(http_headers, "%s%s\r\n", http_headers, custom_headers); + memcpy(&http_headers[strlen(http_headers)], "\r\n", 2); + memcpy(&http_headers[strlen(http_headers)], custom_headers, strlen(custom_headers)); } else { - sprintf(http_headers, "%s\r\n", http_headers); + memcpy(&http_headers[strlen(http_headers)], "\r\n", 2); } + http_headers = (char*)realloc(http_headers, strlen(http_headers) + 1); /* Make request and return response */ - struct http_response *hresp = http_req(http_headers, purl); + struct http_response *hresp = http_req(http_headers, purl, strlen(http_headers)); /* Handle redirect */ return handle_redirect_get(hresp, custom_headers); @@ -369,7 +403,7 @@ struct http_response* http_get(char *url, char *custom_headers) /* Makes a HTTP POST request to the given url */ -struct http_response* http_post(char *url, char *custom_headers, char *post_data) +struct http_response* http_post(char *url, char *custom_headers, char *post_data, size_t len) { /* Parse url */ struct parsed_url *purl = parse_url(url); @@ -387,22 +421,22 @@ struct http_response* http_post(char *url, char *custom_headers, char *post_data { 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)); + 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, len); } 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)); + 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, len); } } 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)); + 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, len); } 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)); + 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, len); } } @@ -415,7 +449,7 @@ struct http_response* http_post(char *url, char *custom_headers, char *post_data upwd = (char*)realloc(upwd, strlen(upwd) + 1); /* Base64 encode */ - char *base64 = base64_encode(upwd); + char *base64 = base64_encode(upwd, strlen(upwd)); /* Form header */ char *auth_header = (char*)malloc(1024); @@ -430,19 +464,20 @@ struct http_response* http_post(char *url, char *custom_headers, char *post_data 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); + + size_t header_length = strlen(http_headers) + len; + http_headers = (char*)realloc(http_headers, header_length + 1); + + memcpy(&http_headers[strlen(http_headers)], post_data, len); + +// http_headers = (char*)realloc(http_headers, strlen(http_headers) + 1); /* Make request and return response */ - struct http_response *hresp = http_req(http_headers, purl); + struct http_response *hresp = http_req(http_headers, purl, header_length); /* Handle redirect */ - return handle_redirect_post(hresp, custom_headers, post_data); + return handle_redirect_post(hresp, custom_headers, post_data, len); } /* @@ -494,7 +529,7 @@ struct http_response* http_head(char *url, char *custom_headers) upwd = (char*)realloc(upwd, strlen(upwd) + 1); /* Base64 encode */ - char *base64 = base64_encode(upwd); + char *base64 = base64_encode(upwd, strlen(upwd)); /* Form header */ char *auth_header = (char*)malloc(1024); @@ -517,7 +552,7 @@ struct http_response* http_head(char *url, char *custom_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); + struct http_response *hresp = http_req(http_headers, purl, 0); /* Handle redirect */ return handle_redirect_head(hresp, custom_headers); @@ -572,7 +607,7 @@ struct http_response* http_options(char *url) upwd = (char*)realloc(upwd, strlen(upwd) + 1); /* Base64 encode */ - char *base64 = base64_encode(upwd); + char *base64 = base64_encode(upwd, strlen(upwd)); /* Form header */ char *auth_header = (char*)malloc(1024); @@ -589,7 +624,7 @@ struct http_response* http_options(char *url) http_headers = (char*)realloc(http_headers, strlen(http_headers) + 1); /* Make request and return response */ - struct http_response *hresp = http_req(http_headers, purl); + struct http_response *hresp = http_req(http_headers, purl, 0); /* Handle redirect */ return hresp; @@ -604,7 +639,7 @@ void http_response_free(struct http_response *hresp) { 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_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); diff --git a/src/stringx.h b/src/stringx.h index c2d30ed..47870b0 100644 --- a/src/stringx.h +++ b/src/stringx.h @@ -33,6 +33,7 @@ #include #endif + /* Gets the offset of one string in another string */ @@ -115,17 +116,17 @@ char *urlencode(char *str) /* 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'; - } - return res; -} +//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'; +// } +// return res; +//} /* Replacement for the string.h strdup, fixes a bug @@ -152,15 +153,16 @@ char *str_replace(char *search , char *replace , char *subject) } c = ( strlen(replace) - search_size )*c + strlen(subject); new_subject = (char*)malloc( c ); - strcpy(new_subject , ""); + memset(new_subject, 0, 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); + 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); + strcpy(&new_subject[strlen(new_subject)] , old); return new_subject; } @@ -170,7 +172,7 @@ char *str_replace(char *search , char *replace , char *subject) char* get_until(char *haystack, char *until) { int offset = str_index_of(haystack, until); - return str_ndup(haystack, offset); + return strndup(haystack, offset); } @@ -185,43 +187,6 @@ void decodeblock(unsigned char in[], char *clrstr) strncat((char *)clrstr, (char *)out, sizeof(out)); } -/* - 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; -} - /* encodeblock - encode 3 8-bit binary bytes as 4 '6-bit' characters */ void encodeblock( unsigned char in[], char b64str[], int len ) { @@ -236,35 +201,3 @@ void encodeblock( unsigned char in[], char b64str[], int len ) strncat((char *)b64str, (char *)out, sizeof(out)); } -/* - 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; -} diff --git a/src/urlparser.h b/src/urlparser.h index 7399356..f5ada6e 100644 --- a/src/urlparser.h +++ b/src/urlparser.h @@ -25,6 +25,9 @@ http://www.ietf.org/rfc/rfc2616.txt */ +#include +#include + /* Represents an url */ @@ -97,13 +100,14 @@ struct parsed_url *parse_url(const char *url) struct parsed_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)); + memset(purl, 0, sizeof (struct parsed_url)); if ( NULL == purl ) { return NULL; diff --git a/test/main.c b/test/main.c new file mode 100644 index 0000000..f6ff0ca --- /dev/null +++ b/test/main.c @@ -0,0 +1,56 @@ +// +// Created by tbela on 2022-04-07. +// +#include "http-client-c.h" + +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); + + char *custom_headers = "User-Agent: Old-Brice\r\n"; + struct http_response *response = http_get(argv[1], custom_headers); + + if (response) { + + fprintf(stderr, "request headers: [\n%s\n]\nresponse headers [\n%s\n]\n", response->request_headers, + response->response_headers); + } + + if (response == NULL || response->status_code_int != 200) { + + fprintf(stderr, "request failed with status code #%d\n", response->status_code_int); + exit(1); + } + + if (response->body_len > 0) { + + FILE *fp = fopen(filename, "wb"); + + if (fp == NULL) { + + fprintf(stderr, "cannot open file for writing: %s\n", filename); + http_response_free(response); + exit(1); + } + + if (fwrite(response->body, 1, response->body_len, fp) != response->body_len) { + + fprintf(stderr, "failed to write data into file: %s\n", filename); + http_response_free(response); + fclose(fp); + exit(1); + } + + http_response_free(response); + fclose(fp); + } + + return 0; +} \ No newline at end of file From 252dba507dd89339dc7217242fd835ad89cf5809 Mon Sep 17 00:00:00 2001 From: Thierry bela Date: Thu, 7 Apr 2022 19:20:54 -0400 Subject: [PATCH 02/12] format readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1a6be4d..d4b0ced 100644 --- a/README.md +++ b/README.md @@ -34,13 +34,13 @@ URL. Look up parsed_url for more information. ##### *body This contains the response BODY (usually HTML). -#####body_len +##### body_len This contains the length of the response. Useful to deal with binary data. ##### *status_code This contains the HTTP Status code returned by the server in plain text format. -#####status_code_int +##### status_code_int This returns the same as status_code but as an integer. ##### *status_text From fcf3de9fae20f84cc4b6c21318cb43e7275bce2e Mon Sep 17 00:00:00 2001 From: Thierry bela Date: Thu, 7 Apr 2022 19:23:28 -0400 Subject: [PATCH 03/12] remove cmake instruction --- CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c33237d..1ce9c8c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,3 @@ -MATH(EXPR stack_size "512") # 16 Mb cmake_minimum_required(VERSION 3.21) project(http_client_c C) From e63ac9de2794bedca406e1cc10b66d9f8dd7a196 Mon Sep 17 00:00:00 2001 From: Thierry bela Date: Thu, 7 Apr 2022 19:28:25 -0400 Subject: [PATCH 04/12] missing free() --- test/main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/test/main.c b/test/main.c index f6ff0ca..219b588 100644 --- a/test/main.c +++ b/test/main.c @@ -26,6 +26,7 @@ int main(int argc, char *argv[]) { if (response == NULL || response->status_code_int != 200) { fprintf(stderr, "request failed with status code #%d\n", response->status_code_int); + http_response_free(response); exit(1); } From 3bc31bbfc5177f154efb5cc84c368d2948df10fb Mon Sep 17 00:00:00 2001 From: Thierry bela Date: Sat, 16 Apr 2022 01:27:30 -0400 Subject: [PATCH 05/12] refactor code | better http headers handling --- CMakeLists.txt | 1 - README.md | 22 +- src/base64.h | 95 ++-- src/http-client-c.h | 1100 +++++++++++++++++++------------------- src/http-client-header.h | 365 +++++++++++++ src/http-client-struct.h | 62 +++ src/stringx.h | 203 ------- src/urlparser.h | 282 +++++----- test/main.c | 114 ++-- 9 files changed, 1238 insertions(+), 1006 deletions(-) create mode 100644 src/http-client-header.h create mode 100644 src/http-client-struct.h delete mode 100644 src/stringx.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 1ce9c8c..c66cc4d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,5 @@ include_directories(src) add_executable(http_client_c src/base64.h src/http-client-c.h - src/stringx.h src/urlparser.h test/main.c) diff --git a/README.md b/README.md index d4b0ced..6586aed 100644 --- a/README.md +++ b/README.md @@ -17,17 +17,17 @@ Please note that all functions return a pointer to an insance of http_response. struct http_response { - struct parsed_url *request_uri; + struct parsed_url *redirect_ui; char *body; size_t body_len; char *status_code; - int status_code_int; + int status_code; char *status_text; char *request_headers; char *response_headers; }; -##### *request_uri +##### *redirect_ui 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. @@ -40,7 +40,7 @@ This contains the length of the response. Useful to deal with binary data. ##### *status_code This contains the HTTP Status code returned by the server in plain text format. -##### status_code_int +##### status_code This returns the same as status_code but as an integer. ##### *status_text @@ -52,24 +52,24 @@ This contains the HTTP headers that were used to make the request. ##### *response_headers Contains the HTTP headers returned by the server. -http_req() +http_request_exec() ------------- -http_req is the basis for all other http_* methodes and makes and HTTP request and returns an instance of the http_response structure. +http_request_exec is the basis for all other http_* methodes and makes and HTTP request and returns an instance of the http_response structure. The prototype for this function is: - struct http_response* http_req(char *http_headers, struct parsed_url *purl) + struct http_response* http_request_exec(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); + struct http_response *hrep = http_request_exec("GET / HTTP/1.1\r\nHostname:www.google.com\r\nConnection:close\r\n\r\n", purl); -Please note that http_req does not handle redirects. (Status code 300-399) +Please note that http_request_exec does not handle redirects. (Status code 300-399) http_get() ------------- -Makes an HTTP GET request to the specified URL. This function makes use of the http_req function. It specifies +Makes an HTTP GET request to the specified URL. This function makes use of the http_request_exec function. It specifies the minimal headers required, in the second parameter you can specify extra headers. The prototype for this function is: @@ -88,7 +88,7 @@ http_get does handle redirects automaticly. The basic headers used in this metho http_post ------------ -Makes an HTTP POST request to the specified URL. This function makes use of the http_req function. It specifies +Makes an HTTP POST request to the specified URL. This function makes use of the http_request_exec 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. diff --git a/src/base64.h b/src/base64.h index 1e43acc..2ed5eca 100644 --- a/src/base64.h +++ b/src/base64.h @@ -4,18 +4,20 @@ #ifndef BASE64_DEF_H #define BASE64_DEF_H -char* base64_encode(const unsigned char *in, size_t size); +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 }; +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() @@ -29,8 +31,7 @@ void b64_generate_decode_table() } } */ -size_t b64_encoded_size(size_t inlen) -{ +size_t b64_encoded_size(size_t inlen) { size_t ret; ret = inlen; @@ -42,8 +43,7 @@ size_t b64_encoded_size(size_t inlen) return ret; } -size_t b64_decoded_size(const char *in) -{ +size_t b64_decoded_size(const char *in) { size_t len; size_t ret; size_t i; @@ -54,7 +54,7 @@ size_t b64_decoded_size(const char *in) len = strlen(in); ret = len / 4 * 3; - for (i=len; i-->0; ) { + for (i = len; i-- > 0;) { if (in[i] == '=') { ret--; } else { @@ -65,8 +65,7 @@ size_t b64_decoded_size(const char *in) return ret; } -int b64_isvalidchar(char c) -{ +int b64_isvalidchar(char c) { if (c >= '0' && c <= '9') return 1; if (c >= 'A' && c <= 'Z') @@ -81,51 +80,49 @@ int b64_isvalidchar(char c) /* 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; +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 = malloc(elen + 1); out[elen] = '\0'; - for (i=0, j=0; i> 18) & 0x3F]; - out[j+1] = b64chars[(v >> 12) & 0x3F]; - if (i+1 < len) { - out[j+2] = b64chars[(v >> 6) & 0x3F]; + 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] = '='; + out[j + 2] = '='; } - if (i+2 < len) { - out[j+3] = b64chars[v & 0x3F]; + if (i + 2 < len) { + out[j + 3] = b64chars[v & 0x3F]; } else { - out[j+3] = '='; + out[j + 3] = '='; } } return out; } -char *base64_decode(const char *in, size_t *size) -{ +char *base64_decode(const char *in, size_t *size) { size_t len; size_t i; size_t j; - int v; + int v; size_t outlen = b64_decoded_size(in); - char *out = (char *) malloc(sizeof (char) * outlen + 1); + char *out = (char *) malloc(sizeof(char) * outlen + 1); char *outpstr = out; memset(out, 0, outlen); @@ -137,30 +134,30 @@ char *base64_decode(const char *in, size_t *size) if (outlen < b64_decoded_size(in) || len % 4 != 0) return 0; - for (i=0; i> 16) & 0xFF; outpstr = &out[j]; - if (in[i+2] != '=') { + if (in[i + 2] != '=') { - out[j+1] = (v >> 8) & 0xFF; - outpstr = &out[j+1]; + out[j + 1] = (v >> 8) & 0xFF; + outpstr = &out[j + 1]; } - if (in[i+3] != '=') { + if (in[i + 3] != '=') { - out[j+2] = v & 0xFF; - outpstr = &out[j+2]; + out[j + 2] = v & 0xFF; + outpstr = &out[j + 2]; } } diff --git a/src/http-client-c.h b/src/http-client-c.h index 711f381..5c4a32c 100644 --- a/src/http-client-c.h +++ b/src/http-client-c.h @@ -30,619 +30,605 @@ #include #include #include +#include +//#include +#include +#include #include "base64.h" +#include "urlparser.h" +#include "http-client-header.h" +#include "http-client-struct.h" + #ifdef _WIN32 - #include - #include - #include - #pragma comment(lib, "Ws2_32.lib") +#include +#include +#include +#pragma comment(lib, "Ws2_32.lib") #elif __linux__ - #include + +#include + #elif __FreeBSD__ - #include - #include - #include - #include +#include +#include +#include +#include #else - #error Platform not suppoted. +#error Platform not suppoted. #endif -#include -#include -#include "stringx.h" -#include "urlparser.h" -/* - Prototype functions -*/ -struct http_response *http_req(char *http_headers, struct parsed_url *purl, unsigned long i); -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, size_t i); +//typedef int (*http_header_cb_ptr)(const char*, int); +struct http_response *http_request_exec(struct http_request *hreq); -/* - Represents an HTTP html response -*/ -struct http_response -{ - struct parsed_url *request_uri; - char *body; - size_t body_len; - char *status_code; - int status_code_int; - char *status_text; - char *request_headers; - char *response_headers; -}; +char *http_request_serialize(struct http_header *headers, const char *method, struct parsed_url *purl, char *body, size_t body_len, size_t *len); -/* - 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; - } -} +int http_request_send(struct http_response *hresp, char *request, size_t request_len, char *host, char *port, http_header_cb_ptr *header_cb, http_response_body_cb_ptr *body_cb); -/* - 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; - } +//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, size_t i); + +struct http_request *http_request_new() { + + struct http_request *hreq = (struct http_request *) calloc(sizeof(struct http_request), 1); + + if (hreq == NULL) { + + return NULL; + } + +// memset(hreq, 0, sizeof (struct http_request)); + +// hreq->body_len = 0; + hreq->max_redirect = HTTP_CLIENT_C_HTTP_MAX_REDIRECT; + return hreq; } -/* - Handles redirect if needed for post requests -*/ -struct http_response* handle_redirect_post(struct http_response* hresp, char* custom_headers, char *post_data, size_t len) -{ - 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, len); - } - token = strtok(NULL, "\r\n"); - } - } - else - { - /* We're not dealing with a redirect, just return the same structure */ - return hresp; - } +struct http_response *http_response_new() { + + struct http_response *hresp = (struct http_response *) calloc(sizeof(struct http_response), 1); + + if (hresp == NULL) { + + return NULL; + } + +// memset(hresp, 0, sizeof (struct http_response)); +// hresp->body_len = 0; + + return hresp; } +void http_request_option(struct http_request *hreq, http_option option, const void *val, int len); + +void http_response_free(struct http_response *hresp); + + /* Makes a HTTP request and returns the response */ -struct http_response* http_req(char *http_headers, struct parsed_url *purl, size_t header_length) -{ - /* 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->body_len = 0; - 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) - { +struct http_response *http_request_exec(struct http_request *hreq) { + + const char *request_uri = hreq->request_uri; + + if (hreq->max_redirect == 0) { + + hreq->max_redirect = HTTP_CLIENT_C_HTTP_MAX_REDIRECT; + } + + if (hreq->method == NULL) { + + hreq->method = "GET"; + } + + if (request_uri == NULL) { + + return NULL; + } + + struct parsed_url *purl = parse_url(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_header_set(hreq, "Content-Length", body_len); + + if(!http_header_exists(hreq->headers, "Content-Type")) { + + http_header_set(hreq, "Content-Type", "application/x-www-form-urlencoded"); + } + } + + if(!http_header_exists(hreq->headers, "User-Agent")) { + + http_header_set(hreq, "User-Agent", "http-client-c"); + } + + http_header_set(hreq, "Connection", "close"); + + struct http_response *hresp = http_response_new(); + + if (hresp == NULL) { + + printf("Unable to allocate memory for HTTP response."); +// free(request); + parsed_url_free(purl); + return NULL; + } + + size_t request_len = 0; + struct http_header *headers = http_header_clone(hreq->headers); + char *request = http_request_serialize(headers, hreq->method, purl, hreq->body, hreq->body_len, &request_len); + + free(headers); + + do { + + if (request_len < 0) { + + free(request); + parsed_url_free(purl); + return NULL; + } + + printf("request:\n'%s'\n", request); + + int response_len = http_request_send(hresp, request, request_len, purl->ip, purl->port, hreq->response_header_cb, hreq->response_body_cb); + + if (response_len < 0) { + + free(request); + parsed_url_free(purl); + http_response_free(hresp); + return NULL; + } + + if (strcasecmp(hreq->method, "OPTIONS") == 0) { + + return hresp; + } + + if (response_len < 0 || hresp->status_code < 300 || hresp->status_code > 399 || hreq->max_redirect == hresp->redirect_count) { + break; + } + +// break; + hresp->redirect_count++; + + struct http_header *location = http_header_get(hresp->headers, "Location"); + + purl = parse_url(location->value); + http_header_free(hresp->headers); + + free(request); + + // 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); + + + } while (hreq->max_redirect > hresp->redirect_count); + + parsed_url_free(purl); + + /* Return response */ + return hresp; +} + +char *http_request_serialize(struct http_header *headers, const char *method, struct parsed_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); + + struct http_header *auth = http_header_get(headers, "Authorization"); + + if (auth == NULL) { + + auth = http_header_new(); + http_header_set_name(auth, "Authorization"); + + struct 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; + + size_t i = strlen(method); + char _method[i + 1]; + + _method[i] = '\0'; + + while (i--) { + + _method[i] = (char) toupper((int) method[i]); + } + + buf_len = snprintf(NULL, 0, "%s /%s%s%s HTTP/1.1\r\nHost:%s\r\n", _method, purl->path, purl->query == NULL ? "" : "?", purl->query == NULL ? "" : purl->query, purl->host); +// printf("buff: %p\n", buff); + + sprintf(&buff[pos], "%s /%s%s%s HTTP/1.1\r\nHost:%s\r\n", _method, purl->path, purl->query == NULL ? "" : "?", purl->query == NULL ? "" : purl->query, purl->host); + pos += buf_len; + + struct 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; +} + +int http_request_send(struct http_response *hresp, char *request, size_t request_len, char *host, char *port, http_header_cb_ptr *header_cb, http_response_body_cb_ptr *body_cb) { + + + /* Declare variable */ + int sock; + size_t tmpres; + struct sockaddr_in *remote; + + /* Create TCP socket */ + if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { + + printf("Can't create TCP socket"); + return -1; + } + + /* 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, host, (void *) (&(remote->sin_addr.s_addr))); + + if (tmpres < 0) { + + printf("Can't set remote->sin_addr.s_addr"); free(remote); - printf("Can't set remote->sin_addr.s_addr"); - return NULL; - } - else if(tmpres == 0) - { + return -1; + } + else if (tmpres == 0) { + + printf("Not a valid IP"); free(remote); - printf("Not a valid IP"); - return NULL; - } + return -1; + } - remote->sin_port = htons(atoi(purl->port)); + remote->sin_port = htons(atoi((const char *) port)); - /* Connect */ - if(connect(sock, (struct sockaddr *)remote, sizeof(struct sockaddr)) < 0) - { + /* Connect */ + if (connect(sock, (struct sockaddr *) remote, sizeof(struct sockaddr)) < 0) { free(remote); - printf("Could not connect"); - return NULL; - } - - /* Send headers to server */ - int sent = 0; - while(sent < header_length) - { - tmpres = send(sock, &http_headers[sent], header_length-sent, 0); - if(tmpres == -1) - { + printf("Could not connect"); + return -1; + } + + /* Send headers to server */ + size_t sent = 0; + while (sent < request_len) { + tmpres = send(sock, &request[sent], request_len - sent, 0); + if (tmpres == -1) { free(remote); - printf("Can't send headers"); - return NULL; - } - sent += tmpres; - } - - /* Recieve into response*/ - char *response = (char*)malloc(0); - char BUF[BUFSIZ]; - size_t received_len = 0; + printf("Can't send headers"); + return -1; + } + sent += tmpres; + } + + /* Receive into response*/ +// char *response = (char *) malloc(0); + char BUF[BUFSIZ]; + size_t received_len = 0; + size_t body_len = 0; size_t response_len = 0; - while((received_len = recv(sock, BUF, BUFSIZ - 1, 0)) > 0) - { + bool headers_parsed = 0; + while ((received_len = recv(sock, BUF, BUFSIZ - 1, 0)) > 0) { + BUF[received_len] = '\0'; - response = (char*)realloc(response, response_len + received_len + 1); - memcpy(&response[response_len], BUF, received_len); + +// printf("BUFF:\n'%s'\n", BUF); + +// response = (char *) realloc(response, response_len + received_len + 1); +// memcpy(&response[response_len], BUF, received_len); response_len += received_len; - response[response_len] = '\0'; - } +// response[response_len] = '\0'; - if (received_len < 0) - { - free(remote); - free(response); - free(http_headers); - #ifdef _WIN32 - closesocket(sock); - #else - close(sock); - #endif - fprintf(stderr,"Unable to receive"); - return NULL; - } + if (!headers_parsed) { - /* Reallocate response */ -// response = (char*)realloc(response, strlen(response) + 1); + char *body_end = strstr(BUF, "\r\n\r\n"); + + if (body_end != NULL) { + + char *first_line = strstr(BUF, "\r\n"); + + if (first_line != NULL) { - /* Close socket */ - #ifdef _WIN32 - closesocket(sock); - #else - close(sock); - #endif + size_t status_len = first_line - BUF; + char status_line[status_len]; - /* Parse status code and text */ - char *status = get_until(response, "\r\n"); - char *status_line = str_replace("HTTP/1.1 ", "", status); + memcpy(status_line, BUF, status_len - 1); + status_line[status_len] = '\0'; - free(status); + char *status_text = strstr(status_line, " "); - status = strndup(status_line, 4); - char *status_code = str_replace(" ", "", status); + hresp->status_code = atoi(status_text); + hresp->status_text = (char *) malloc(sizeof status_text); - free(status); + memcpy(hresp->status_text, &status_text[1], strlen(status_text)); + } - status = str_replace(status_code, "", status_line); - char *status_text = str_replace(" ", "", status); + headers_parsed = true; + size_t headers_len = 0; + hresp->headers = http_header_parse(BUF, &headers_len); - free(status); - free(status_line); + if (header_cb != NULL) { - hresp->status_code = status_code; - hresp->status_code_int = atoi(status_code); - hresp->status_text = status_text; + header_cb(hresp->headers); + } + + if (hresp->status_code > 299 && hresp->status_code < 400) { + + fprintf(stderr, "R%d ignoring response body\n", hresp->status_code); + + free(remote); + +#ifdef _WIN32 + closesocket(sock); +#else + close(sock); +#endif + return 0; + } + if (body_cb != NULL) { - /* Parse response headers */ - char *headers = get_until(response, "\r\n\r\n"); - hresp->response_headers = headers; +// body_end += 4; +// printf("Body:\n'%s'\nBUF:\n'%s'\n", body_end, BUF); - /* Assign request headers */ - hresp->request_headers = http_headers; + body_len = received_len - (body_end - BUF) - 4; + body_cb(&body_end[4], body_len, hresp->headers, 0); + } + } + } - /* Assign request url */ - hresp->request_uri = purl; + else { - /* Parse body */ - hresp->body_len = response_len - strlen(headers) - 4; + if (body_cb != NULL) { + + body_len += received_len; + body_cb(BUF, received_len, hresp->headers, 0); + } + } + } + + if (received_len < 0) { + + free(remote); +// free(response); +#ifdef _WIN32 + closesocket(sock); +#else + close(sock); +#endif + fprintf(stderr, "Unable to receive"); + return -1; + } + + if (body_cb != NULL) { + + body_cb(NULL, 0, (struct http_header *) hresp->headers, 1); + } + + hresp->body_len = body_len; + + /* 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 = get_until(response, "\r\n"); +// char *status_line = str_replace("HTTP/1.1 ", "", status); +// +// free(status); +// +// status = strndup(status_line, 4); +// char *status_code = str_replace(" ", "", status); +// +// free(status); +// +// status = str_replace(status_code, "", status_line); +// char *status_text = str_replace(" ", "", status); +// +// free(status); +// free(status_line); +// +// hresp->status_code = status_code; +// hresp->status_code = 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->redirect_ui = purl; + + /* Parse body */ +// hresp->body_len = response_len - strlen(headers) - 4; // char *body = strstr(response, "\r\n\r\n"); // body = str_replace("\r\n\r\n", "", body); - hresp->body = malloc(hresp->body_len + 1); - memcpy(hresp->body, &response[strlen(headers) + 4], hresp->body_len); +// hresp->body = malloc(hresp->body_len + 1); +// memcpy(hresp->body, &response[strlen(headers) + 4], hresp->body_len); free(remote); - free(response); - /* Return response */ - return hresp; + return response_len; } -/* - 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); - memset(http_headers, 0, 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, strlen(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) - { - memcpy(&http_headers[strlen(http_headers)], "\r\n", 2); - memcpy(&http_headers[strlen(http_headers)], custom_headers, strlen(custom_headers)); - } - else - { - memcpy(&http_headers[strlen(http_headers)], "\r\n", 2); - } - - http_headers = (char*)realloc(http_headers, strlen(http_headers) + 1); - - /* Make request and return response */ - struct http_response *hresp = http_req(http_headers, purl, strlen(http_headers)); - - /* Handle redirect */ - return handle_redirect_get(hresp, custom_headers); -} /* - Makes a HTTP POST request to the given url + Free memory of http_response */ -struct http_response* http_post(char *url, char *custom_headers, char *post_data, size_t len) -{ - /* 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, len); - } - 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, len); - } - } - 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, len); - } - 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, len); - } - } - - /* 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, strlen(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); - } - - size_t header_length = strlen(http_headers) + len; - http_headers = (char*)realloc(http_headers, header_length + 1); - - memcpy(&http_headers[strlen(http_headers)], post_data, len); - -// http_headers = (char*)realloc(http_headers, strlen(http_headers) + 1); - - /* Make request and return response */ - struct http_response *hresp = http_req(http_headers, purl, header_length); - - /* Handle redirect */ - return handle_redirect_post(hresp, custom_headers, post_data, len); -} +void http_request_free(struct http_request *hreq) { -/* - 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, strlen(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, 0); - - /* Handle redirect */ - return handle_redirect_head(hresp, custom_headers); + if (hreq != NULL) { + + if (hreq->headers != NULL) { + + http_header_free(hreq->headers); + } + + free(hreq); + } } -/* - 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, strlen(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, 0); - - /* Handle redirect */ - return hresp; +void http_response_free(struct http_response *hresp) { + + if (hresp != NULL) { + + + if (hresp->status_text != NULL) { + + free(hresp->status_text); + } + if (hresp->headers != NULL) { + + http_header_free(hresp->headers); + } + + free(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); - } +void http_request_option(struct http_request *hreq, http_option option, const void *val, int len) { + + struct http_header *header; + + switch (option) { + + case HTTP_OPTION_URL: + + hreq->request_uri = (char *) val; + break; + + case HTTP_OPTION_HEADER: + + header = (struct http_header *) val; + http_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_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 = (char *) val; + break; + } } diff --git a/src/http-client-header.h b/src/http-client-header.h new file mode 100644 index 0000000..64d444d --- /dev/null +++ b/src/http-client-header.h @@ -0,0 +1,365 @@ +// +// Created by tbela on 2022-04-08. +// + +#ifndef HTTP_CLIENT_C_HTTP_CLIENT_HEADER_H +#define HTTP_CLIENT_C_HTTP_CLIENT_HEADER_H + +#include "http-client-struct.h" +//#include "http-client-c.h" + +struct http_header *http_header_new(); + +struct http_header *http_header_clone(struct http_header *); + +int http_header_exists(struct http_header *header, const char *name); + +struct http_header *http_header_get(struct http_header *header, const char *name); + +void http_header_set(struct http_request *hreq, const char *name, const char *value); + +void http_header_add(struct http_request *hreq, const char *name, const char *value); + +void http_header_unset(struct http_request *hreq, const char *name); + +void http_header_set_name(struct http_header *header, char *name); + +void http_header_set_value(struct http_header *header, char *value); + +//char *http_headers_print(const struct http_header *header); + +struct http_header *http_header_parse(const char *http_headers, size_t *len); + +void http_header_free(struct http_header *header); + +// def + +struct http_header *http_header_new() { + + struct http_header *header = (struct http_header *) calloc(sizeof(struct http_header), 1); +// memset(header, 0, sizeof(struct http_header)); + + return header; +} + +struct http_header *http_header_clone(struct http_header *headers) { + + if (headers == NULL) { + + return NULL; + } + + struct http_header *header = http_header_new(); + struct http_header *next = header; + struct 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; +} + +int http_header_exists(struct http_header *header, const char *name) { + + struct http_header *head = header; + + while (head != NULL) { + + if (strcasecmp(head->name, name) == 0) { + + return 1; + } + + head = head->next; + } + + return 0; +} + +struct http_header *http_header_get(struct http_header *header, const char *name) { + + struct http_header *head = header; + struct http_header *result = NULL; + struct http_header *tmp, *tmp2; + + size_t len; + + while (head != NULL) { + + if (strcasecmp(head->name, name) == 0) { + + if (result == NULL) { + + result = http_header_new(); + + http_header_set_name(result, head->name); + http_header_set_value(result, head->value); + + tmp = result; + } else { + + tmp2 = http_header_new(); + + http_header_set_name(tmp2, head->name); + http_header_set_value(tmp2, head->value); + + tmp->next = tmp2; + tmp = tmp2; + } + } + + head = head->next; + } + + return result; +} + +void http_header_set(struct http_request *hreq, const char *name, const char *value) { + + struct 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_header_add(struct http_request *hreq, const char *name, const char *value) { + + struct 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_header_unset(struct http_request *hreq, const char *name) { + + struct http_header *head = hreq->headers; + struct 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; + } +} + +void http_header_set_name(struct http_header *header, char *name) { + + if (header->name != NULL) { + + free(header->name); + } + + size_t len = strlen(name); + header->name = malloc(len + 1); + + memcpy(header->name, name, len); + header->name[len] = '\0'; +} + +void http_header_set_value(struct http_header *header, char *value) { + + if (header->value != NULL) { + + free(header->value); + } + + size_t len = strlen(value); + header->value = malloc(len + 1); + + memcpy(header->value, value, len); + header->value[len] = '\0'; +} + +/** + * result must be freed + * @param header + * @return + */ +//char *http_headers_print(const struct http_header *header) { +// +// struct http_header *head = (struct http_header *) header; +// size_t len = 0; +// size_t pos = 0; +// +// char *buff = (char *) malloc(sizeof(char)); +// +// while (head != NULL) { +// +// len = snprintf(NULL, 0, "%s: %s\r\n", head->name, head->value); +// buff = (char *) realloc(buff, pos + len + 1); +// +// sprintf(&buff[pos], "%s: %s\r\n", head->name, head->value); +// +// pos += len; +// head = head->next; +// } +// +// buff[pos] = '\0'; +// return buff; +//} + +struct http_header *http_header_parse(const char *http_headers, size_t *len) { + + struct http_header *_hd = NULL; + struct 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 + 1; + + char value[val_len + 1]; + + memset(value, 0, val_len); + memcpy(value, &token[_len], val_len); + + value[val_len + 1] = '\0'; + + http_header_set_value(_next, (char *) value); + } + + token = strtok(NULL, delim); + } + + return _hd; +} + +void http_header_free(struct 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); + } + + free(header); + } +} + +#endif //HTTP_CLIENT_C_HTTP_CLIENT_HEADER_H diff --git a/src/http-client-struct.h b/src/http-client-struct.h new file mode 100644 index 0000000..7c79916 --- /dev/null +++ b/src/http-client-struct.h @@ -0,0 +1,62 @@ +// +// Created by tbela on 2022-04-08. +// + +#ifndef HTTP_CLIENT_C_HTTP_CLIENT_STRUCT_H +#define HTTP_CLIENT_C_HTTP_CLIENT_STRUCT_H + +#include "stdbool.h" + +#define HTTP_CLIENT_C_HTTP_MAX_REDIRECT 10; + +typedef void (http_header_cb_ptr)(struct http_header *); +typedef void (http_response_body_cb_ptr)(const char*, size_t, struct http_header *, int); + +typedef enum { + HTTP_OPTION_URL, + HTTP_OPTION_HEADER, + HTTP_OPTION_BODY, + HTTP_OPTION_METHOD, + HTTP_OPTION_REQUEST_HEADER_CALLBACK, + HTTP_OPTION_RESPONSE_HEADER_CALLBACK, + HTTP_OPTION_RESPONSE_BODY_CALLBACK +} http_option; + +typedef struct http_response http_response; + +struct http_header { + + char *name; + char *value; + struct http_header *next; +}; + +struct http_request { +// http_response *response; + char *request_uri; + int max_redirect; + char *method; + char *body; + size_t body_len; + struct http_header *headers; + http_header_cb_ptr *request_header_cb; + http_header_cb_ptr *response_header_cb; + http_response_body_cb_ptr *response_body_cb; +}; + +/* + Represents an HTTP html response +*/ +struct http_response { + char *redirect_ui; +// char *body; + char *redirect_uri; + size_t body_len; + bool redirected; + int redirect_count; + int status_code; + char *status_text; + struct http_header *headers; +}; + +#endif //HTTP_CLIENT_C_HTTP_CLIENT_STRUCT_H diff --git a/src/stringx.h b/src/stringx.h deleted file mode 100644 index 47870b0..0000000 --- a/src/stringx.h +++ /dev/null @@ -1,203 +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 -*/ - -#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; -} - -/* - 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; - } -} - -/* - 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; -} - -/* - Converts an integer value to its hex character -*/ -char to_hex(char code) -{ - static char hex[] = "0123456789abcdef"; - return hex[code & 15]; -} - -/* - 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; -} - -/* - 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'; -// } -// 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; -} - -/* - 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 ); - memset(new_subject, 0, 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; -} - -/* - Get's all characters until '*until' has been found -*/ -char* get_until(char *haystack, char *until) -{ - int offset = str_index_of(haystack, until); - return strndup(haystack, offset); -} - - -/* 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)); -} - -/* 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)); -} - diff --git a/src/urlparser.h b/src/urlparser.h index f5ada6e..2845d9f 100644 --- a/src/urlparser.h +++ b/src/urlparser.h @@ -28,15 +28,17 @@ #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 */ +struct parsed_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 */ @@ -48,18 +50,16 @@ struct parsed_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 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); free(purl); } } @@ -67,23 +67,20 @@ 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; } @@ -93,10 +90,9 @@ int is_scheme_char(int c) 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 *parse_url(const char *url) { + + /* Define variable */ struct parsed_url *purl; const char *tmpstr; const char *curstr; @@ -106,10 +102,9 @@ struct parsed_url *parse_url(const char *url) int bracket_flag; /* Allocate the parsed url storage */ - purl = (struct parsed_url*)malloc(sizeof(struct parsed_url)); - memset(purl, 0, sizeof (struct parsed_url)); - if ( NULL == purl ) - { + purl = (struct parsed_url *) malloc(sizeof(struct parsed_url)); + memset(purl, 0, sizeof(struct parsed_url)); + if (NULL == purl) { return NULL; } purl->scheme = NULL; @@ -129,10 +124,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) { + parsed_url_free(purl); + fprintf(stderr, "Error on line %d (%s)\n", __LINE__, __FILE__); + return NULL; } @@ -140,30 +135,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__); + parsed_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) { + parsed_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]); } @@ -176,11 +169,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) { + parsed_url_free(purl); + fprintf(stderr, "Error on line %d (%s)\n", __LINE__, __FILE__); return NULL; } curstr++; @@ -189,16 +181,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; @@ -208,199 +196,183 @@ 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) { + parsed_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) { + parsed_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) { + parsed_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) { + parsed_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) { + parsed_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 */ + char *ip = hostname_to_ip(purl->host); + purl->ip = ip; + + /* 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) { + parsed_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) { + parsed_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) { + parsed_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) { + parsed_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 index 219b588..b21735e 100644 --- a/test/main.c +++ b/test/main.c @@ -3,55 +3,109 @@ // #include "http-client-c.h" -int main(int argc, char *argv[]) { +FILE *fp; - if (argc <= 2) { +void respone_header_cb(struct http_header *headers) { - fprintf(stderr, "Usage: \n$ %s URL DEST_FILE\n", argv[0]); - exit(1); + struct http_header *header = headers; + + fprintf(stderr, "headers received:\n"); + + while (header != NULL) { + + fprintf(stderr, "header -> '%s: %s'\n", header->name, header->value); + header = header->next; } +} - char *filename = argc > 2 ? argv[2] : ""; - fprintf(stderr, "opening %s ...\n", filename); +void response_body_cb(const char *chunk, size_t chunk_len, struct http_header *headers, int stop) { + + if (chunk_len > 0) { - char *custom_headers = "User-Agent: Old-Brice\r\n"; - struct http_response *response = http_get(argv[1], custom_headers); + fprintf(stderr, "writing %lu bytes of data:\n", chunk_len); + fwrite(chunk, 1, chunk_len, fp); - if (response) { + struct http_header *content_type = http_header_get(headers, "Content-Type"); - fprintf(stderr, "request headers: [\n%s\n]\nresponse headers [\n%s\n]\n", response->request_headers, - response->response_headers); + // if text content, dump to stderr + if (content_type != NULL && strstr(content_type->value, "text/") != NULL) { + + fwrite(chunk, 1, chunk_len, stderr); + } } +} + +int main(int argc, char *argv[]) { - if (response == NULL || response->status_code_int != 200) { + if (argc <= 2) { - fprintf(stderr, "request failed with status code #%d\n", response->status_code_int); - http_response_free(response); + fprintf(fp, "Usage: \n$ %s URL DEST_FILE\n", argv[0]); exit(1); } - if (response->body_len > 0) { + char *filename = argc > 2 ? argv[2] : ""; + fprintf(stderr, "opening %s ...\n", filename); - FILE *fp = fopen(filename, "wb"); + struct http_request *request = http_request_new(); - if (fp == NULL) { +// printf("url: %s\n", argv[1]); - fprintf(stderr, "cannot open file for writing: %s\n", filename); - http_response_free(response); - exit(1); - } + http_request_option(request, HTTP_OPTION_URL, argv[1], 0); +// http_request_option(request, HTTP_OPTION_METHOD, "POST", 0); +// http_request_option(request, HTTP_OPTION_BODY, "a=1&b=2", 0); + http_request_option(request, HTTP_OPTION_RESPONSE_HEADER_CALLBACK, respone_header_cb, 0); + http_request_option(request, HTTP_OPTION_RESPONSE_BODY_CALLBACK, response_body_cb, 0); +// http_header_set(request, "User-Agent", "IRON BOY"); +// http_header_set(request, "Authorization", "Bearer "); + http_header_add(request, "Authorization", "Bearer afeb5dv86"); - if (fwrite(response->body, 1, response->body_len, fp) != response->body_len) { + fp = fopen(filename, "wb"); - fprintf(stderr, "failed to write data into file: %s\n", filename); - http_response_free(response); - fclose(fp); - exit(1); - } + struct http_response *response = http_request_exec(request); - http_response_free(response); - fclose(fp); - } + fclose(fp); + + http_request_free(request); + http_response_free(response); + + +// struct http_response *response = http_get(argv[1], custom_headers); +// +// if (response) { +// +// fprintf(stderr, "request headers: [\n%s\n]\nresponse headers [\n%s\n]\n", response->request_headers, +// response->response_headers); +// } +// +// if (response == NULL || response->status_code != 200) { +// +// fprintf(stderr, "request failed with status code #%d\n", response->status_code); +// http_response_free(response); +// exit(1); +// } +// +// if (response->body_len > 0) { +// +// fp = fopen(filename, "wb"); +// +// if (fp == NULL) { +// +// fprintf(stderr, "cannot open file for writing: %s\n", filename); +// http_response_free(response); +// exit(1); +// } +// +// if (fwrite(response->body, 1, response->body_len, fp) != response->body_len) { +// +// fprintf(stderr, "failed to write data into file: %s\n", filename); +// http_response_free(response); +// fclose(fp); +// exit(1); +// } +// +// http_response_free(response); +// fclose(fp); +// } return 0; } \ No newline at end of file From 9126535c2365b5c1c6ffb9ac9242df0406f44e8d Mon Sep 17 00:00:00 2001 From: Thierry bela Date: Wed, 27 Apr 2022 18:14:13 -0400 Subject: [PATCH 06/12] fix memory leak | reduce memory footprint | huge refactoring (breaking changes) --- CMakeLists.txt | 17 +- src/encoding/chunked.h | 133 +++++++ src/encoding/decode.h | 43 ++ src/encoding/struct.h | 133 +++++++ src/error.h | 51 +++ src/http-client-c.h | 634 ------------------------------ src/http-client-header.h | 365 ----------------- src/http-client-struct.h | 62 --- src/http/client.h | 630 +++++++++++++++++++++++++++++ src/http/header.h | 240 +++++++++++ src/http/struct.h | 112 ++++++ src/stringx.h | 88 +++++ src/{urlparser.h => url/parser.h} | 14 +- test/main.c | 33 +- 14 files changed, 1467 insertions(+), 1088 deletions(-) create mode 100644 src/encoding/chunked.h create mode 100644 src/encoding/decode.h create mode 100644 src/encoding/struct.h create mode 100644 src/error.h delete mode 100644 src/http-client-c.h delete mode 100644 src/http-client-header.h delete mode 100644 src/http-client-struct.h create mode 100644 src/http/client.h create mode 100644 src/http/header.h create mode 100644 src/http/struct.h create mode 100644 src/stringx.h rename src/{urlparser.h => url/parser.h} (97%) diff --git a/CMakeLists.txt b/CMakeLists.txt index c66cc4d..b57718e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,11 +3,18 @@ project(http_client_c C) set(CMAKE_C_STANDARD 11) +include_directories(${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/src/encoding) -include_directories(src) +#file(GLOB SOURCES +# ${SOURCES}/src/encoding/* +# ) add_executable(http_client_c - src/base64.h - src/http-client-c.h - src/urlparser.h - test/main.c) +# ${CMAKE_SOURCE_DIR}/src/base64.h +# ${CMAKE_SOURCE_DIR}/src/encoding/decode.h + ${CMAKE_SOURCE_DIR}/src/http/client.h +# ${CMAKE_SOURCE_DIR}/src/url/parser.h + ${CMAKE_SOURCE_DIR}/test/main.c + ) + +target_link_libraries(http_client_c m) \ No newline at end of file diff --git a/src/encoding/chunked.h b/src/encoding/chunked.h new file mode 100644 index 0000000..d90059e --- /dev/null +++ b/src/encoding/chunked.h @@ -0,0 +1,133 @@ +#ifndef HTTP_CLIENT_C_CHUNKED_H +#define HTTP_CLIENT_C_CHUNKED_H + +#include "encoding/struct.h" +#include "error.h" +#include "stringx.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 socket, const char *buf, size_t buf_len, size_t offset, http_header *headers, + http_response_body_cb_ptr *); + +/** + * + * @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; + + while (pos++ < buf_len) { + + if (buf[pos] == '\r' && pos + 1 < buf_len && buf[pos + 1] == '\n') { + + pos++; + break; + } + } + + if (pos <= 1) { + + return HTTP_CLIENT_ERROR_OK; + } + + char chunk_size[pos]; + + memcpy(chunk_size, buf, pos - 1); + + chunk_size[pos] = '\0'; + + *offset = pos + 1; + *data_len = hex2dec(chunk_size, pos - 1); + + return HTTP_CLIENT_ERROR_OK; +} + +int http_chunked_transfer_decode(int socket, const char *buf, size_t buf_len, size_t offset, http_header *headers, + http_response_body_cb_ptr *body_cb_ptr) { + + size_t bytes_read = 0; + size_t block_size = 0; + size_t block_offset = 0; + + ssize_t received_len = buf_len; + + int status = 0; + + char data[BUF_READ]; + memcpy(data, buf, buf_len); + + while (received_len > 0) { + + block_info: + status = http_chunked_transfer_block_info(&data[offset], buf_len - offset, &block_offset, &block_size); + + if (status < 0) { + + return HTTP_CLIENT_ERROR_DATA; + } + + if (block_size == 0) { + + return 0; + } + + while (block_size > 0) { + + if (block_size > buf_len - offset) { + + bytes_read = buf_len - block_offset - offset; + body_cb_ptr(&data[offset] + block_offset, bytes_read, headers); + + block_size -= bytes_read; + + chunked_read: + received_len = recv(socket, data, BUF_READ - 1, 0); + offset = 0; + + if (received_len < 0) { + + return HTTP_CLIENT_ERROR_RECV; + } + + if (received_len == 0) { + + return HTTP_CLIENT_ERROR_OK; + } + + buf_len = received_len; + + if (received_len >= block_size) { + + body_cb_ptr(&data[offset], block_size, headers); + offset = block_size + 2; + goto block_info; + } else { + + body_cb_ptr(&data[offset], received_len, headers); + block_size -= received_len; + goto chunked_read; + } + } else { + + body_cb_ptr(&data[offset], block_size, headers); + offset += block_offset + block_size + 2; + goto block_info; + } + } + } + + return HTTP_CLIENT_ERROR_OK; +} + + +#endif + + + + + diff --git a/src/encoding/decode.h b/src/encoding/decode.h new file mode 100644 index 0000000..b861f5e --- /dev/null +++ b/src/encoding/decode.h @@ -0,0 +1,43 @@ + +#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 socket, const char *buf, size_t buf_len, size_t offset, http_header *headers, + http_response_body_cb_ptr *); + +http_client_errors http_transfer_decode(http_transfer_encoding *te, int socket, const char *buf, size_t buf_len, size_t offset, http_header *headers, + http_response_body_cb_ptr *response_cb_ptr) { + + if (te != NULL) { + + if(strcmp(te->value, "chunked") == 0) { + + return http_chunked_transfer_decode(socket, buf, buf_len, offset, headers, response_cb_ptr); + } + + return HTTP_CLIENT_ERROR_TRANSFER_ENCODING; + } + + response_cb_ptr(&buf[offset], buf_len - offset, headers); + + size_t received_len = 0; + char BUF[BUF_READ]; + + while ((received_len = recv(socket, BUF, BUF_READ - 1, 0)) > 0) { + + response_cb_ptr(BUF, received_len, headers); + } + + 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..10c3905 --- /dev/null +++ b/src/encoding/struct.h @@ -0,0 +1,133 @@ + + +#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 *) malloc(sizeof (http_transfer_encoding)); + + if (te != NULL) { + + memset(te, 0, sizeof (http_transfer_encoding)); + } + + 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); + } + + if (te->next != NULL) { + + http_transfer_encoding_free(te->next); + } + + free(te); + } +} + +#endif diff --git a/src/error.h b/src/error.h new file mode 100644 index 0000000..55939b7 --- /dev/null +++ b/src/error.h @@ -0,0 +1,51 @@ + + +#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_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"; + } + + 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 5c4a32c..0000000 --- a/src/http-client-c.h +++ /dev/null @@ -1,634 +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 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 -*/ - -//#pragma GCC diagnostic ignored "-Wwrite-strings" -#include -#include -#include -#include -#include -//#include -#include -#include -#include "base64.h" -#include "urlparser.h" -#include "http-client-header.h" -#include "http-client-struct.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 - - -//typedef int (*http_header_cb_ptr)(const char*, int); - -struct http_response *http_request_exec(struct http_request *hreq); - -char *http_request_serialize(struct http_header *headers, const char *method, struct parsed_url *purl, char *body, size_t body_len, size_t *len); - -int http_request_send(struct http_response *hresp, char *request, size_t request_len, char *host, char *port, http_header_cb_ptr *header_cb, http_response_body_cb_ptr *body_cb); - -//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, size_t i); - -struct http_request *http_request_new() { - - struct http_request *hreq = (struct http_request *) calloc(sizeof(struct http_request), 1); - - if (hreq == NULL) { - - return NULL; - } - -// memset(hreq, 0, sizeof (struct http_request)); - -// hreq->body_len = 0; - hreq->max_redirect = HTTP_CLIENT_C_HTTP_MAX_REDIRECT; - return hreq; -} - -struct http_response *http_response_new() { - - struct http_response *hresp = (struct http_response *) calloc(sizeof(struct http_response), 1); - - if (hresp == NULL) { - - return NULL; - } - -// memset(hresp, 0, sizeof (struct http_response)); -// hresp->body_len = 0; - - return hresp; -} - -void http_request_option(struct http_request *hreq, http_option option, const void *val, int len); - -void http_response_free(struct http_response *hresp); - - -/* - Makes a HTTP request and returns the response -*/ -struct http_response *http_request_exec(struct http_request *hreq) { - - const char *request_uri = hreq->request_uri; - - if (hreq->max_redirect == 0) { - - hreq->max_redirect = HTTP_CLIENT_C_HTTP_MAX_REDIRECT; - } - - if (hreq->method == NULL) { - - hreq->method = "GET"; - } - - if (request_uri == NULL) { - - return NULL; - } - - struct parsed_url *purl = parse_url(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_header_set(hreq, "Content-Length", body_len); - - if(!http_header_exists(hreq->headers, "Content-Type")) { - - http_header_set(hreq, "Content-Type", "application/x-www-form-urlencoded"); - } - } - - if(!http_header_exists(hreq->headers, "User-Agent")) { - - http_header_set(hreq, "User-Agent", "http-client-c"); - } - - http_header_set(hreq, "Connection", "close"); - - struct http_response *hresp = http_response_new(); - - if (hresp == NULL) { - - printf("Unable to allocate memory for HTTP response."); -// free(request); - parsed_url_free(purl); - return NULL; - } - - size_t request_len = 0; - struct http_header *headers = http_header_clone(hreq->headers); - char *request = http_request_serialize(headers, hreq->method, purl, hreq->body, hreq->body_len, &request_len); - - free(headers); - - do { - - if (request_len < 0) { - - free(request); - parsed_url_free(purl); - return NULL; - } - - printf("request:\n'%s'\n", request); - - int response_len = http_request_send(hresp, request, request_len, purl->ip, purl->port, hreq->response_header_cb, hreq->response_body_cb); - - if (response_len < 0) { - - free(request); - parsed_url_free(purl); - http_response_free(hresp); - return NULL; - } - - if (strcasecmp(hreq->method, "OPTIONS") == 0) { - - return hresp; - } - - if (response_len < 0 || hresp->status_code < 300 || hresp->status_code > 399 || hreq->max_redirect == hresp->redirect_count) { - break; - } - -// break; - hresp->redirect_count++; - - struct http_header *location = http_header_get(hresp->headers, "Location"); - - purl = parse_url(location->value); - http_header_free(hresp->headers); - - free(request); - - // 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); - - - } while (hreq->max_redirect > hresp->redirect_count); - - parsed_url_free(purl); - - /* Return response */ - return hresp; -} - -char *http_request_serialize(struct http_header *headers, const char *method, struct parsed_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); - - struct http_header *auth = http_header_get(headers, "Authorization"); - - if (auth == NULL) { - - auth = http_header_new(); - http_header_set_name(auth, "Authorization"); - - struct 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; - - size_t i = strlen(method); - char _method[i + 1]; - - _method[i] = '\0'; - - while (i--) { - - _method[i] = (char) toupper((int) method[i]); - } - - buf_len = snprintf(NULL, 0, "%s /%s%s%s HTTP/1.1\r\nHost:%s\r\n", _method, purl->path, purl->query == NULL ? "" : "?", purl->query == NULL ? "" : purl->query, purl->host); -// printf("buff: %p\n", buff); - - sprintf(&buff[pos], "%s /%s%s%s HTTP/1.1\r\nHost:%s\r\n", _method, purl->path, purl->query == NULL ? "" : "?", purl->query == NULL ? "" : purl->query, purl->host); - pos += buf_len; - - struct 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; -} - -int http_request_send(struct http_response *hresp, char *request, size_t request_len, char *host, char *port, http_header_cb_ptr *header_cb, http_response_body_cb_ptr *body_cb) { - - - /* Declare variable */ - int sock; - size_t tmpres; - struct sockaddr_in *remote; - - /* Create TCP socket */ - if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { - - printf("Can't create TCP socket"); - return -1; - } - - /* 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, host, (void *) (&(remote->sin_addr.s_addr))); - - if (tmpres < 0) { - - printf("Can't set remote->sin_addr.s_addr"); - free(remote); - return -1; - } - else if (tmpres == 0) { - - printf("Not a valid IP"); - free(remote); - return -1; - } - - remote->sin_port = htons(atoi((const char *) port)); - - /* Connect */ - if (connect(sock, (struct sockaddr *) remote, sizeof(struct sockaddr)) < 0) { - free(remote); - printf("Could not connect"); - return -1; - } - - /* Send headers to server */ - size_t sent = 0; - while (sent < request_len) { - tmpres = send(sock, &request[sent], request_len - sent, 0); - if (tmpres == -1) { - free(remote); - printf("Can't send headers"); - return -1; - } - sent += tmpres; - } - - /* Receive into response*/ -// char *response = (char *) malloc(0); - char BUF[BUFSIZ]; - size_t received_len = 0; - size_t body_len = 0; - size_t response_len = 0; - bool headers_parsed = 0; - while ((received_len = recv(sock, BUF, BUFSIZ - 1, 0)) > 0) { - - BUF[received_len] = '\0'; - -// printf("BUFF:\n'%s'\n", BUF); - -// response = (char *) realloc(response, response_len + received_len + 1); -// memcpy(&response[response_len], BUF, received_len); - response_len += received_len; -// response[response_len] = '\0'; - - if (!headers_parsed) { - - 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 = (char *) malloc(sizeof status_text); - - memcpy(hresp->status_text, &status_text[1], strlen(status_text)); - } - - headers_parsed = true; - size_t headers_len = 0; - hresp->headers = http_header_parse(BUF, &headers_len); - - if (header_cb != NULL) { - - header_cb(hresp->headers); - } - - if (hresp->status_code > 299 && hresp->status_code < 400) { - - fprintf(stderr, "R%d ignoring response body\n", hresp->status_code); - - free(remote); - -#ifdef _WIN32 - closesocket(sock); -#else - close(sock); -#endif - return 0; - } - - if (body_cb != NULL) { - -// body_end += 4; -// printf("Body:\n'%s'\nBUF:\n'%s'\n", body_end, BUF); - - body_len = received_len - (body_end - BUF) - 4; - body_cb(&body_end[4], body_len, hresp->headers, 0); - } - } - } - - else { - - if (body_cb != NULL) { - - body_len += received_len; - body_cb(BUF, received_len, hresp->headers, 0); - } - } - } - - if (received_len < 0) { - - free(remote); -// free(response); -#ifdef _WIN32 - closesocket(sock); -#else - close(sock); -#endif - fprintf(stderr, "Unable to receive"); - return -1; - } - - if (body_cb != NULL) { - - body_cb(NULL, 0, (struct http_header *) hresp->headers, 1); - } - - hresp->body_len = body_len; - - /* 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 = get_until(response, "\r\n"); -// char *status_line = str_replace("HTTP/1.1 ", "", status); -// -// free(status); -// -// status = strndup(status_line, 4); -// char *status_code = str_replace(" ", "", status); -// -// free(status); -// -// status = str_replace(status_code, "", status_line); -// char *status_text = str_replace(" ", "", status); -// -// free(status); -// free(status_line); -// -// hresp->status_code = status_code; -// hresp->status_code = 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->redirect_ui = purl; - - /* Parse body */ -// hresp->body_len = response_len - strlen(headers) - 4; -// char *body = strstr(response, "\r\n\r\n"); -// body = str_replace("\r\n\r\n", "", body); -// hresp->body = malloc(hresp->body_len + 1); -// memcpy(hresp->body, &response[strlen(headers) + 4], hresp->body_len); - - free(remote); - - return response_len; -} - - -/* - Free memory of http_response -*/ -void http_request_free(struct http_request *hreq) { - - if (hreq != NULL) { - - if (hreq->headers != NULL) { - - http_header_free(hreq->headers); - } - - free(hreq); - } -} - -void http_response_free(struct http_response *hresp) { - - if (hresp != NULL) { - - - if (hresp->status_text != NULL) { - - free(hresp->status_text); - } - if (hresp->headers != NULL) { - - http_header_free(hresp->headers); - } - - free(hresp); - } -} - -void http_request_option(struct http_request *hreq, http_option option, const void *val, int len) { - - struct http_header *header; - - switch (option) { - - case HTTP_OPTION_URL: - - hreq->request_uri = (char *) val; - break; - - case HTTP_OPTION_HEADER: - - header = (struct http_header *) val; - http_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_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 = (char *) val; - break; - } -} diff --git a/src/http-client-header.h b/src/http-client-header.h deleted file mode 100644 index 64d444d..0000000 --- a/src/http-client-header.h +++ /dev/null @@ -1,365 +0,0 @@ -// -// Created by tbela on 2022-04-08. -// - -#ifndef HTTP_CLIENT_C_HTTP_CLIENT_HEADER_H -#define HTTP_CLIENT_C_HTTP_CLIENT_HEADER_H - -#include "http-client-struct.h" -//#include "http-client-c.h" - -struct http_header *http_header_new(); - -struct http_header *http_header_clone(struct http_header *); - -int http_header_exists(struct http_header *header, const char *name); - -struct http_header *http_header_get(struct http_header *header, const char *name); - -void http_header_set(struct http_request *hreq, const char *name, const char *value); - -void http_header_add(struct http_request *hreq, const char *name, const char *value); - -void http_header_unset(struct http_request *hreq, const char *name); - -void http_header_set_name(struct http_header *header, char *name); - -void http_header_set_value(struct http_header *header, char *value); - -//char *http_headers_print(const struct http_header *header); - -struct http_header *http_header_parse(const char *http_headers, size_t *len); - -void http_header_free(struct http_header *header); - -// def - -struct http_header *http_header_new() { - - struct http_header *header = (struct http_header *) calloc(sizeof(struct http_header), 1); -// memset(header, 0, sizeof(struct http_header)); - - return header; -} - -struct http_header *http_header_clone(struct http_header *headers) { - - if (headers == NULL) { - - return NULL; - } - - struct http_header *header = http_header_new(); - struct http_header *next = header; - struct 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; -} - -int http_header_exists(struct http_header *header, const char *name) { - - struct http_header *head = header; - - while (head != NULL) { - - if (strcasecmp(head->name, name) == 0) { - - return 1; - } - - head = head->next; - } - - return 0; -} - -struct http_header *http_header_get(struct http_header *header, const char *name) { - - struct http_header *head = header; - struct http_header *result = NULL; - struct http_header *tmp, *tmp2; - - size_t len; - - while (head != NULL) { - - if (strcasecmp(head->name, name) == 0) { - - if (result == NULL) { - - result = http_header_new(); - - http_header_set_name(result, head->name); - http_header_set_value(result, head->value); - - tmp = result; - } else { - - tmp2 = http_header_new(); - - http_header_set_name(tmp2, head->name); - http_header_set_value(tmp2, head->value); - - tmp->next = tmp2; - tmp = tmp2; - } - } - - head = head->next; - } - - return result; -} - -void http_header_set(struct http_request *hreq, const char *name, const char *value) { - - struct 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_header_add(struct http_request *hreq, const char *name, const char *value) { - - struct 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_header_unset(struct http_request *hreq, const char *name) { - - struct http_header *head = hreq->headers; - struct 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; - } -} - -void http_header_set_name(struct http_header *header, char *name) { - - if (header->name != NULL) { - - free(header->name); - } - - size_t len = strlen(name); - header->name = malloc(len + 1); - - memcpy(header->name, name, len); - header->name[len] = '\0'; -} - -void http_header_set_value(struct http_header *header, char *value) { - - if (header->value != NULL) { - - free(header->value); - } - - size_t len = strlen(value); - header->value = malloc(len + 1); - - memcpy(header->value, value, len); - header->value[len] = '\0'; -} - -/** - * result must be freed - * @param header - * @return - */ -//char *http_headers_print(const struct http_header *header) { -// -// struct http_header *head = (struct http_header *) header; -// size_t len = 0; -// size_t pos = 0; -// -// char *buff = (char *) malloc(sizeof(char)); -// -// while (head != NULL) { -// -// len = snprintf(NULL, 0, "%s: %s\r\n", head->name, head->value); -// buff = (char *) realloc(buff, pos + len + 1); -// -// sprintf(&buff[pos], "%s: %s\r\n", head->name, head->value); -// -// pos += len; -// head = head->next; -// } -// -// buff[pos] = '\0'; -// return buff; -//} - -struct http_header *http_header_parse(const char *http_headers, size_t *len) { - - struct http_header *_hd = NULL; - struct 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 + 1; - - char value[val_len + 1]; - - memset(value, 0, val_len); - memcpy(value, &token[_len], val_len); - - value[val_len + 1] = '\0'; - - http_header_set_value(_next, (char *) value); - } - - token = strtok(NULL, delim); - } - - return _hd; -} - -void http_header_free(struct 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); - } - - free(header); - } -} - -#endif //HTTP_CLIENT_C_HTTP_CLIENT_HEADER_H diff --git a/src/http-client-struct.h b/src/http-client-struct.h deleted file mode 100644 index 7c79916..0000000 --- a/src/http-client-struct.h +++ /dev/null @@ -1,62 +0,0 @@ -// -// Created by tbela on 2022-04-08. -// - -#ifndef HTTP_CLIENT_C_HTTP_CLIENT_STRUCT_H -#define HTTP_CLIENT_C_HTTP_CLIENT_STRUCT_H - -#include "stdbool.h" - -#define HTTP_CLIENT_C_HTTP_MAX_REDIRECT 10; - -typedef void (http_header_cb_ptr)(struct http_header *); -typedef void (http_response_body_cb_ptr)(const char*, size_t, struct http_header *, int); - -typedef enum { - HTTP_OPTION_URL, - HTTP_OPTION_HEADER, - HTTP_OPTION_BODY, - HTTP_OPTION_METHOD, - HTTP_OPTION_REQUEST_HEADER_CALLBACK, - HTTP_OPTION_RESPONSE_HEADER_CALLBACK, - HTTP_OPTION_RESPONSE_BODY_CALLBACK -} http_option; - -typedef struct http_response http_response; - -struct http_header { - - char *name; - char *value; - struct http_header *next; -}; - -struct http_request { -// http_response *response; - char *request_uri; - int max_redirect; - char *method; - char *body; - size_t body_len; - struct http_header *headers; - http_header_cb_ptr *request_header_cb; - http_header_cb_ptr *response_header_cb; - http_response_body_cb_ptr *response_body_cb; -}; - -/* - Represents an HTTP html response -*/ -struct http_response { - char *redirect_ui; -// char *body; - char *redirect_uri; - size_t body_len; - bool redirected; - int redirect_count; - int status_code; - char *status_text; - struct http_header *headers; -}; - -#endif //HTTP_CLIENT_C_HTTP_CLIENT_STRUCT_H diff --git a/src/http/client.h b/src/http/client.h new file mode 100644 index 0000000..6dcd42b --- /dev/null +++ b/src/http/client.h @@ -0,0 +1,630 @@ +/* + 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/struct.h" +//#include "encoding/chunked.h" +#include "encoding/decode.h" +#include "error.h" +#include "stringx.h" +#include "struct.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 + + +//typedef int (*http_header_cb_ptr)(const char*, int); + +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, parsed_url *purl, char *body, + size_t body_len, size_t *len); + +int http_request_send(http_response *hresp, http_header *request_headers, char *request, size_t request_len, char *host, + char *port, http_header_cb_ptr *request_header_cb, + http_header_cb_ptr *response_header_cb, http_response_body_cb_ptr *body_cb); + +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 (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 (request_uri == NULL) { + + return NULL; + } + + parsed_url *purl = parse_url(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, "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."); + parsed_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 (request_len < 0) { + + free(request); + parsed_url_free(purl); + return NULL; + } + + printf("request:\n'%s'\n", request); + + http_header *request_headers = hreq->headers; + + int result = http_request_send(hresp, request_headers, request, request_len, purl->ip, purl->port, + hreq->request_header_cb, + hreq->response_header_cb, hreq->response_body_cb); + + free(request); + + if (result < 0) { + + parsed_url_free(purl); + http_response_free(hresp); + + fprintf(stderr, "error: %s\n", http_client_error(result)); + 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; + } + +// break; + hresp->redirect_count++; + http_header *location = http_header_get(hresp->headers, "Location"); + + purl = parse_url(location->value); + http_header_free(hresp->headers); + +// free(request); + + // 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); + + + } while (hreq->max_redirect > hresp->redirect_count); + + parsed_url_free(purl); + + /* Return response */ + return hresp; +} + +char *http_request_serialize(http_header *headers, const char *method, parsed_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_header *request_headers, char *request, size_t request_len, char *host, + char *port, + http_header_cb_ptr *request_header_cb, http_header_cb_ptr *response_header_cb, + http_response_body_cb_ptr *body_cb) { + + /* Declare variable */ + int sock; + http_client_errors error_reason = HTTP_CLIENT_ERROR_OK; + size_t tmpres; + struct sockaddr_in *remote; + + /* Create TCP socket */ + if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 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, host, (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 *) port)); + + /* Connect */ + if (connect(sock, (struct sockaddr *) remote, sizeof(struct sockaddr)) < 0) { + + error_reason = HTTP_CLIENT_ERROR_CONNECT; + goto exit; + } + + if (request_header_cb != NULL) { + + request_header_cb(request_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 *response = (char *) malloc(0); + char BUF[BUF_READ]; + http_transfer_encoding *te = NULL, *temp; + ssize_t received_len = 0; + size_t body_len = 0; +// size_t response_len = 0; +// uint8_t headers_parsed = 0; + char *chunk = NULL; + + if ((received_len = recv(sock, BUF, BUF_READ - 1, 0)) > 0) { + + BUF[received_len] = '\0'; +// response_len += received_len; + +// if (!headers_parsed) { + + 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 = (char *) malloc(sizeof status_text); + + memcpy(hresp->status_text, &status_text[1], strlen(status_text)); + } + +// headers_parsed = 1; + size_t headers_len = 0; + hresp->headers = http_header_parse(BUF, &headers_len); + + if (response_header_cb != NULL) { + + 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; + } + + if (body_cb != NULL) { + + http_header *teh = http_header_get(hresp->headers, "Transfer-Encoding"); + + char *data = &body_end[4]; + + body_len = received_len - (body_end - BUF) - 4; + + if (teh != NULL) { + + te = http_transfer_encoding_parse(teh->value); + } + + error_reason = http_transfer_decode(te, sock, BUF, received_len, (body_end - BUF) + 4, hresp->headers, body_cb); + +// if (error_reason == HTTP_CLIENT_ERROR_TRANSFER_ENCODING) { + +// fprintf(stderr, "unsupported transfer encoding: '%s'", te->value); +// error_reason = HTTP_CLIENT_ERROR_OK; +// } + + goto exit; + } + } +// } + } + + if (received_len < 0) { + + error_reason = HTTP_CLIENT_ERROR_RECV; + goto exit; + } + + exit: + + if (remote != NULL) { + + free(remote); + } + + if (chunk != NULL) { + + free(chunk); + } + + 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 = (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_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..bfb1fd4 --- /dev/null +++ b/src/http/header.h @@ -0,0 +1,240 @@ +// +// Created by tbela on 2022-04-08. +// + +#ifndef HTTP_CLIENT_C_HEADER_H +#define HTTP_CLIENT_C_HEADER_H + +typedef struct http_header { + + char *name; + char *value; + struct http_header *next; +} http_header; + +http_header *http_header_new(); + +http_header *http_header_clone(http_header *); + +int http_header_exists(http_header *header, const char *name); + +http_header *http_header_get(http_header *header, const char *name); + +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 *http_headers, size_t *len); + +void http_header_free(http_header *header); + +// def +http_header *http_header_new() { + + http_header *header = (http_header *) calloc(sizeof(http_header), 1); + + return header; +} + +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; +} + +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, *tmp2; + + while (head != NULL) { + + if (strcasecmp(head->name, name) == 0) { + + if (result == NULL) { + + result = http_header_new(); + + http_header_set_name(result, head->name); + http_header_set_value(result, head->value); + + tmp = result; + } else { + + tmp2 = http_header_new(); + + http_header_set_name(tmp2, head->name); + http_header_set_value(tmp2, head->value); + + tmp->next = tmp2; + tmp = tmp2; + } + } + + head = head->next; + } + + return result; +} + +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) { + + header->value = strdup(value); + } +} + +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 + 1; + + char value[val_len + 1]; + + memset(value, 0, val_len); + memcpy(value, &token[_len], val_len); + + value[val_len + 1] = '\0'; + + http_header_set_value(_next, (char *) value); + } + + token = strtok(NULL, delim); + } + + return _hd; +} + +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); + } + + free(header); + } +} + +#endif //HTTP_CLIENT_C_HEADER_H diff --git a/src/http/struct.h b/src/http/struct.h new file mode 100644 index 0000000..71fe1ba --- /dev/null +++ b/src/http/struct.h @@ -0,0 +1,112 @@ +// +// Created by tbela on 2022-04-08. +// + +#ifndef HTTP_CLIENT_C_HTTP_STRUCT_H +#define HTTP_CLIENT_C_HTTP_STRUCT_H + +#define BUF_READ 16384 +#define HTTP_CLIENT_C_HTTP_MAX_REDIRECT 10; + +#include "http/header.h" + +typedef void (http_header_cb_ptr)(http_header *); +typedef void (http_response_body_cb_ptr)(const wchar_t*, size_t, http_header *); + +typedef enum { + HTTP_OPTION_URL, + HTTP_OPTION_HEADER, + HTTP_OPTION_BODY, + HTTP_OPTION_METHOD, + HTTP_OPTION_REQUEST_HEADER_CALLBACK, + HTTP_OPTION_RESPONSE_HEADER_CALLBACK, + HTTP_OPTION_RESPONSE_BODY_CALLBACK +} http_option; + +typedef struct http_request { + char *request_uri; + int max_redirect; + char *method; + char *body; + size_t body_len; + http_header *headers; + http_header_cb_ptr *request_header_cb; + http_header_cb_ptr *response_header_cb; + http_response_body_cb_ptr *response_body_cb; +} http_request; + +/* + Represents an HTTP html response +*/ +typedef struct http_response { +// char *body; + char *redirect_uri; + size_t body_len; + uint8_t redirected; + int redirect_count; + int status_code; + char *status_text; + http_header *headers; +} 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; + } + + 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->method != NULL) { + + free(hreq->method); + } + + free(hreq); + } +} + +void http_response_free(http_response *hresp) { + + if (hresp != NULL) { + + + if (hresp->status_text != NULL) { + + free(hresp->status_text); + } + 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 new file mode 100644 index 0000000..dfa7a04 --- /dev/null +++ b/src/stringx.h @@ -0,0 +1,88 @@ + + +#ifndef HTTP_CLIENT_C_HTTP_CLIENT_HEX_H +#define HTTP_CLIENT_C_HTTP_CLIENT_HEX_H + +#include "stddef.h" +#include "string.h" +#include + +size_t hex2dec(const char *, size_t len); +void str_to_upper(char *, size_t); + +char *str2hex(unsigned char *str, size_t len) { + + char *result = malloc(2 * len + 1); + + for (size_t i = 0; i < len; ++i) { + + sprintf(&result[2 * i], "%02X", str[i]); + } + + result[2 * len] = '\0'; + return result; +} + +char *hex2str(char *str, size_t *len) { + + 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); + } + + *len = counter - 1; + result[counter] = '\0'; + return result; +} + +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; +} + +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 97% rename from src/urlparser.h rename to src/url/parser.h index 2845d9f..2419da4 100644 --- a/src/urlparser.h +++ b/src/url/parser.h @@ -34,7 +34,7 @@ /* Represents an url */ -struct parsed_url { +typedef struct parsed_url { char *uri; /* mandatory */ char *scheme; /* mandatory */ char *host; /* mandatory */ @@ -45,12 +45,12 @@ struct parsed_url { char *fragment; /* optional */ char *username; /* optional */ char *password; /* optional */ -}; +} parsed_url; /* Free memory of parsed url */ -void parsed_url_free(struct parsed_url *purl) { +void parsed_url_free(parsed_url *purl) { if (NULL != purl) { if (NULL != purl->scheme) free(purl->scheme); if (NULL != purl->host) free(purl->host); @@ -90,10 +90,10 @@ int is_scheme_char(int c) { 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) { +parsed_url *parse_url(const char *url) { /* Define variable */ - struct parsed_url *purl; + parsed_url *purl; const char *tmpstr; const char *curstr; size_t len; @@ -102,8 +102,8 @@ struct parsed_url *parse_url(const char *url) { int bracket_flag; /* Allocate the parsed url storage */ - purl = (struct parsed_url *) malloc(sizeof(struct parsed_url)); - memset(purl, 0, sizeof(struct parsed_url)); + purl = (parsed_url *) calloc(1, sizeof(parsed_url)); + if (NULL == purl) { return NULL; } diff --git a/test/main.c b/test/main.c index b21735e..f021960 100644 --- a/test/main.c +++ b/test/main.c @@ -1,13 +1,14 @@ // // Created by tbela on 2022-04-07. // -#include "http-client-c.h" +#include "http/client.h" +#include "wchar.h" FILE *fp; -void respone_header_cb(struct http_header *headers) { +void respone_header_cb(http_header *headers) { - struct http_header *header = headers; + http_header *header = headers; fprintf(stderr, "headers received:\n"); @@ -18,20 +19,22 @@ void respone_header_cb(struct http_header *headers) { } } -void response_body_cb(const char *chunk, size_t chunk_len, struct http_header *headers, int stop) { +void response_body_cb(const char *chunk, size_t chunk_len, http_header *headers) { if (chunk_len > 0) { - fprintf(stderr, "writing %lu bytes of data:\n", chunk_len); +// fprintf(stderr, "\n\nwriting %lu bytes of data:\n\n", chunk_len); fwrite(chunk, 1, chunk_len, fp); - struct http_header *content_type = http_header_get(headers, "Content-Type"); + 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, 1, chunk_len, stderr); + fwrite(chunk, chunk_len, 1, stderr); } + + http_header_free(content_type); } } @@ -46,22 +49,22 @@ int main(int argc, char *argv[]) { char *filename = argc > 2 ? argv[2] : ""; fprintf(stderr, "opening %s ...\n", filename); - struct http_request *request = http_request_new(); + http_request *request = http_request_new(); // printf("url: %s\n", argv[1]); http_request_option(request, HTTP_OPTION_URL, argv[1], 0); -// http_request_option(request, HTTP_OPTION_METHOD, "POST", 0); -// http_request_option(request, HTTP_OPTION_BODY, "a=1&b=2", 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_RESPONSE_HEADER_CALLBACK, respone_header_cb, 0); http_request_option(request, HTTP_OPTION_RESPONSE_BODY_CALLBACK, response_body_cb, 0); -// http_header_set(request, "User-Agent", "IRON BOY"); -// http_header_set(request, "Authorization", "Bearer "); - http_header_add(request, "Authorization", "Bearer afeb5dv86"); + http_request_header_set(request, "User-Agent", "FireFlox"); +// http_request_header_set(request, "Authorization", "Bearer "); + http_request_header_add(request, "Authorization", "Bearer afeb5dv86"); fp = fopen(filename, "wb"); - struct http_response *response = http_request_exec(request); + http_response *response = http_request_exec(request); fclose(fp); @@ -69,7 +72,7 @@ int main(int argc, char *argv[]) { http_response_free(response); -// struct http_response *response = http_get(argv[1], custom_headers); +// http_response *response = http_get(argv[1], custom_headers); // // if (response) { // From 265b1826ffd06331d6254a97ee050862a4b4b6ce Mon Sep 17 00:00:00 2001 From: Thierry bela Date: Wed, 27 Apr 2022 18:31:06 -0400 Subject: [PATCH 07/12] fix missing bytes --- src/error.h | 3 ++- src/http/client.h | 1 - test/main.c | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/error.h b/src/error.h index 55939b7..349adb9 100644 --- a/src/error.h +++ b/src/error.h @@ -11,7 +11,8 @@ typedef enum { HTTP_CLIENT_ERROR_HOST = -3, HTTP_CLIENT_ERROR_DATA = -4, HTTP_CLIENT_ERROR_RECV = -5, - HTTP_CLIENT_ERROR_TRANSFER_ENCODING = -6 + HTTP_CLIENT_ERROR_TRANSFER_ENCODING = -6, + HTTP_CLIENT_PROTO = -7 } http_client_errors; char *http_client_error(http_client_errors); diff --git a/src/http/client.h b/src/http/client.h index 6dcd42b..60ffd2f 100644 --- a/src/http/client.h +++ b/src/http/client.h @@ -478,7 +478,6 @@ http_client_errors http_request_send(http_response *hresp, http_header *request_ // size_t response_len = 0; // uint8_t headers_parsed = 0; char *chunk = NULL; - if ((received_len = recv(sock, BUF, BUF_READ - 1, 0)) > 0) { BUF[received_len] = '\0'; diff --git a/test/main.c b/test/main.c index f021960..060e102 100644 --- a/test/main.c +++ b/test/main.c @@ -2,11 +2,11 @@ // Created by tbela on 2022-04-07. // #include "http/client.h" -#include "wchar.h" +//#include "wchar.h" FILE *fp; -void respone_header_cb(http_header *headers) { +void response_header_cb(http_header *headers) { http_header *header = headers; @@ -56,7 +56,7 @@ int main(int argc, char *argv[]) { 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_RESPONSE_HEADER_CALLBACK, respone_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", "FireFlox"); // http_request_header_set(request, "Authorization", "Bearer "); From 1e1bca51998d88e18ace2a98f162f00967e9ac6a Mon Sep 17 00:00:00 2001 From: Thierry bela Date: Fri, 29 Apr 2022 16:14:26 -0400 Subject: [PATCH 08/12] parse http header value --- CMakeLists.txt | 5 +- src/encoding/chunked.h | 19 ++-- src/encoding/decode.h | 14 +-- src/error.h | 4 + src/http/client.h | 123 +++++++++++---------- src/http/header.h | 241 +++++++++++++++++++++++++++++++++++++++-- src/http/struct.h | 28 +++-- src/stringx.h | 21 ++++ test/main.c | 47 ++++++-- 9 files changed, 392 insertions(+), 110 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b57718e..49a974a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,7 @@ 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) @@ -10,10 +11,6 @@ include_directories(${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/src/encoding) # ) add_executable(http_client_c -# ${CMAKE_SOURCE_DIR}/src/base64.h -# ${CMAKE_SOURCE_DIR}/src/encoding/decode.h - ${CMAKE_SOURCE_DIR}/src/http/client.h -# ${CMAKE_SOURCE_DIR}/src/url/parser.h ${CMAKE_SOURCE_DIR}/test/main.c ) diff --git a/src/encoding/chunked.h b/src/encoding/chunked.h index d90059e..14ee0a5 100644 --- a/src/encoding/chunked.h +++ b/src/encoding/chunked.h @@ -6,9 +6,7 @@ #include "stringx.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 socket, const char *buf, size_t buf_len, size_t offset, http_header *headers, - http_response_body_cb_ptr *); +int http_chunked_transfer_decode(int sock, const char *buf, size_t buf_len, size_t offset, http_request *, http_response *); /** * @@ -47,10 +45,9 @@ http_client_errors http_chunked_transfer_block_info(const char *buf, long buf_le return HTTP_CLIENT_ERROR_OK; } -int http_chunked_transfer_decode(int socket, const char *buf, size_t buf_len, size_t offset, http_header *headers, - http_response_body_cb_ptr *body_cb_ptr) { +int http_chunked_transfer_decode(int sock, const char *buf, size_t buf_len, size_t offset, http_request *hreq, http_response *hresp) { - size_t bytes_read = 0; + size_t bytes_read; size_t block_size = 0; size_t block_offset = 0; @@ -81,12 +78,12 @@ int http_chunked_transfer_decode(int socket, const char *buf, size_t buf_len, si if (block_size > buf_len - offset) { bytes_read = buf_len - block_offset - offset; - body_cb_ptr(&data[offset] + block_offset, bytes_read, headers); + hreq->response_body_cb(&data[offset] + block_offset, bytes_read, hresp->headers); block_size -= bytes_read; chunked_read: - received_len = recv(socket, data, BUF_READ - 1, 0); + received_len = recv(sock, data, BUF_READ - 1, 0); offset = 0; if (received_len < 0) { @@ -103,18 +100,18 @@ int http_chunked_transfer_decode(int socket, const char *buf, size_t buf_len, si if (received_len >= block_size) { - body_cb_ptr(&data[offset], block_size, headers); + hreq->response_body_cb(&data[offset], block_size, hresp->headers); offset = block_size + 2; goto block_info; } else { - body_cb_ptr(&data[offset], received_len, headers); + hreq->response_body_cb(&data[offset], received_len, hresp->headers); block_size -= received_len; goto chunked_read; } } else { - body_cb_ptr(&data[offset], block_size, headers); + hreq->response_body_cb(&data[offset], block_size, hresp->headers); offset += block_offset + block_size + 2; goto block_info; } diff --git a/src/encoding/decode.h b/src/encoding/decode.h index b861f5e..42cb9b2 100644 --- a/src/encoding/decode.h +++ b/src/encoding/decode.h @@ -6,30 +6,28 @@ #include "encoding/chunked.h" #include "http/struct.h" -http_client_errors http_transfer_decode(http_transfer_encoding *te, int socket, const char *buf, size_t buf_len, size_t offset, http_header *headers, - http_response_body_cb_ptr *); +http_client_errors http_transfer_decode(http_transfer_encoding *te, int sock, const char *buf, size_t buf_len, size_t offset, http_request *, http_response *); -http_client_errors http_transfer_decode(http_transfer_encoding *te, int socket, const char *buf, size_t buf_len, size_t offset, http_header *headers, - http_response_body_cb_ptr *response_cb_ptr) { +http_client_errors http_transfer_decode(http_transfer_encoding *te, int sock, const 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(socket, buf, buf_len, offset, headers, response_cb_ptr); + return http_chunked_transfer_decode(sock, buf, buf_len, offset, hreq, hresp); } return HTTP_CLIENT_ERROR_TRANSFER_ENCODING; } - response_cb_ptr(&buf[offset], buf_len - offset, headers); + hreq->response_body_cb(&buf[offset], buf_len - offset, hresp->headers); size_t received_len = 0; char BUF[BUF_READ]; - while ((received_len = recv(socket, BUF, BUF_READ - 1, 0)) > 0) { + while ((received_len = recv(sock, BUF, BUF_READ - 1, 0)) > 0) { - response_cb_ptr(BUF, received_len, headers); + hreq->response_body_cb(BUF, received_len, hresp->headers); } if (received_len < 0) { diff --git a/src/error.h b/src/error.h index 349adb9..5d16d81 100644 --- a/src/error.h +++ b/src/error.h @@ -44,6 +44,10 @@ char *http_client_error(http_client_errors err) { case HTTP_CLIENT_ERROR_TRANSFER_ENCODING: return "unsupported transfer encoding"; + + case HTTP_CLIENT_PROTO: + + return "unsupported protocol"; } return ""; diff --git a/src/http/client.h b/src/http/client.h index 60ffd2f..b904219 100644 --- a/src/http/client.h +++ b/src/http/client.h @@ -39,12 +39,11 @@ #include "url/parser.h" #include "header.h" #include "http/struct.h" -//#include "encoding/struct.h" -//#include "encoding/chunked.h" #include "encoding/decode.h" #include "error.h" #include "stringx.h" #include "struct.h" +#include "iconv.h" #ifdef _WIN32 @@ -65,9 +64,6 @@ #error Platform not suppoted. #endif - -//typedef int (*http_header_cb_ptr)(const char*, int); - 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); @@ -79,9 +75,7 @@ http_response *http_request_exec(http_request *hreq); char *http_request_serialize(http_header *headers, const char *method, parsed_url *purl, char *body, size_t body_len, size_t *len); -int http_request_send(http_response *hresp, http_header *request_headers, char *request, size_t request_len, char *host, - char *port, http_header_cb_ptr *request_header_cb, - http_header_cb_ptr *response_header_cb, http_response_body_cb_ptr *body_cb); +int http_request_send(http_response *hresp, http_request *hreq, char *request, size_t request_len, parsed_url *purl); void http_request_option(http_request *hreq, http_option option, const void *val, size_t len); @@ -203,6 +197,7 @@ http_response *http_request_exec(http_request *hreq) { parsed_url *purl = parse_url(hreq->request_uri); if (purl == NULL) { + printf("Unable to parse url"); return NULL; } @@ -246,29 +241,52 @@ http_response *http_request_exec(http_request *hreq) { do { - if (request_len < 0) { + if(strcasecmp(purl->scheme, "http") != 0) { - free(request); + if (request != NULL) { + + free(request); + } + + fprintf(stderr, "error: %s: '%s'\n", http_client_error(HTTP_CLIENT_PROTO), purl->scheme); parsed_url_free(purl); return NULL; } - printf("request:\n'%s'\n", request); + if (request_len < 0) { - http_header *request_headers = hreq->headers; + free(request); + parsed_url_free(purl); - int result = http_request_send(hresp, request_headers, request, request_len, purl->ip, purl->port, - hreq->request_header_cb, - hreq->response_header_cb, hreq->response_body_cb); + 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)); + } + parsed_url_free(purl); http_response_free(hresp); - fprintf(stderr, "error: %s\n", http_client_error(result)); return NULL; } @@ -287,8 +305,6 @@ http_response *http_request_exec(http_request *hreq) { purl = parse_url(location->value); http_header_free(hresp->headers); -// free(request); - // change HTTP method? const char *method = hresp->status_code == 307 ? hreq->method : (strcasecmp(hreq->method, "GET") == 0 || strcasecmp(hreq->method, "HEAD") == 0 @@ -298,9 +314,6 @@ http_response *http_request_exec(http_request *hreq) { 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); @@ -317,6 +330,7 @@ http_response *http_request_exec(http_request *hreq) { } free(headers); + headers = NULL; } while (hreq->max_redirect > hresp->redirect_count); @@ -410,19 +424,19 @@ char *http_request_serialize(http_header *headers, const char *method, parsed_ur return buff; } -http_client_errors http_request_send(http_response *hresp, http_header *request_headers, char *request, size_t request_len, char *host, - char *port, - http_header_cb_ptr *request_header_cb, http_header_cb_ptr *response_header_cb, - http_response_body_cb_ptr *body_cb) { +http_client_errors http_request_send(http_response *hresp, http_request *hreq, char *request, size_t request_len, parsed_url *purl) { /* Declare variable */ int sock; http_client_errors error_reason = HTTP_CLIENT_ERROR_OK; size_t tmpres; - struct sockaddr_in *remote; + struct sockaddr_in *remote = NULL; /* Create TCP socket */ - if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { + if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0 + || setsockopt (sock, SOL_SOCKET, SO_RCVTIMEO, hreq->timeout, sizeof *hreq->timeout) < 0 + || setsockopt (sock, SOL_SOCKET, SO_SNDTIMEO, hreq->timeout, sizeof *hreq->timeout) < 0 + ) { error_reason = HTTP_CLIENT_ERROR_CONNECT; goto exit; @@ -431,7 +445,7 @@ http_client_errors http_request_send(http_response *hresp, http_header *request_ /* 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, host, (void *) (&(remote->sin_addr.s_addr))); + tmpres = inet_pton(AF_INET, purl->ip, (void *) (&(remote->sin_addr.s_addr))); if (tmpres < 0) { @@ -443,7 +457,7 @@ http_client_errors http_request_send(http_response *hresp, http_header *request_ goto exit; } - remote->sin_port = htons(atoi((const char *) port)); + remote->sin_port = htons(atoi((const char *) purl->port)); /* Connect */ if (connect(sock, (struct sockaddr *) remote, sizeof(struct sockaddr)) < 0) { @@ -452,9 +466,9 @@ http_client_errors http_request_send(http_response *hresp, http_header *request_ goto exit; } - if (request_header_cb != NULL) { + if (hreq->request_header_cb != NULL) { - request_header_cb(request_headers); + hreq->request_header_cb(hreq->headers); } /* Send headers to server */ @@ -470,20 +484,14 @@ http_client_errors http_request_send(http_response *hresp, http_header *request_ } /* Receive into response*/ -// char *response = (char *) malloc(0); char BUF[BUF_READ]; - http_transfer_encoding *te = NULL, *temp; - ssize_t received_len = 0; - size_t body_len = 0; -// size_t response_len = 0; -// uint8_t headers_parsed = 0; - char *chunk = NULL; + + http_transfer_encoding *te = NULL; + ssize_t received_len; + if ((received_len = recv(sock, BUF, BUF_READ - 1, 0)) > 0) { BUF[received_len] = '\0'; -// response_len += received_len; - -// if (!headers_parsed) { char *body_end = strstr(BUF, "\r\n\r\n"); @@ -507,13 +515,12 @@ http_client_errors http_request_send(http_response *hresp, http_header *request_ memcpy(hresp->status_text, &status_text[1], strlen(status_text)); } -// headers_parsed = 1; size_t headers_len = 0; hresp->headers = http_header_parse(BUF, &headers_len); - if (response_header_cb != NULL) { + if (hreq->response_header_cb != NULL) { - response_header_cb(hresp->headers); + hreq->response_header_cb(hresp->headers); } if (hresp->status_code > 299 && hresp->status_code < 400) { @@ -522,31 +529,19 @@ http_client_errors http_request_send(http_response *hresp, http_header *request_ goto exit; } - if (body_cb != NULL) { + if (hreq->response_body_cb != NULL) { http_header *teh = http_header_get(hresp->headers, "Transfer-Encoding"); - char *data = &body_end[4]; - - body_len = received_len - (body_end - BUF) - 4; - if (teh != NULL) { te = http_transfer_encoding_parse(teh->value); } - error_reason = http_transfer_decode(te, sock, BUF, received_len, (body_end - BUF) + 4, hresp->headers, body_cb); - -// if (error_reason == HTTP_CLIENT_ERROR_TRANSFER_ENCODING) { - -// fprintf(stderr, "unsupported transfer encoding: '%s'", te->value); -// error_reason = HTTP_CLIENT_ERROR_OK; -// } - + error_reason = http_transfer_decode(te, sock, BUF, received_len, (body_end - BUF) + 4, hreq, hresp); goto exit; } } -// } } if (received_len < 0) { @@ -562,10 +557,10 @@ http_client_errors http_request_send(http_response *hresp, http_header *request_ free(remote); } - if (chunk != NULL) { - - free(chunk); - } +// if (chunk != NULL) { +// +// free(chunk); +// } if (te != NULL) { @@ -608,6 +603,10 @@ void http_request_option(http_request *hreq, http_option option, const void *val hreq->body_len = len == 0 ? strlen(hreq->body) : len; break; + case HTTP_OPTION_REQUEST_TIMEOUT: + hreq->timeout->tv_sec = atol(val); + break; + case HTTP_OPTION_REQUEST_HEADER_CALLBACK: hreq->request_header_cb = (http_header_cb_ptr *) val; break; diff --git a/src/http/header.h b/src/http/header.h index bfb1fd4..37fa819 100644 --- a/src/http/header.h +++ b/src/http/header.h @@ -5,14 +5,31 @@ #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 *); @@ -24,18 +41,28 @@ 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 *http_headers, size_t *len); +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) { @@ -139,10 +166,33 @@ void http_header_set_value(http_header *header, char *value) { header->value = NULL; } - if(value != NULL) { + if (value == NULL) { - header->value = strdup(value); + 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) { @@ -188,7 +238,7 @@ http_header *http_header_parse(const char *http_headers, size_t *len) { size_t _len = strlen(token) - strlen(split); char name[_len + 1]; - memset(name, 0, _len); +// memset(name, 0, _len); memcpy(name, token, _len); name[_len] = '\0'; @@ -196,15 +246,14 @@ http_header *http_header_parse(const char *http_headers, size_t *len) { while (token[++_len] == ' '); - size_t val_len = strlen(token) - _len + 1; + size_t val_len = strlen(token) - _len; char value[val_len + 1]; - memset(value, 0, val_len); +// memset(value, 0, val_len - 1); memcpy(value, &token[_len], val_len); - value[val_len + 1] = '\0'; - + value[val_len] = '\0'; http_header_set_value(_next, (char *) value); } @@ -214,6 +263,151 @@ http_header *http_header_parse(const char *http_headers, size_t *len) { 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) { @@ -233,8 +427,37 @@ void http_header_free(http_header *header) { 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 index 71fe1ba..aa08233 100644 --- a/src/http/struct.h +++ b/src/http/struct.h @@ -5,48 +5,56 @@ #ifndef HTTP_CLIENT_C_HTTP_STRUCT_H #define HTTP_CLIENT_C_HTTP_STRUCT_H -#define BUF_READ 16384 +#define BUF_READ 8192 #define HTTP_CLIENT_C_HTTP_MAX_REDIRECT 10; #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 wchar_t*, size_t, http_header *); +typedef void (http_response_body_cb_ptr)(const 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; - char *body; - size_t body_len; - http_header *headers; http_header_cb_ptr *request_header_cb; http_header_cb_ptr *response_header_cb; http_response_body_cb_ptr *response_body_cb; + struct timeval *timeout; } http_request; /* Represents an HTTP html response */ typedef struct http_response { -// char *body; + struct http_struct; char *redirect_uri; - size_t body_len; uint8_t redirected; int redirect_count; int status_code; char *status_text; - http_header *headers; } http_response; void http_response_free(http_response *hresp); @@ -58,6 +66,9 @@ http_request *http_request_new() { if (hreq != NULL) { hreq->max_redirect = HTTP_CLIENT_C_HTTP_MAX_REDIRECT; + hreq->timeout = (struct timeval *) calloc(1, sizeof (struct timeval)); + hreq->timeout->tv_sec = 10; + hreq->timeout->tv_usec = 0; } return hreq; @@ -87,6 +98,7 @@ void http_request_free(http_request *hreq) { free(hreq->method); } + free(hreq->timeout); free(hreq); } } diff --git a/src/stringx.h b/src/stringx.h index dfa7a04..ca38497 100644 --- a/src/stringx.h +++ b/src/stringx.h @@ -39,6 +39,27 @@ char *hex2str(char *str, size_t *len) { return result; } +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); +} + size_t hex2dec(const char *hex, size_t len) { size_t i = len; diff --git a/test/main.c b/test/main.c index 060e102..16e0305 100644 --- a/test/main.c +++ b/test/main.c @@ -2,7 +2,6 @@ // Created by tbela on 2022-04-07. // #include "http/client.h" -//#include "wchar.h" FILE *fp; @@ -12,18 +11,34 @@ void response_header_cb(http_header *headers) { fprintf(stderr, "headers received:\n"); - while (header != NULL) { + char *printed = http_header_print(headers); - fprintf(stderr, "header -> '%s: %s'\n", header->name, header->value); - header = header->next; - } + fprintf(stderr, "%s\r\n", printed); + + fwrite(printed, strlen(printed), 1, fp); + fwrite("\r\n", 2, 1, fp); + free(printed); +} + +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); } void response_body_cb(const char *chunk, size_t chunk_len, http_header *headers) { if (chunk_len > 0) { -// fprintf(stderr, "\n\nwriting %lu bytes of data:\n\n", chunk_len); fwrite(chunk, 1, chunk_len, fp); http_header *content_type = http_header_get(headers, "Content-Type"); @@ -40,6 +55,18 @@ void response_body_cb(const char *chunk, size_t chunk_len, http_header *headers) int main(int argc, char *argv[]) { +// size_t header_len = 0; +// http_header *headers = http_header_parse("P3P: CP=\"This is not a P3P policy! See g.co/p3phelp for more info.\"\r\ncache-control: private, max-age=36500", &header_len); +// +// char *print = http_header_print(headers); +// +// fprintf(stderr, "%s", print); +// +// free(print); +// http_header_free(headers); +// +// return 0; + if (argc <= 2) { fprintf(fp, "Usage: \n$ %s URL DEST_FILE\n", argv[0]); @@ -56,11 +83,15 @@ int main(int argc, char *argv[]) { 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", "FireFlox"); + http_request_header_set(request, "User-Agent", "Firevox"); // http_request_header_set(request, "Authorization", "Bearer "); - http_request_header_add(request, "Authorization", "Bearer afeb5dv86"); + http_request_header_add(request, "Accept-Language", "en-US;q=0.6,en;q=0.4"); + // + // fr-CA,en-CA;q=0.8,en-US;q=0.6,en;q=0.4,fr;q=0.2 fp = fopen(filename, "wb"); From b722fe62d836a892158e82674fe996c192eb1166 Mon Sep 17 00:00:00 2001 From: Thierry bela Date: Mon, 9 May 2022 15:45:23 -0400 Subject: [PATCH 09/12] add chunked transfer support --- CMakeLists.txt | 8 +- README.md | 296 ++++++++++++++++++++++++++++++----------- src/charset/utf8.h | 97 ++++++++++++++ src/encoding/chunked.h | 193 ++++++++++++++++++++------- src/encoding/decode.h | 32 ++++- src/encoding/struct.h | 9 +- src/http/client.h | 32 ++--- src/http/header.h | 34 +++++ src/http/struct.h | 34 ++++- test/main.c | 81 +++-------- 10 files changed, 596 insertions(+), 220 deletions(-) create mode 100644 src/charset/utf8.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 49a974a..f9eccf1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,4 +14,10 @@ add_executable(http_client_c ${CMAKE_SOURCE_DIR}/test/main.c ) -target_link_libraries(http_client_c m) \ No newline at end of file +#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/README.md b/README.md index 6586aed..f37ca93 100644 --- a/README.md +++ b/README.md @@ -10,102 +10,238 @@ in C, it can be used in C++ code as well. Basic Usage =============== -http_response + +```c + + 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 *redirect_ui; - char *body; - size_t body_len; - char *status_code; - int status_code; - char *status_text; - char *request_headers; - char *response_headers; - }; - +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 -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. +The last redirected url. + +##### *redirect_count +the HTTP redirect count ##### *body -This contains the response BODY (usually HTML). +The response body. always NULL if you set a response body callback ##### body_len -This contains the length of the response. Useful to deal with binary data. - -##### *status_code -This contains the HTTP Status code returned by the server in plain text format. +the response body length ##### status_code -This returns the same as status_code but as an integer. +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_request_exec() -------------- -http_request_exec 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_request_exec(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_request_exec("GET / HTTP/1.1\r\nHostname:www.google.com\r\nConnection:close\r\n\r\n", purl); +```c -Please note that http_request_exec 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_request_exec 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_request_exec 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(fp, "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/charset/utf8.h b/src/charset/utf8.h new file mode 100644 index 0000000..452385b --- /dev/null +++ b/src/charset/utf8.h @@ -0,0 +1,97 @@ +// 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]; + +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; +} + +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; +} diff --git a/src/encoding/chunked.h b/src/encoding/chunked.h index 14ee0a5..1f495f5 100644 --- a/src/encoding/chunked.h +++ b/src/encoding/chunked.h @@ -4,9 +4,10 @@ #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, const char *buf, size_t buf_len, size_t offset, http_request *, http_response *); +int http_chunked_transfer_decode(int sock, const char *buf, size_t data_len, size_t offset, http_request *, http_response *); /** * @@ -19,38 +20,49 @@ http_client_errors http_chunked_transfer_block_info(const char *buf, long buf_le long pos = -1; + *data_len = 0; + *offset = 0; + while (pos++ < buf_len) { if (buf[pos] == '\r' && pos + 1 < buf_len && buf[pos + 1] == '\n') { - pos++; break; } } - if (pos <= 1) { + if (pos >= 1) { + + char chunk_size[pos + 1]; + + memcpy(chunk_size, buf, pos); + chunk_size[pos] = '\0'; - return HTTP_CLIENT_ERROR_OK; + *offset = pos + 2; + *data_len = hex2dec(chunk_size, pos); } - char chunk_size[pos]; + return HTTP_CLIENT_ERROR_OK; +} - memcpy(chunk_size, buf, pos - 1); +char *u8block_info(char *data, size_t bytes_read, size_t *mb_len) { - chunk_size[pos] = '\0'; + char *block = (char *) calloc(bytes_read + 1, 1); - *offset = pos + 1; - *data_len = hex2dec(chunk_size, pos - 1); + u8strncpy(block, data, bytes_read); + *mb_len = u8strlen(block); - return HTTP_CLIENT_ERROR_OK; + return block; } int http_chunked_transfer_decode(int sock, const char *buf, size_t buf_len, size_t offset, http_request *hreq, http_response *hresp) { - size_t bytes_read; 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 = 0; @@ -58,64 +70,151 @@ int http_chunked_transfer_decode(int sock, const char *buf, size_t buf_len, size char data[BUF_READ]; memcpy(data, buf, buf_len); - while (received_len > 0) { +// http_header_attribute *charset = NULL; +// http_header *header = http_header_get(hresp->headers, "Content-Type"); +// +// if (header != NULL) { +// +// char *print = http_header_print(header); +// +// printf("content-type: %s", print); +// charset = http_header_attribute_get(header, "charset"); +// +// free(print); +// print = NULL; +// +// if (charset != NULL) { +// +// print = http_header_attribute_print(charset); +// +// +// fprintf(stderr, "charset %s\n\n\n", print); +// free(print); +// free(charset); +// } +// +// free(header); +// header = NULL; +// } +// +// return HTTP_CLIENT_ERROR_OK; + + + + if (received_len - 1 > offset) { + +// block_info: + status = http_chunked_transfer_block_info(&data[offset], buf_len - offset, &block_offset, &block_size); + + if (status < 0) { + + return HTTP_CLIENT_ERROR_RECV; + } - block_info: - status = http_chunked_transfer_block_info(&data[offset], buf_len - offset, &block_offset, &block_size); + if (block_size == 0) { - if (status < 0) { +// hresp->body_len = body_len; + return HTTP_CLIENT_ERROR_OK; + } - return HTTP_CLIENT_ERROR_DATA; - } + while (block_size > 0) { - if (block_size == 0) { + partial_read: - return 0; + offset += block_offset; + mb_str = u8block_info(&data[offset], block_size, &mb_len); + + size_t _len = strlen(mb_str); + + if (hreq->response_body_cb != NULL) { + + hreq->response_body_cb(mb_str, _len, hresp->headers); } - while (block_size > 0) { + else { - if (block_size > buf_len - offset) { + if (hresp->body == NULL) { - bytes_read = buf_len - block_offset - offset; - hreq->response_body_cb(&data[offset] + block_offset, bytes_read, hresp->headers); + hresp->body = strdup(mb_str); + } - block_size -= bytes_read; + else { - chunked_read: - received_len = recv(sock, data, BUF_READ - 1, 0); - offset = 0; + hresp->body = (char *) realloc(hresp->body, hresp->body_len + _len + 1); + memcpy(&hresp->body[hresp->body_len], mb_str, _len); - if (received_len < 0) { + hresp->body[hresp->body_len + _len] = '\0'; + } + } + + hresp->body_len += _len; + offset += _len; + block_size -= mb_len; + block_offset = 0; - return HTTP_CLIENT_ERROR_RECV; - } + free(mb_str); + mb_str = NULL; - if (received_len == 0) { + if (offset < received_len) { - return HTTP_CLIENT_ERROR_OK; - } + if (block_size > 0) { + + goto partial_read; + } - buf_len = received_len; + if (block_size == 0) { - if (received_len >= block_size) { + offset += 2; + status = http_chunked_transfer_block_info(&data[offset], received_len - offset, &block_offset, &block_size); - hreq->response_body_cb(&data[offset], block_size, hresp->headers); - offset = block_size + 2; - goto block_info; - } else { + if (status < 0) { - hreq->response_body_cb(&data[offset], received_len, hresp->headers); - block_size -= received_len; - goto chunked_read; - } - } else { + return HTTP_CLIENT_ERROR_RECV; + } - hreq->response_body_cb(&data[offset], block_size, hresp->headers); - offset += block_offset + block_size + 2; - goto block_info; + if (block_size == 0) { + +// hresp->body_len = body_len; + return HTTP_CLIENT_ERROR_OK; + } + + goto partial_read; } } + + // completely read data block + // fetch next data block + // reset offset + +// bool complete = received_len == offset; + + received_len = recv(sock, data, BUF_READ - 1, 0); + + offset = 0; + + if (received_len > 0) { + +// if (complete) { +// +// goto block_info; +// } + + data[received_len] = '\0'; + + goto partial_read; + } + + if (received_len == 0) { + +// hresp->body_len = body_len; + return HTTP_CLIENT_ERROR_OK; + } + +// if (received_len < 0) { + + return HTTP_CLIENT_ERROR_RECV; +// } + } } return HTTP_CLIENT_ERROR_OK; diff --git a/src/encoding/decode.h b/src/encoding/decode.h index 42cb9b2..d3294f2 100644 --- a/src/encoding/decode.h +++ b/src/encoding/decode.h @@ -20,14 +20,42 @@ http_client_errors http_transfer_decode(http_transfer_encoding *te, int sock, co return HTTP_CLIENT_ERROR_TRANSFER_ENCODING; } - hreq->response_body_cb(&buf[offset], buf_len - offset, hresp->headers); + size_t body_len = 0; + + if (hreq->response_body_cb != NULL) { + + 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; char BUF[BUF_READ]; while ((received_len = recv(sock, BUF, BUF_READ - 1, 0)) > 0) { - hreq->response_body_cb(BUF, received_len, hresp->headers); + 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) { diff --git a/src/encoding/struct.h b/src/encoding/struct.h index 10c3905..fa0a8f0 100644 --- a/src/encoding/struct.h +++ b/src/encoding/struct.h @@ -26,12 +26,7 @@ void http_transfer_encoding_free(http_transfer_encoding *); // http_transfer_encoding *http_transfer_encoding_new() { - http_transfer_encoding *te = (http_transfer_encoding *) malloc(sizeof (http_transfer_encoding)); - - if (te != NULL) { - - memset(te, 0, sizeof (http_transfer_encoding)); - } + http_transfer_encoding *te = (http_transfer_encoding *) calloc(sizeof (http_transfer_encoding), 1); return te; } @@ -119,11 +114,13 @@ void http_transfer_encoding_free(http_transfer_encoding *te) { 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); diff --git a/src/http/client.h b/src/http/client.h index b904219..d48707c 100644 --- a/src/http/client.h +++ b/src/http/client.h @@ -217,6 +217,12 @@ http_response *http_request_exec(http_request *hreq) { } } + if (!http_header_exists(hreq->headers, "Accept-Encoding")) { + + // disable encoding pleas 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"); @@ -298,12 +304,13 @@ http_response *http_request_exec(http_request *hreq) { break; } -// break; hresp->redirect_count++; http_header *location = http_header_get(hresp->headers, "Location"); + hresp->redirect_uri = strdup(location->value); purl = parse_url(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 || @@ -431,11 +438,12 @@ http_client_errors http_request_send(http_response *hresp, http_request *hreq, c 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->timeout, sizeof *hreq->timeout) < 0 - || setsockopt (sock, SOL_SOCKET, SO_SNDTIMEO, hreq->timeout, sizeof *hreq->timeout) < 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; @@ -486,7 +494,6 @@ http_client_errors http_request_send(http_response *hresp, http_request *hreq, c /* Receive into response*/ char BUF[BUF_READ]; - http_transfer_encoding *te = NULL; ssize_t received_len; if ((received_len = recv(sock, BUF, BUF_READ - 1, 0)) > 0) { @@ -510,9 +517,7 @@ http_client_errors http_request_send(http_response *hresp, http_request *hreq, c char *status_text = strstr(status_line, " "); hresp->status_code = atoi(status_text); - hresp->status_text = (char *) malloc(sizeof status_text); - - memcpy(hresp->status_text, &status_text[1], strlen(status_text)); + hresp->status_text = strdup(&status_text[1]); } size_t headers_len = 0; @@ -529,7 +534,7 @@ http_client_errors http_request_send(http_response *hresp, http_request *hreq, c goto exit; } - if (hreq->response_body_cb != NULL) { +// if (hreq->response_body_cb != NULL) { http_header *teh = http_header_get(hresp->headers, "Transfer-Encoding"); @@ -540,7 +545,7 @@ http_client_errors http_request_send(http_response *hresp, http_request *hreq, c error_reason = http_transfer_decode(te, sock, BUF, received_len, (body_end - BUF) + 4, hreq, hresp); goto exit; - } +// } } } @@ -557,11 +562,6 @@ http_client_errors http_request_send(http_response *hresp, http_request *hreq, c free(remote); } -// if (chunk != NULL) { -// -// free(chunk); -// } - if (te != NULL) { http_transfer_encoding_free(te); @@ -589,7 +589,7 @@ void http_request_option(http_request *hreq, http_option option, const void *val case HTTP_OPTION_URL: - hreq->request_uri = (char *) val; + hreq->request_uri = strdup((char *) val); break; case HTTP_OPTION_HEADER: @@ -604,7 +604,7 @@ void http_request_option(http_request *hreq, http_option option, const void *val break; case HTTP_OPTION_REQUEST_TIMEOUT: - hreq->timeout->tv_sec = atol(val); + hreq->request_timeout->tv_sec = atol(val); break; case HTTP_OPTION_REQUEST_HEADER_CALLBACK: diff --git a/src/http/header.h b/src/http/header.h index 37fa819..7050c55 100644 --- a/src/http/header.h +++ b/src/http/header.h @@ -32,10 +32,12 @@ 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 *); 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 *header, const char *name); void http_header_set_name(http_header *header, char *name); @@ -91,6 +93,21 @@ http_header *http_header_clone(http_header *headers) { 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; +} + int http_header_exists(http_header *header, const char *name) { http_header *head = header; @@ -144,6 +161,23 @@ http_header *http_header_get(http_header *header, const char *name) { return result; } +http_header_attribute *http_header_attribute_get(http_header *header, const char *name) { + + http_header_attribute *head = header->attribute; + + while (head != NULL) { + + if (strcasecmp(head->name, name) == 0) { + + return http_header_attribute_clone(head); + } + + head = head->next; + } + + return NULL; +} + void http_header_set_name(http_header *header, char *name) { if (header->name != NULL) { diff --git a/src/http/struct.h b/src/http/struct.h index aa08233..ce6b56e 100644 --- a/src/http/struct.h +++ b/src/http/struct.h @@ -42,16 +42,15 @@ typedef struct http_request { http_header_cb_ptr *request_header_cb; http_header_cb_ptr *response_header_cb; http_response_body_cb_ptr *response_body_cb; - struct timeval *timeout; + struct timeval *request_timeout; } http_request; /* - Represents an HTTP html response + Represents an HTTP response */ typedef struct http_response { struct http_struct; char *redirect_uri; - uint8_t redirected; int redirect_count; int status_code; char *status_text; @@ -66,9 +65,9 @@ http_request *http_request_new() { if (hreq != NULL) { hreq->max_redirect = HTTP_CLIENT_C_HTTP_MAX_REDIRECT; - hreq->timeout = (struct timeval *) calloc(1, sizeof (struct timeval)); - hreq->timeout->tv_sec = 10; - hreq->timeout->tv_usec = 0; + hreq->request_timeout = (struct timeval *) calloc(1, sizeof (struct timeval)); + hreq->request_timeout->tv_sec = 10; + hreq->request_timeout->tv_usec = 0; } return hreq; @@ -93,12 +92,22 @@ void http_request_free(http_request *hreq) { 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->timeout); + free(hreq->request_timeout); free(hreq); } } @@ -112,6 +121,17 @@ void http_response_free(http_response *hresp) { 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); diff --git a/test/main.c b/test/main.c index 16e0305..11a8841 100644 --- a/test/main.c +++ b/test/main.c @@ -5,6 +5,10 @@ FILE *fp; +/** + * response header callback + * @param headers + */ void response_header_cb(http_header *headers) { http_header *header = headers; @@ -15,11 +19,15 @@ void response_header_cb(http_header *headers) { fprintf(stderr, "%s\r\n", printed); - fwrite(printed, strlen(printed), 1, fp); - fwrite("\r\n", 2, 1, fp); +// 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; @@ -30,11 +38,17 @@ void request_header_cb(http_header *headers) { fprintf(stderr, "%s\r\n", printed); - fwrite(printed, strlen(printed), 1, fp); - fwrite("\r\n", 2, 1, fp); +// 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) { @@ -55,18 +69,6 @@ void response_body_cb(const char *chunk, size_t chunk_len, http_header *headers) int main(int argc, char *argv[]) { -// size_t header_len = 0; -// http_header *headers = http_header_parse("P3P: CP=\"This is not a P3P policy! See g.co/p3phelp for more info.\"\r\ncache-control: private, max-age=36500", &header_len); -// -// char *print = http_header_print(headers); -// -// fprintf(stderr, "%s", print); -// -// free(print); -// http_header_free(headers); -// -// return 0; - if (argc <= 2) { fprintf(fp, "Usage: \n$ %s URL DEST_FILE\n", argv[0]); @@ -78,8 +80,6 @@ int main(int argc, char *argv[]) { http_request *request = http_request_new(); -// printf("url: %s\n", argv[1]); - 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"); @@ -87,11 +87,9 @@ int main(int argc, char *argv[]) { 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, "User-Agent", "Bluefox"); // http_request_header_set(request, "Authorization", "Bearer "); - http_request_header_add(request, "Accept-Language", "en-US;q=0.6,en;q=0.4"); - // - // fr-CA,en-CA;q=0.8,en-US;q=0.6,en;q=0.4,fr;q=0.2 +// http_request_header_add(request, "Accept-Language", "en-US;q=0.6,en;q=0.4"); fp = fopen(filename, "wb"); @@ -102,44 +100,5 @@ int main(int argc, char *argv[]) { http_request_free(request); http_response_free(response); - -// http_response *response = http_get(argv[1], custom_headers); -// -// if (response) { -// -// fprintf(stderr, "request headers: [\n%s\n]\nresponse headers [\n%s\n]\n", response->request_headers, -// response->response_headers); -// } -// -// if (response == NULL || response->status_code != 200) { -// -// fprintf(stderr, "request failed with status code #%d\n", response->status_code); -// http_response_free(response); -// exit(1); -// } -// -// if (response->body_len > 0) { -// -// fp = fopen(filename, "wb"); -// -// if (fp == NULL) { -// -// fprintf(stderr, "cannot open file for writing: %s\n", filename); -// http_response_free(response); -// exit(1); -// } -// -// if (fwrite(response->body, 1, response->body_len, fp) != response->body_len) { -// -// fprintf(stderr, "failed to write data into file: %s\n", filename); -// http_response_free(response); -// fclose(fp); -// exit(1); -// } -// -// http_response_free(response); -// fclose(fp); -// } - return 0; } \ No newline at end of file From d0b12345019fc4e0df3130fa6610bb8bceffcda8 Mon Sep 17 00:00:00 2001 From: Thierry bela Date: Sat, 14 May 2022 09:28:14 -0400 Subject: [PATCH 10/12] fix typo in the doc --- CMakeLists.txt | 8 ++--- gpl-3.0.txt => LICENSE.md | 0 README.md | 3 +- src/charset/utf8.h | 54 +++++++++++++++++++++++++++++-- src/encoding/chunked.h | 46 +++++++++----------------- src/encoding/decode.h | 13 ++++---- src/http/client.h | 34 +++++++++----------- src/http/header.h | 68 +++++++++++++++++++++++++++------------ src/url/parser.h | 12 ++----- test/main.c | 55 ++++++++++++++++++++++--------- 10 files changed, 184 insertions(+), 109 deletions(-) rename gpl-3.0.txt => LICENSE.md (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index f9eccf1..45f98d4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,10 +14,10 @@ add_executable(http_client_c ${CMAKE_SOURCE_DIR}/test/main.c ) -#add_executable(http_chunked_c -# ${CMAKE_SOURCE_DIR}/test/chunked.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 +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 f37ca93..3bd395d 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Basic Usage =============== ```c + #include "http/client.h" http_request *request = http_request_new(); @@ -213,7 +214,7 @@ int main(int argc, char *argv[]) { if (argc <= 2) { - fprintf(fp, "Usage: \n$ %s URL DEST_FILE\n", argv[0]); + fprintf(stderr, "Usage: \n$ %s URL DEST_FILE\n", argv[0]); exit(1); } diff --git a/src/charset/utf8.h b/src/charset/utf8.h index 452385b..553496e 100644 --- a/src/charset/utf8.h +++ b/src/charset/utf8.h @@ -12,6 +12,11 @@ static uint8_t const u8_length[] = { #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] @@ -31,6 +36,12 @@ int u8chrisvalid(u8chr_t c) return 0; } +/** + * next utf-8 char + * @param txt + * @param ch + * @return + */ size_t u8next(unsigned char *txt, u8chr_t *ch) { size_t len; @@ -60,9 +71,10 @@ size_t u8next(unsigned char *txt, u8chr_t *ch) size_t u8strlen(const char *s) { size_t len=0; + while (*s) { - size_t w = u8next(s, NULL); + size_t w = u8next((unsigned char *) s, NULL); if (w == 0) { @@ -72,10 +84,14 @@ size_t u8strlen(const char *s) s+= w; len++; } + return len; } -// Avoids truncating multibyte UTF-8 encoding at the end. +/** + * Avoids truncating multibyte UTF-8 encoding at the end. + */ + char *u8strncpy(char *dest, const char *src, size_t n) { int k = n-1; @@ -95,3 +111,37 @@ char *u8strncpy(char *dest, const char *src, size_t n) } 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 index 1f495f5..4beb359 100644 --- a/src/encoding/chunked.h +++ b/src/encoding/chunked.h @@ -7,7 +7,8 @@ #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, const char *buf, size_t data_len, size_t offset, http_request *, http_response *); + +int http_chunked_transfer_decode(int sock, char *buf, size_t data_len, size_t offset, http_request *, http_response *); /** * @@ -55,7 +56,8 @@ char *u8block_info(char *data, size_t bytes_read, size_t *mb_len) { return block; } -int http_chunked_transfer_decode(int sock, const char *buf, size_t buf_len, size_t offset, http_request *hreq, http_response *hresp) { +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; @@ -67,8 +69,8 @@ int http_chunked_transfer_decode(int sock, const char *buf, size_t buf_len, size int status = 0; - char data[BUF_READ]; - memcpy(data, buf, buf_len); +// char data[BUF_READ]; +// memcpy(data, buf, buf_len); // http_header_attribute *charset = NULL; // http_header *header = http_header_get(hresp->headers, "Content-Type"); @@ -104,7 +106,7 @@ int http_chunked_transfer_decode(int sock, const char *buf, size_t buf_len, size if (received_len - 1 > offset) { // block_info: - status = http_chunked_transfer_block_info(&data[offset], buf_len - offset, &block_offset, &block_size); + status = http_chunked_transfer_block_info(&buf[offset], buf_len - offset, &block_offset, &block_size); if (status < 0) { @@ -122,23 +124,19 @@ int http_chunked_transfer_decode(int sock, const char *buf, size_t buf_len, size partial_read: offset += block_offset; - mb_str = u8block_info(&data[offset], block_size, &mb_len); + mb_str = u8block_info(&buf[offset], block_size, &mb_len); size_t _len = strlen(mb_str); if (hreq->response_body_cb != NULL) { hreq->response_body_cb(mb_str, _len, hresp->headers); - } - - else { + } else { if (hresp->body == NULL) { hresp->body = strdup(mb_str); - } - - else { + } else { hresp->body = (char *) realloc(hresp->body, hresp->body_len + _len + 1); memcpy(&hresp->body[hresp->body_len], mb_str, _len); @@ -165,7 +163,8 @@ int http_chunked_transfer_decode(int sock, const char *buf, size_t buf_len, size if (block_size == 0) { offset += 2; - status = http_chunked_transfer_block_info(&data[offset], received_len - offset, &block_offset, &block_size); + status = http_chunked_transfer_block_info(&buf[offset], received_len - offset, &block_offset, + &block_size); if (status < 0) { @@ -182,38 +181,23 @@ int http_chunked_transfer_decode(int sock, const char *buf, size_t buf_len, size } } - // completely read data block - // fetch next data block - // reset offset - -// bool complete = received_len == offset; - - received_len = recv(sock, data, BUF_READ - 1, 0); + received_len = recv(sock, (void *) buf, BUF_READ - 1, 0); offset = 0; if (received_len > 0) { -// if (complete) { -// -// goto block_info; -// } - - data[received_len] = '\0'; + buf[received_len] = '\0'; goto partial_read; } if (received_len == 0) { -// hresp->body_len = body_len; return HTTP_CLIENT_ERROR_OK; } -// if (received_len < 0) { - - return HTTP_CLIENT_ERROR_RECV; -// } + return HTTP_CLIENT_ERROR_RECV; } } diff --git a/src/encoding/decode.h b/src/encoding/decode.h index d3294f2..c0a8196 100644 --- a/src/encoding/decode.h +++ b/src/encoding/decode.h @@ -6,9 +6,9 @@ #include "encoding/chunked.h" #include "http/struct.h" -http_client_errors http_transfer_decode(http_transfer_encoding *te, int sock, const 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 *, http_response *); -http_client_errors http_transfer_decode(http_transfer_encoding *te, int sock, const char *buf, size_t buf_len, size_t offset, http_request *hreq, http_response *hresp) { +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) { @@ -24,6 +24,7 @@ http_client_errors http_transfer_decode(http_transfer_encoding *te, int sock, co if (hreq->response_body_cb != NULL) { + fprintf(stderr, "%s", &buf[offset]); hreq->response_body_cb(&buf[offset], buf_len - offset, hresp->headers); } else { @@ -33,20 +34,18 @@ http_client_errors http_transfer_decode(http_transfer_encoding *te, int sock, co memcpy(hresp->body, &buf[offset], body_len); } - size_t received_len = 0; - char BUF[BUF_READ]; - while ((received_len = recv(sock, BUF, BUF_READ - 1, 0)) > 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); + 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); + memcpy(&hresp->body[body_len], buf, received_len); body_len += received_len; } } diff --git a/src/http/client.h b/src/http/client.h index d48707c..cd06077 100644 --- a/src/http/client.h +++ b/src/http/client.h @@ -219,7 +219,7 @@ http_response *http_request_exec(http_request *hreq) { if (!http_header_exists(hreq->headers, "Accept-Encoding")) { - // disable encoding pleas please + // disable encoding please please http_request_header_set(hreq, "Accept-Encoding", "identity"); } @@ -247,7 +247,7 @@ http_response *http_request_exec(http_request *hreq) { do { - if(strcasecmp(purl->scheme, "http") != 0) { + if (strcasecmp(purl->scheme, "http") != 0) { if (request != NULL) { @@ -284,8 +284,7 @@ http_response *http_request_exec(http_request *hreq) { fprintf(stderr, "error: %s: '%s'\n", http_client_error(result), te->value); http_header_free(te); } - } - else { + } else { fprintf(stderr, "error: %s\n", http_client_error(result)); } @@ -442,9 +441,9 @@ http_client_errors http_request_send(http_response *hresp, http_request *hreq, c /* 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 - ) { + || 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; @@ -482,6 +481,7 @@ http_client_errors http_request_send(http_response *hresp, http_request *hreq, c /* Send headers to server */ size_t sent = 0; while (sent < request_len) { + tmpres = send(sock, &request[sent], request_len - sent, 0); if (tmpres == -1) { @@ -496,7 +496,7 @@ http_client_errors http_request_send(http_response *hresp, http_request *hreq, c ssize_t received_len; - if ((received_len = recv(sock, BUF, BUF_READ - 1, 0)) > 0) { + if ((received_len = recv(sock, (void *) BUF, BUF_READ - 1, 0)) > 0) { BUF[received_len] = '\0'; @@ -534,18 +534,16 @@ http_client_errors http_request_send(http_response *hresp, http_request *hreq, c goto exit; } -// if (hreq->response_body_cb != NULL) { - - http_header *teh = http_header_get(hresp->headers, "Transfer-Encoding"); + http_header *teh = http_header_get(hresp->headers, "Transfer-Encoding"); - if (teh != NULL) { + if (teh != NULL) { - te = http_transfer_encoding_parse(teh->value); - } + te = http_transfer_encoding_parse(teh->value); + } - error_reason = http_transfer_decode(te, sock, BUF, received_len, (body_end - BUF) + 4, hreq, hresp); - goto exit; -// } + error_reason = http_transfer_decode(te, sock, (char *) BUF, received_len, (body_end - BUF) + 4, hreq, + hresp); + goto exit; } } @@ -579,12 +577,12 @@ http_client_errors http_request_send(http_response *hresp, http_request *hreq, c } #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: diff --git a/src/http/header.h b/src/http/header.h index 7050c55..3fff192 100644 --- a/src/http/header.h +++ b/src/http/header.h @@ -33,11 +33,12 @@ 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 *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); @@ -108,6 +109,36 @@ http_header_attribute *http_header_attribute_clone(http_header_attribute *attr) 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; @@ -129,7 +160,7 @@ http_header *http_header_get(http_header *header, const char *name) { http_header *head = header; http_header *result = NULL; - http_header *tmp, *tmp2; + http_header *tmp; while (head != NULL) { @@ -137,21 +168,19 @@ http_header *http_header_get(http_header *header, const char *name) { if (result == NULL) { - result = http_header_new(); - - http_header_set_name(result, head->name); - http_header_set_value(result, head->value); - - tmp = result; + tmp = result = http_header_new(); } else { - tmp2 = http_header_new(); + tmp->next = http_header_new(); + tmp = tmp->next; + } + + http_header_set_name(tmp, head->name); + http_header_set_value(tmp, head->value); - http_header_set_name(tmp2, head->name); - http_header_set_value(tmp2, head->value); + if (head->attribute != NULL) { - tmp->next = tmp2; - tmp = tmp2; + tmp->attribute = http_header_attribute_deep_clone(head->attribute); } } @@ -161,18 +190,18 @@ http_header *http_header_get(http_header *header, const char *name) { return result; } -http_header_attribute *http_header_attribute_get(http_header *header, const char *name) { +http_header_attribute *http_header_attribute_get(http_header_attribute *attribute, const char *name) { - http_header_attribute *head = header->attribute; + http_header_attribute *attr = attribute; - while (head != NULL) { + while (attr != NULL) { - if (strcasecmp(head->name, name) == 0) { + if (strcasecmp(attr->name, name) == 0) { - return http_header_attribute_clone(head); + return http_header_attribute_clone(attr); } - head = head->next; + attr = attr->next; } return NULL; @@ -299,7 +328,6 @@ http_header *http_header_parse(const char *http_headers, size_t *len) { http_header_attribute *http_header_attribute_parse(const char *http_headers) { - http_header_attribute *attr = NULL; http_header_attribute *root = NULL; diff --git a/src/url/parser.h b/src/url/parser.h index 2419da4..76d7aa3 100644 --- a/src/url/parser.h +++ b/src/url/parser.h @@ -107,14 +107,7 @@ parsed_url *parse_url(const char *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; /* @@ -296,8 +289,7 @@ parsed_url *parse_url(const char *url) { } /* Get ip */ - char *ip = hostname_to_ip(purl->host); - purl->ip = ip; + purl->ip = hostname_to_ip(purl->host); /* Set uri */ purl->uri = (char *) url; diff --git a/test/main.c b/test/main.c index 11a8841..9fade27 100644 --- a/test/main.c +++ b/test/main.c @@ -3,7 +3,9 @@ // #include "http/client.h" -FILE *fp; +FILE *fp = NULL; +char *charset = NULL; +char *filename = NULL; /** * response header callback @@ -11,16 +13,36 @@ FILE *fp; */ void response_header_cb(http_header *headers) { - http_header *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); - -// fwrite(printed, strlen(printed), 1, fp); -// fwrite("\r\n", 2, 1, fp); free(printed); } @@ -30,16 +52,11 @@ void response_header_cb(http_header *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); } @@ -71,11 +88,11 @@ int main(int argc, char *argv[]) { if (argc <= 2) { - fprintf(fp, "Usage: \n$ %s URL DEST_FILE\n", argv[0]); + fprintf(stderr, "Usage: \n$ %s URL DEST_FILE\n", argv[0]); exit(1); } - char *filename = argc > 2 ? argv[2] : ""; + filename = argc > 2 ? argv[2] : ""; fprintf(stderr, "opening %s ...\n", filename); http_request *request = http_request_new(); @@ -89,13 +106,19 @@ int main(int argc, char *argv[]) { 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", "en-US;q=0.6,en;q=0.4"); - - fp = fopen(filename, "wb"); +// 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); - fclose(fp); + if (fp != NULL) { + + fclose(fp); + } + + if (charset != NULL) { + + free(charset); + } http_request_free(request); http_response_free(response); From 79aa795b70a37fa2aaaaca71038492c0dc859e10 Mon Sep 17 00:00:00 2001 From: Thierry bela Date: Sat, 14 May 2022 09:29:48 -0400 Subject: [PATCH 11/12] remove CMake test --- CMakeLists.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 45f98d4..f9eccf1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,10 +14,10 @@ add_executable(http_client_c ${CMAKE_SOURCE_DIR}/test/main.c ) -add_executable(http_chunked_c - ${CMAKE_SOURCE_DIR}/test/chunked.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 +#target_link_libraries(http_chunked_c m) \ No newline at end of file From b9227f7b019772a76e373e08d20a2008d8f76dd6 Mon Sep 17 00:00:00 2001 From: Thierry bela Date: Sat, 14 May 2022 11:10:29 -0400 Subject: [PATCH 12/12] handle infinite loop when mb_len is 0 --- src/encoding/chunked.h | 88 ++++++++++++++++++------------------------ src/http/client.h | 34 +++++++++------- src/http/struct.h | 4 +- src/url/parser.h | 42 ++++++++++---------- test/main.c | 2 +- 5 files changed, 82 insertions(+), 88 deletions(-) diff --git a/src/encoding/chunked.h b/src/encoding/chunked.h index 4beb359..296b279 100644 --- a/src/encoding/chunked.h +++ b/src/encoding/chunked.h @@ -10,6 +10,8 @@ int http_chunked_transfer_block_info(const char *buf, long buf_len, size_t *offs 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 @@ -67,45 +69,10 @@ int http_chunked_transfer_decode(int sock, char *buf, size_t buf_len, size_t off char *mb_str = NULL; ssize_t received_len = buf_len; - int status = 0; - -// char data[BUF_READ]; -// memcpy(data, buf, buf_len); - -// http_header_attribute *charset = NULL; -// http_header *header = http_header_get(hresp->headers, "Content-Type"); -// -// if (header != NULL) { -// -// char *print = http_header_print(header); -// -// printf("content-type: %s", print); -// charset = http_header_attribute_get(header, "charset"); -// -// free(print); -// print = NULL; -// -// if (charset != NULL) { -// -// print = http_header_attribute_print(charset); -// -// -// fprintf(stderr, "charset %s\n\n\n", print); -// free(print); -// free(charset); -// } -// -// free(header); -// header = NULL; -// } -// -// return HTTP_CLIENT_ERROR_OK; - - + int status; if (received_len - 1 > offset) { -// block_info: status = http_chunked_transfer_block_info(&buf[offset], buf_len - offset, &block_offset, &block_size); if (status < 0) { @@ -115,7 +82,6 @@ int http_chunked_transfer_decode(int sock, char *buf, size_t buf_len, size_t off if (block_size == 0) { -// hresp->body_len = body_len; return HTTP_CLIENT_ERROR_OK; } @@ -128,23 +94,25 @@ int http_chunked_transfer_decode(int sock, char *buf, size_t buf_len, size_t off size_t _len = strlen(mb_str); - if (hreq->response_body_cb != NULL) { - - hreq->response_body_cb(mb_str, _len, hresp->headers); - } else { - - if (hresp->body == NULL) { + if (_len == 0) { - hresp->body = strdup(mb_str); - } else { + char *end = strstr(&buf[offset], "\r\n"); + + if (end != NULL) { - 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'; + 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; @@ -162,6 +130,7 @@ int http_chunked_transfer_decode(int sock, char *buf, size_t buf_len, size_t off if (block_size == 0) { + block_info: offset += 2; status = http_chunked_transfer_block_info(&buf[offset], received_len - offset, &block_offset, &block_size); @@ -173,7 +142,6 @@ int http_chunked_transfer_decode(int sock, char *buf, size_t buf_len, size_t off if (block_size == 0) { -// hresp->body_len = body_len; return HTTP_CLIENT_ERROR_OK; } @@ -182,7 +150,6 @@ int http_chunked_transfer_decode(int sock, char *buf, size_t buf_len, size_t off } received_len = recv(sock, (void *) buf, BUF_READ - 1, 0); - offset = 0; if (received_len > 0) { @@ -204,6 +171,25 @@ int http_chunked_transfer_decode(int sock, char *buf, size_t buf_len, size_t off 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/http/client.h b/src/http/client.h index cd06077..12343c3 100644 --- a/src/http/client.h +++ b/src/http/client.h @@ -72,10 +72,10 @@ 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, parsed_url *purl, char *body, +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, parsed_url *purl); +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); @@ -176,6 +176,11 @@ 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; @@ -189,12 +194,15 @@ http_response *http_request_exec(http_request *hreq) { str_to_upper(hreq->method, strlen(hreq->method)); } - if (request_uri == NULL) { + if (strcasecmp(hreq->method, "GET") == 0 && hreq->body_len > 0) { - return NULL; + fprintf(stderr, "converting HTTP method from GET tp POST"); + + free(hreq->method); + hreq->method = strdup("POST"); } - parsed_url *purl = parse_url(hreq->request_uri); + http_url *purl = http_url_parse(hreq->request_uri); if (purl == NULL) { @@ -235,7 +243,7 @@ http_response *http_request_exec(http_request *hreq) { if (hresp == NULL) { printf("Unable to allocate memory for HTTP response."); - parsed_url_free(purl); + http_url_free(purl); return NULL; } @@ -255,14 +263,14 @@ http_response *http_request_exec(http_request *hreq) { } fprintf(stderr, "error: %s: '%s'\n", http_client_error(HTTP_CLIENT_PROTO), purl->scheme); - parsed_url_free(purl); + http_url_free(purl); return NULL; } if (request_len < 0) { free(request); - parsed_url_free(purl); + http_url_free(purl); fprintf(stderr, "error: %s\n", http_client_error(request_len)); return NULL; @@ -289,7 +297,7 @@ http_response *http_request_exec(http_request *hreq) { fprintf(stderr, "error: %s\n", http_client_error(result)); } - parsed_url_free(purl); + http_url_free(purl); http_response_free(hresp); return NULL; @@ -307,7 +315,7 @@ http_response *http_request_exec(http_request *hreq) { http_header *location = http_header_get(hresp->headers, "Location"); hresp->redirect_uri = strdup(location->value); - purl = parse_url(location->value); + purl = http_url_parse(location->value); http_header_free(hresp->headers); hresp->headers = NULL; @@ -341,13 +349,13 @@ http_response *http_request_exec(http_request *hreq) { } while (hreq->max_redirect > hresp->redirect_count); - parsed_url_free(purl); + http_url_free(purl); /* Return response */ return hresp; } -char *http_request_serialize(http_header *headers, const char *method, parsed_url *purl, char *body, +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) { @@ -430,7 +438,7 @@ char *http_request_serialize(http_header *headers, const char *method, parsed_ur return buff; } -http_client_errors http_request_send(http_response *hresp, http_request *hreq, char *request, size_t request_len, parsed_url *purl) { +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; diff --git a/src/http/struct.h b/src/http/struct.h index ce6b56e..628422c 100644 --- a/src/http/struct.h +++ b/src/http/struct.h @@ -6,14 +6,14 @@ #define HTTP_CLIENT_C_HTTP_STRUCT_H #define BUF_READ 8192 -#define HTTP_CLIENT_C_HTTP_MAX_REDIRECT 10; +#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 char*, size_t, http_header *); +typedef void (http_response_body_cb_ptr)(const unsigned char*, size_t, http_header *); typedef enum { HTTP_OPTION_URL, diff --git a/src/url/parser.h b/src/url/parser.h index 76d7aa3..679a531 100644 --- a/src/url/parser.h +++ b/src/url/parser.h @@ -34,7 +34,7 @@ /* Represents an url */ -typedef struct parsed_url { +typedef struct http_url { char *uri; /* mandatory */ char *scheme; /* mandatory */ char *host; /* mandatory */ @@ -45,12 +45,12 @@ typedef struct parsed_url { char *fragment; /* optional */ char *username; /* optional */ char *password; /* optional */ -} parsed_url; +} http_url; /* Free memory of parsed url */ -void parsed_url_free(parsed_url *purl) { +void http_url_free(http_url *purl) { if (NULL != purl) { if (NULL != purl->scheme) free(purl->scheme); if (NULL != purl->host) free(purl->host); @@ -68,7 +68,7 @@ void parsed_url_free(parsed_url *purl) { Retrieves the IP adress of a hostname */ char *hostname_to_ip(char *hostname) { - char *ip = "0.0.0.0"; +// char *ip = "0.0.0.0"; struct hostent *h; if ((h = gethostbyname(hostname)) == NULL) { printf("gethostbyname"); @@ -85,15 +85,15 @@ int is_scheme_char(int c) { } /* - 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 */ -parsed_url *parse_url(const char *url) { +http_url *http_url_parse(const char *url) { /* Define variable */ - parsed_url *purl; + http_url *purl; const char *tmpstr; const char *curstr; size_t len; @@ -102,7 +102,7 @@ parsed_url *parse_url(const char *url) { int bracket_flag; /* Allocate the parsed url storage */ - purl = (parsed_url *) calloc(1, sizeof(parsed_url)); + purl = (http_url *) calloc(1, sizeof(http_url)); if (NULL == purl) { return NULL; @@ -118,7 +118,7 @@ parsed_url *parse_url(const char *url) { /* Read scheme */ tmpstr = strchr(curstr, ':'); if (NULL == tmpstr) { - parsed_url_free(purl); + http_url_free(purl); fprintf(stderr, "Error on line %d (%s)\n", __LINE__, __FILE__); return NULL; @@ -131,7 +131,7 @@ parsed_url *parse_url(const char *url) { for (i = 0; i < len; i++) { if (is_scheme_char(curstr[i]) == 0) { /* Invalid format */ - parsed_url_free(purl); + http_url_free(purl); fprintf(stderr, "Error on line %d (%s)\n", __LINE__, __FILE__); return NULL; } @@ -139,7 +139,7 @@ parsed_url *parse_url(const char *url) { /* Copy the scheme to the storage */ purl->scheme = (char *) malloc(sizeof(char) * (len + 1)); if (NULL == purl->scheme) { - parsed_url_free(purl); + http_url_free(purl); fprintf(stderr, "Error on line %d (%s)\n", __LINE__, __FILE__); return NULL; @@ -164,7 +164,7 @@ parsed_url *parse_url(const char *url) { /* Eat "//" */ for (i = 0; i < 2; i++) { if ('/' != *curstr) { - parsed_url_free(purl); + http_url_free(purl); fprintf(stderr, "Error on line %d (%s)\n", __LINE__, __FILE__); return NULL; } @@ -197,7 +197,7 @@ parsed_url *parse_url(const char *url) { len = tmpstr - curstr; purl->username = (char *) malloc(sizeof(char) * (len + 1)); if (NULL == purl->username) { - parsed_url_free(purl); + http_url_free(purl); fprintf(stderr, "Error on line %d (%s)\n", __LINE__, __FILE__); return NULL; } @@ -218,7 +218,7 @@ parsed_url *parse_url(const char *url) { len = tmpstr - curstr; purl->password = (char *) malloc(sizeof(char) * (len + 1)); if (NULL == purl->password) { - parsed_url_free(purl); + http_url_free(purl); fprintf(stderr, "Error on line %d (%s)\n", __LINE__, __FILE__); return NULL; } @@ -228,7 +228,7 @@ parsed_url *parse_url(const char *url) { } /* Skip '@' */ if ('@' != *curstr) { - parsed_url_free(purl); + http_url_free(purl); fprintf(stderr, "Error on line %d (%s)\n", __LINE__, __FILE__); return NULL; } @@ -256,7 +256,7 @@ parsed_url *parse_url(const char *url) { len = tmpstr - curstr; purl->host = (char *) malloc(sizeof(char) * (len + 1)); if (NULL == purl->host || len <= 0) { - parsed_url_free(purl); + http_url_free(purl); fprintf(stderr, "Error on line %d (%s)\n", __LINE__, __FILE__); return NULL; } @@ -275,7 +275,7 @@ parsed_url *parse_url(const char *url) { len = tmpstr - curstr; purl->port = (char *) malloc(sizeof(char) * (len + 1)); if (NULL == purl->port) { - parsed_url_free(purl); + http_url_free(purl); fprintf(stderr, "Error on line %d (%s)\n", __LINE__, __FILE__); return NULL; } @@ -301,7 +301,7 @@ parsed_url *parse_url(const char *url) { /* Skip '/' */ if ('/' != *curstr) { - parsed_url_free(purl); + http_url_free(purl); fprintf(stderr, "Error on line %d (%s)\n", __LINE__, __FILE__); return NULL; } @@ -315,7 +315,7 @@ parsed_url *parse_url(const char *url) { len = tmpstr - curstr; purl->path = (char *) malloc(sizeof(char) * (len + 1)); if (NULL == purl->path) { - parsed_url_free(purl); + http_url_free(purl); fprintf(stderr, "Error on line %d (%s)\n", __LINE__, __FILE__); return NULL; } @@ -335,7 +335,7 @@ parsed_url *parse_url(const char *url) { len = tmpstr - curstr; purl->query = (char *) malloc(sizeof(char) * (len + 1)); if (NULL == purl->query) { - parsed_url_free(purl); + http_url_free(purl); fprintf(stderr, "Error on line %d (%s)\n", __LINE__, __FILE__); return NULL; } @@ -356,7 +356,7 @@ parsed_url *parse_url(const char *url) { len = tmpstr - curstr; purl->fragment = (char *) malloc(sizeof(char) * (len + 1)); if (NULL == purl->fragment) { - parsed_url_free(purl); + http_url_free(purl); fprintf(stderr, "Error on line %d (%s)\n", __LINE__, __FILE__); return NULL; } diff --git a/test/main.c b/test/main.c index 9fade27..0f2bb37 100644 --- a/test/main.c +++ b/test/main.c @@ -66,7 +66,7 @@ void request_header_cb(http_header *headers) { * @param chunk_len * @param headers */ -void response_body_cb(const char *chunk, size_t chunk_len, http_header *headers) { +void response_body_cb(const unsigned char *chunk, size_t chunk_len, http_header *headers) { if (chunk_len > 0) {