diff --git a/src/plugins/relay/relay-client.c b/src/plugins/relay/relay-client.c index 312b67404..e1567722f 100644 --- a/src/plugins/relay/relay-client.c +++ b/src/plugins/relay/relay-client.c @@ -2206,7 +2206,7 @@ relay_client_print_log () weechat_log_printf (" gnutls_handshake_ok . . . : %p", ptr_client->gnutls_handshake_ok); weechat_log_printf (" websocket . . . . . . . . ; %d", ptr_client->websocket); relay_websocket_deflate_print_log (ptr_client->ws_deflate, ""); - relay_http_print_log (ptr_client->http_req); + relay_http_print_log_request (ptr_client->http_req); weechat_log_printf (" address . . . . . . . . . : '%s'", ptr_client->address); weechat_log_printf (" real_ip . . . . . . . . . : '%s'", ptr_client->real_ip); weechat_log_printf (" status. . . . . . . . . . : %d (%s)", diff --git a/src/plugins/relay/relay-http.c b/src/plugins/relay/relay-http.c index e44cbc9c6..8b64deb23 100644 --- a/src/plugins/relay/relay-http.c +++ b/src/plugins/relay/relay-http.c @@ -344,7 +344,7 @@ relay_http_parse_method_path (struct t_relay_http_request *request, char **items; int num_items; - if (!method_path || !method_path[0]) + if (!request || !method_path || !method_path[0]) return 0; weechat_string_dyn_concat (request->raw, method_path, -1); @@ -1372,19 +1372,265 @@ relay_http_request_free (struct t_relay_http_request *request) free (request); } +/* + * Allocates a t_relay_http_response structure. + */ + +struct t_relay_http_response * +relay_http_response_alloc () +{ + struct t_relay_http_response *new_response; + + new_response = (struct t_relay_http_response *)malloc (sizeof (*new_response)); + if (!new_response) + return NULL; + + new_response->status = RELAY_HTTP_METHOD; + new_response->http_version = NULL; + new_response->return_code = 0; + new_response->message = NULL; + new_response->headers = weechat_hashtable_new ( + 32, + WEECHAT_HASHTABLE_STRING, + WEECHAT_HASHTABLE_STRING, + NULL, NULL); + new_response->content_length = 0; + new_response->body_size = 0; + new_response->body = NULL; + + return new_response; +} + +/* + * Parses and saves response code. + * + * Returns: + * 1: OK, response code and HTTP version saved + * 0: error: invalid format + */ + +int +relay_http_parse_response_code (struct t_relay_http_response *response, + const char *response_code) +{ + const char *pos, *pos2; + char *error, *return_code; + long value; + + if (!response) + return 0; + + if (!response_code || !response_code[0]) + { + response->status = RELAY_HTTP_END; + return 0; + } + + pos = strchr (response_code, ' '); + if (!pos) + goto error; + + if (response->http_version) + free (response->http_version); + response->http_version = weechat_strndup (response_code, pos - response_code); + + while (pos[0] == ' ') + { + pos++; + } + + pos2 = strchr (pos, ' '); + if (pos2) + return_code = weechat_strndup (pos, pos2 - pos); + else + return_code = strdup (pos); + if (!return_code) + goto error; + + error = NULL; + value = strtol (return_code, &error, 10); + if (error && !error[0]) + response->return_code = (int)value; + + free (return_code); + + if (pos2) + { + while (pos2[0] == ' ') + { + pos2++; + } + if (response->message) + free (response->message); + response->message = strdup (pos2); + } + + response->status = RELAY_HTTP_HEADERS; + + return 1; + +error: + response->status = RELAY_HTTP_END; + return 0; +} + +/* + * Parses and saves a header of a HTTP response in hashtable "headers". + * + * Returns: + * 1: OK, header saved + * 0: error: invalid format + */ + +int +relay_http_parse_response_header (struct t_relay_http_response *response, + const char *header) +{ + char *pos, *name, *name_lower, *error; + const char *ptr_value; + long number; + + /* empty line => end of headers */ + if (!header || !header[0]) + { + response->status = (response->content_length > 0) ? + RELAY_HTTP_BODY : RELAY_HTTP_END; + return 1; + } + + pos = strchr (header, ':'); + + /* not a valid header */ + if (!pos || (pos == header)) + return 0; + + /* get header name, which is case-insensitive */ + name = weechat_strndup (header, pos - header); + if (!name) + return 0; + name_lower = weechat_string_tolower (name); + if (!name_lower) + { + free (name); + return 0; + } + + /* get pointer on header value */ + ptr_value = pos + 1; + while (ptr_value[0] == ' ') + { + ptr_value++; + } + + /* add header in the hashtable */ + weechat_hashtable_set (response->headers, name_lower, ptr_value); + + /* if header is "Content-Length", save the length */ + if (strcmp (name_lower, "content-length") == 0) + { + error = NULL; + number = strtol (ptr_value, &error, 10); + if (error && !error[0]) + response->content_length = (int)number; + } + + free (name); + free (name_lower); + + return 1; +} + +/* + * Parses HTTP response with a string. + * + * Returns HTTP request structure, NULL if error. + */ + +struct t_relay_http_response * +relay_http_parse_response (const char *data) +{ + struct t_relay_http_response *http_resp; + const char *ptr_data, *pos; + char *line; + + if (!data || !data[0]) + return NULL; + + http_resp = relay_http_response_alloc (); + if (!http_resp) + return NULL; + + ptr_data = data; + while (ptr_data && ptr_data[0]) + { + if ((http_resp->status == RELAY_HTTP_METHOD) + || (http_resp->status == RELAY_HTTP_HEADERS)) + { + pos = strchr (ptr_data, '\r'); + if (!pos) + break; + line = weechat_strndup (ptr_data, pos - ptr_data); + if (!line) + break; + if (http_resp->status == RELAY_HTTP_METHOD) + relay_http_parse_response_code (http_resp, line); + else + relay_http_parse_response_header (http_resp, line); + free (line); + ptr_data = pos + 1; + if (ptr_data[0] == '\n') + ptr_data++; + } + else if (http_resp->status == RELAY_HTTP_BODY) + { + http_resp->body_size = strlen (ptr_data); + http_resp->body = malloc (http_resp->body_size); + if (http_resp->body) + memcpy (http_resp->body, ptr_data, http_resp->body_size); + http_resp->status = RELAY_HTTP_END; + } + else + break; + + if (http_resp->status == RELAY_HTTP_END) + break; + } + + return http_resp; +} + +/* + * Frees a HTTP response. + */ + +void +relay_http_response_free (struct t_relay_http_response *response) +{ + if (response->http_version) + free (response->http_version); + if (response->message) + free (response->message); + if (response->headers) + weechat_hashtable_free (response->headers); + if (response->body) + free (response->body); + + free (response); +} + /* * Prints HTTP request in WeeChat log file (usually for crash dump). */ void -relay_http_print_log (struct t_relay_http_request *request) +relay_http_print_log_request (struct t_relay_http_request *request) { int i; weechat_log_printf (" http_request:"); + weechat_log_printf (" status. . . . . . . . . : %d", request->status); weechat_log_printf (" raw . . . . . . . . . . : '%s'", (request->raw) ? *(request->raw) : NULL); - weechat_log_printf (" status. . . . . . . . . : %d", request->status); weechat_log_printf (" method. . . . . . . . . : '%s'", request->method); weechat_log_printf (" path. . . . . . . . . . : '%s'", request->path); weechat_log_printf (" path_items. . . . . . . : %p", request->path_items); @@ -1412,3 +1658,23 @@ relay_http_print_log (struct t_relay_http_request *request) weechat_log_printf (" body_size . . . . . . . : %d", request->body_size); weechat_log_printf (" body. . . . . . . . . . : '%s'", request->body); } + +/* + * Prints HTTP response in WeeChat log file (usually for crash dump). + */ + +void +relay_http_print_log_response (struct t_relay_http_response *response) +{ + weechat_log_printf (" http_response:"); + weechat_log_printf (" status. . . . . . . . . : %d", response->status); + weechat_log_printf (" http_version. . . . . . : '%s'", response->http_version); + weechat_log_printf (" return_code . . . . . . : %d", response->return_code); + weechat_log_printf (" message . . . . . . . . : '%s'", response->message); + weechat_log_printf (" headers . . . . . . . . : 0x%lx (hashtable: '%s')", + response->headers, + weechat_hashtable_get_string (response->headers, "keys_values")); + weechat_log_printf (" content_length. . . . . : %d", response->content_length); + weechat_log_printf (" body_size . . . . . . . : %d", response->body_size); + weechat_log_printf (" body. . . . . . . . . . : '%s'", response->body); +} diff --git a/src/plugins/relay/relay-http.h b/src/plugins/relay/relay-http.h index e3b6c2bf4..72d57346e 100644 --- a/src/plugins/relay/relay-http.h +++ b/src/plugins/relay/relay-http.h @@ -71,6 +71,19 @@ struct t_relay_http_request char *body; /* HTTP body (can be NULL) */ }; +struct t_relay_http_response +{ + enum t_relay_client_http_status status; /* HTTP status */ + char *http_version; /* HTTP version (eg: "HTTP/1.1") */ + int return_code; /* HTTP return code (eg: 200, 401) */ + char *message; /* message after return code */ + struct t_hashtable *headers; /* HTTP headers for websocket */ + /* and API protocol */ + int content_length; /* value of header "Content-Length" */ + int body_size; /* size of HTTP body read so far */ + char *body; /* HTTP body (can be NULL) */ +}; + extern void relay_http_request_reinit (struct t_relay_http_request *request); extern struct t_relay_http_request *relay_http_request_alloc (); extern int relay_http_get_param_boolean (struct t_relay_http_request *request, @@ -96,6 +109,10 @@ extern int relay_http_send_error_json (struct t_relay_client *client, const char *headers, const char *format, ...); extern void relay_http_request_free (struct t_relay_http_request *request); -extern void relay_http_print_log (struct t_relay_http_request *request); +extern struct t_relay_http_response *relay_http_response_alloc (); +extern struct t_relay_http_response *relay_http_parse_response (const char *data); +extern void relay_http_response_free (struct t_relay_http_response *response); +extern void relay_http_print_log_request (struct t_relay_http_request *request); +extern void relay_http_print_log_response (struct t_relay_http_response *response); #endif /* WEECHAT_PLUGIN_RELAY_HTTP_H */ diff --git a/tests/unit/plugins/relay/test-relay-http.cpp b/tests/unit/plugins/relay/test-relay-http.cpp index c5dda3d45..17254a051 100644 --- a/tests/unit/plugins/relay/test-relay-http.cpp +++ b/tests/unit/plugins/relay/test-relay-http.cpp @@ -56,6 +56,10 @@ extern char *relay_http_compress (struct t_relay_http_request *request, int *compressed_size, char *http_content_encoding, int http_content_encoding_size); +extern int relay_http_parse_response_code (struct t_relay_http_response *response, + const char *response_code); +extern int relay_http_parse_response_header (struct t_relay_http_response *response, + const char *header); } #define WEE_PARSE_PATH(__path) \ @@ -75,7 +79,7 @@ TEST_GROUP(RelayHttp) * relay_http_request_free */ -TEST(RelayHttp, AllocReinitFree) +TEST(RelayHttp, RequestAllocReinitFree) { struct t_relay_http_request *request; @@ -346,6 +350,7 @@ TEST(RelayHttp, ParseMethodPath) request = relay_http_request_alloc (); CHECK(request); + relay_http_parse_method_path (NULL, NULL); relay_http_parse_method_path (request, NULL); LONGS_EQUAL(RELAY_HTTP_METHOD, request->status); STRCMP_EQUAL("", *(request->raw)); @@ -1014,10 +1019,283 @@ TEST(RelayHttp, SendErrorJson) /* * Tests functions: - * relay_http_print_log + * relay_http_response_alloc + * relay_http_response_free */ -TEST(RelayHttp, PrintLog) +TEST(RelayHttp, ResponseAllocFree) +{ + struct t_relay_http_response *response; + + response = relay_http_response_alloc (); + CHECK(response); + + LONGS_EQUAL(RELAY_HTTP_METHOD, response->status); + POINTERS_EQUAL(NULL, response->http_version); + LONGS_EQUAL(0, response->return_code); + POINTERS_EQUAL(NULL, response->message); + CHECK(response->headers); + LONGS_EQUAL(0, response->headers->items_count); + LONGS_EQUAL(0, response->content_length); + LONGS_EQUAL(0, response->body_size); + POINTERS_EQUAL(NULL, response->body); + + relay_http_response_free (response); +} + +/* + * Tests functions: + * relay_http_parse_response_code + */ + +TEST(RelayHttp, ParseResponseCode) +{ + struct t_relay_http_response *response; + + response = relay_http_response_alloc (); + CHECK(response); + relay_http_parse_response_code (NULL, NULL); + LONGS_EQUAL(RELAY_HTTP_METHOD, response->status); + POINTERS_EQUAL(NULL, response->http_version); + LONGS_EQUAL(0, response->return_code); + POINTERS_EQUAL(NULL, response->message); + CHECK(response->headers); + LONGS_EQUAL(0, response->headers->items_count); + LONGS_EQUAL(0, response->content_length); + LONGS_EQUAL(0, response->body_size); + POINTERS_EQUAL(NULL, response->body); + relay_http_response_free (response); + + response = relay_http_response_alloc (); + CHECK(response); + relay_http_parse_response_code (response, NULL); + relay_http_parse_response_code (response, ""); + LONGS_EQUAL(RELAY_HTTP_END, response->status); + POINTERS_EQUAL(NULL, response->http_version); + LONGS_EQUAL(0, response->return_code); + POINTERS_EQUAL(NULL, response->message); + CHECK(response->headers); + LONGS_EQUAL(0, response->headers->items_count); + LONGS_EQUAL(0, response->content_length); + LONGS_EQUAL(0, response->body_size); + POINTERS_EQUAL(NULL, response->body); + relay_http_response_free (response); + + response = relay_http_response_alloc (); + CHECK(response); + relay_http_parse_response_code (response, "HTTP/1.1"); + LONGS_EQUAL(RELAY_HTTP_END, response->status); + POINTERS_EQUAL(NULL, response->http_version); + LONGS_EQUAL(0, response->return_code); + POINTERS_EQUAL(NULL, response->message); + CHECK(response->headers); + LONGS_EQUAL(0, response->headers->items_count); + LONGS_EQUAL(0, response->content_length); + LONGS_EQUAL(0, response->body_size); + POINTERS_EQUAL(NULL, response->body); + relay_http_response_free (response); + + response = relay_http_response_alloc (); + CHECK(response); + relay_http_parse_response_code (response, "HTTP/1.1 200"); + LONGS_EQUAL(RELAY_HTTP_HEADERS, response->status); + STRCMP_EQUAL("HTTP/1.1", response->http_version); + LONGS_EQUAL(200, response->return_code); + POINTERS_EQUAL(NULL, response->message); + CHECK(response->headers); + LONGS_EQUAL(0, response->headers->items_count); + LONGS_EQUAL(0, response->content_length); + LONGS_EQUAL(0, response->body_size); + POINTERS_EQUAL(NULL, response->body); + relay_http_response_free (response); + + response = relay_http_response_alloc (); + CHECK(response); + relay_http_parse_response_code (response, "HTTP/1.1 200 OK"); + LONGS_EQUAL(RELAY_HTTP_HEADERS, response->status); + STRCMP_EQUAL("HTTP/1.1", response->http_version); + LONGS_EQUAL(200, response->return_code); + STRCMP_EQUAL("OK", response->message); + CHECK(response->headers); + LONGS_EQUAL(0, response->headers->items_count); + LONGS_EQUAL(0, response->content_length); + LONGS_EQUAL(0, response->body_size); + POINTERS_EQUAL(NULL, response->body); + relay_http_response_free (response); + + response = relay_http_response_alloc (); + CHECK(response); + relay_http_parse_response_code (response, "HTTP/1.1 101 Switching Protocols"); + LONGS_EQUAL(RELAY_HTTP_HEADERS, response->status); + STRCMP_EQUAL("HTTP/1.1", response->http_version); + LONGS_EQUAL(101, response->return_code); + STRCMP_EQUAL("Switching Protocols", response->message); + CHECK(response->headers); + LONGS_EQUAL(0, response->headers->items_count); + LONGS_EQUAL(0, response->content_length); + LONGS_EQUAL(0, response->body_size); + POINTERS_EQUAL(NULL, response->body); + relay_http_response_free (response); + + + response = relay_http_response_alloc (); + CHECK(response); + relay_http_parse_response_code (response, "HTTP/1.1 401 Unauthorized"); + LONGS_EQUAL(RELAY_HTTP_HEADERS, response->status); + STRCMP_EQUAL("HTTP/1.1", response->http_version); + LONGS_EQUAL(401, response->return_code); + STRCMP_EQUAL("Unauthorized", response->message); + CHECK(response->headers); + LONGS_EQUAL(0, response->headers->items_count); + LONGS_EQUAL(0, response->content_length); + LONGS_EQUAL(0, response->body_size); + POINTERS_EQUAL(NULL, response->body); + relay_http_response_free (response); +} + +/* + * Tests functions: + * relay_http_parse_response_header + */ + +TEST(RelayHttp, ParseResponseHeader) +{ + struct t_relay_http_response *response; + + response = relay_http_response_alloc (); + CHECK(response); + relay_http_parse_response_code (response, "HTTP/1.1 200 OK"); + relay_http_parse_response_header (response, NULL); + LONGS_EQUAL(RELAY_HTTP_END, response->status); + STRCMP_EQUAL("HTTP/1.1", response->http_version); + LONGS_EQUAL(200, response->return_code); + STRCMP_EQUAL("OK", response->message); + LONGS_EQUAL(0, response->headers->items_count); + relay_http_response_free (response); + + response = relay_http_response_alloc (); + CHECK(response); + relay_http_parse_response_code (response, "HTTP/1.1 200 OK"); + relay_http_parse_response_header (response, ""); + LONGS_EQUAL(RELAY_HTTP_END, response->status); + STRCMP_EQUAL("HTTP/1.1", response->http_version); + LONGS_EQUAL(200, response->return_code); + STRCMP_EQUAL("OK", response->message); + LONGS_EQUAL(0, response->headers->items_count); + relay_http_response_free (response); + + response = relay_http_response_alloc (); + CHECK(response); + relay_http_parse_response_code (response, "HTTP/1.1 200 OK"); + relay_http_parse_response_header (response, "Test"); + LONGS_EQUAL(RELAY_HTTP_HEADERS, response->status); + STRCMP_EQUAL("HTTP/1.1", response->http_version); + LONGS_EQUAL(200, response->return_code); + STRCMP_EQUAL("OK", response->message); + LONGS_EQUAL(0, response->headers->items_count); + relay_http_response_free (response); + + response = relay_http_response_alloc (); + CHECK(response); + relay_http_parse_response_code (response, "HTTP/1.1 200 OK"); + relay_http_parse_response_header (response, "X-Test: value"); + LONGS_EQUAL(RELAY_HTTP_HEADERS, response->status); + STRCMP_EQUAL("HTTP/1.1", response->http_version); + LONGS_EQUAL(200, response->return_code); + STRCMP_EQUAL("OK", response->message); + LONGS_EQUAL(1, response->headers->items_count); + STRCMP_EQUAL("value", (const char *)hashtable_get (response->headers, "x-test")); + relay_http_response_free (response); + + response = relay_http_response_alloc (); + CHECK(response); + relay_http_parse_response_code (response, "HTTP/1.1 200 OK"); + relay_http_parse_response_header (response, "Content-Length: 123"); + LONGS_EQUAL(RELAY_HTTP_HEADERS, response->status); + STRCMP_EQUAL("HTTP/1.1", response->http_version); + LONGS_EQUAL(200, response->return_code); + STRCMP_EQUAL("OK", response->message); + LONGS_EQUAL(1, response->headers->items_count); + LONGS_EQUAL(123, response->content_length); + relay_http_response_free (response); +} + +/* + * Tests functions: + * relay_http_parse_response + */ + +TEST(RelayHttp, ParseResponse) +{ + struct t_relay_http_response *response; + + POINTERS_EQUAL(NULL, relay_http_parse_response (NULL)); + POINTERS_EQUAL(NULL, relay_http_parse_response ("")); + + response = relay_http_parse_response ("invalid"); + CHECK(response); + LONGS_EQUAL(RELAY_HTTP_METHOD, response->status); + POINTERS_EQUAL(NULL, response->http_version); + LONGS_EQUAL(0, response->return_code); + POINTERS_EQUAL(NULL, response->message); + CHECK(response->headers); + LONGS_EQUAL(0, response->headers->items_count); + LONGS_EQUAL(0, response->content_length); + LONGS_EQUAL(0, response->body_size); + POINTERS_EQUAL(NULL, response->body); + relay_http_response_free (response); + + response = relay_http_parse_response ("invalid\r\n"); + CHECK(response); + LONGS_EQUAL(RELAY_HTTP_END, response->status); + POINTERS_EQUAL(NULL, response->http_version); + LONGS_EQUAL(0, response->return_code); + POINTERS_EQUAL(NULL, response->message); + CHECK(response->headers); + LONGS_EQUAL(0, response->headers->items_count); + LONGS_EQUAL(0, response->content_length); + LONGS_EQUAL(0, response->body_size); + POINTERS_EQUAL(NULL, response->body); + relay_http_response_free (response); + + response = relay_http_parse_response ("HTTP/1.1 200 OK\r\n" + "Content-Type: application/json\r\n" + "Content-Length: 13\r\n" + "\r\n" + "{\"test\": 123}"); + CHECK(response); + LONGS_EQUAL(RELAY_HTTP_END, response->status); + STRCMP_EQUAL("HTTP/1.1", response->http_version); + LONGS_EQUAL(200, response->return_code); + STRCMP_EQUAL("OK", response->message); + CHECK(response->headers); + LONGS_EQUAL(2, response->headers->items_count); + STRCMP_EQUAL("application/json", (const char *)hashtable_get (response->headers, + "content-type")); + STRCMP_EQUAL("13", (const char *)hashtable_get (response->headers, + "content-length")); + LONGS_EQUAL(13, response->content_length); + LONGS_EQUAL(13, response->body_size); + STRCMP_EQUAL("{\"test\": 123}", response->body); + relay_http_response_free (response); +} + +/* + * Tests functions: + * relay_http_print_log_request + */ + +TEST(RelayHttp, PrintLogRequest) +{ + /* TODO: write tests */ +} + +/* + * Tests functions: + * relay_http_print_log_response + */ + +TEST(RelayHttp, PrintLogResponse) { /* TODO: write tests */ }