/* * Copyright (c) 2013-2014 Tim Ruehsen * Copyright (c) 2015-2024 Free Software Foundation, Inc. * * This file is part of Wget * * Wget 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. * * Wget 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 Wget If not, see . * * * Test suite function library * * Changelog * 16.01.2013 Tim Ruehsen created * * To create the X.509 stuff, I followed the instructions at * gnutls.org/manual/html_node/gnutls_002dserv-Invocation.html * */ #include #include #include #include #include #include #include #include #include #include #include #include #include "../src/wget_utils.h" #include "libtest.h" #include #ifndef HAVE_MHD_FREE # define MHD_free wget_free #endif #ifdef WITH_LIBNGHTTP2 # include # include #endif #ifndef MHD_HTTP_RANGE_NOT_SATISFIABLE # define MHD_HTTP_RANGE_NOT_SATISFIABLE MHD_HTTP_REQUESTED_RANGE_NOT_SATISFIABLE #endif #ifndef MHD_USE_TLS # define MHD_USE_TLS MHD_USE_SSL #endif #if MHD_VERSION <= 0x00097000 #undef MHD_NO #undef MHD_YES enum MHD_Result { MHD_NO = 0, MHD_YES = 1 }; #endif #include #include #include #ifdef WITH_GNUTLS_IN_TESTSUITE #ifdef WITH_GNUTLS_OCSP # include # include # include #endif # include # define file_load_err(fname, msg) wget_error_printf_exit("Couldn't load '%s' : %s\n", fname, msg) #endif static int http_server_port, https_server_port, ocsp_server_port, h2_server_port, keep_tmpfiles, clean_directory, reject_http_connection, reject_https_connection, ocsp_response_pos, h2_min_concurrent_streams; static wget_vector *request_urls, *ocsp_responses; static wget_test_url_t *urls; static size_t nurls; static char tmpdir[128]; static char server_send_content_length = 1; #if MHD_VERSION >= 0x00096302 && GNUTLS_VERSION_NUMBER >= 0x030603 static enum CHECK_POST_HANDSHAKE_AUTH { CHECK_ENABLED, CHECK_PASSED, CHECK_FAILED } *post_handshake_auth; #endif // MHD_Daemon instance static struct MHD_Daemon *httpdaemon, *httpsdaemon, *ocspdaemon; #ifdef WITH_GNUTLS_OCSP static gnutls_pcert_st *pcrt; static gnutls_privkey_t *privkey; typedef struct { char *data; size_t size; } ocsp_resp_t; #endif #ifdef WITH_GNUTLS_OCSP #if MHD_VERSION >= 0x00096502 && GNUTLS_VERSION_NUMBER >= 0x030603 static gnutls_ocsp_data_st *ocsp_stap_resp; #endif #endif #ifdef WITH_LIBNGHTTP2 typedef struct { nghttp2_session *session; gnutls_session_t tls_session; int sockfd; char *method; char *path; const char *response_body; // Points to test URL body size_t response_body_len; size_t response_body_offset; int32_t stream_id; char **header_names; // Allocated header names to free later char **header_values; // Allocated header values to free later int num_headers; // Number of allocated headers } h2_session_data; typedef struct { int listen_fd; pthread_t thread; volatile int running; gnutls_certificate_credentials_t creds; } h2_server_t; static h2_server_t *nghttp2_server; #endif // for passing URL query string struct query_string { wget_buffer *params; int it; }; static char *key_pem, *cert_pem; enum SERVER_MODE { HTTP_MODE, HTTPS_MODE, OCSP_MODE, OCSP_STAP_MODE, H2_MODE }; static enum PASS { HTTP_1_1_PASS, H2_PASS, END_PASS } proto_pass; static const char *_parse_hostname(const char* data) { if (data) { if (!wget_strncasecmp_ascii(data, "http://", 7)) { return strchr(data + 7, '/'); } if (!wget_strncasecmp_ascii(data, "https://", 8)) { return strchr(data + 8, '/'); } } return data; } static void _replace_space_with_plus(wget_buffer *buf, const char *data) { for (; *data; data++) wget_buffer_memcat(buf, *data == ' ' ? "+" : data, 1); } static enum MHD_Result _print_query_string( void *cls, enum MHD_ValueKind kind WGET_GCC_UNUSED, const char *key, const char *value) { struct query_string *query = cls; if (key && !query->it) { wget_buffer_strcpy(query->params, "?"); _replace_space_with_plus(query->params, key); if (value) { wget_buffer_strcat(query->params, "="); _replace_space_with_plus(query->params, value); } } if (key && query->it) { wget_buffer_strcat(query->params, "&"); _replace_space_with_plus(query->params, key); if (value) { wget_buffer_strcat(query->params, "="); _replace_space_with_plus(query->params, value); } } query->it++; return MHD_YES; } static enum MHD_Result _print_header_range( void *cls, enum MHD_ValueKind kind WGET_GCC_UNUSED, const char *key, const char *value) { wget_buffer *header_range = cls; if (!strcasecmp(key, MHD_HTTP_HEADER_RANGE)) { wget_buffer_strcpy(header_range, key); if (value) { wget_buffer_strcat(header_range, value); } } return MHD_YES; } struct ResponseContentCallbackParam { const char *response_data; size_t response_size; interrupt_response_mode_t interrupt_response_mode; size_t interrupt_response_after_nbytes; }; static ssize_t _callback (void *cls, uint64_t pos, char *buf, size_t buf_size) { size_t size_to_copy; struct ResponseContentCallbackParam *const param = (struct ResponseContentCallbackParam *)cls; if (pos >= param->response_size) return MHD_CONTENT_READER_END_OF_STREAM; // divide data into two chunks buf_size = (param->response_size / 2) + 1; if (buf_size < (param->response_size - pos)) size_to_copy = buf_size; else size_to_copy = param->response_size - pos; memcpy(buf, param->response_data + pos, size_to_copy); return size_to_copy; } static ssize_t _callback_interruptable (void *cls, uint64_t pos, char *buf, size_t buf_size) { size_t size_to_copy; struct ResponseContentCallbackParam *const param = (struct ResponseContentCallbackParam *)cls; if (pos >= param->response_size) return MHD_CONTENT_READER_END_OF_STREAM; if (buf_size <= (param->response_size - pos)) { size_to_copy = buf_size; } else { size_to_copy = param->response_size - pos; } if (param->interrupt_response_mode != INTERRUPT_RESPONSE_DISABLED) { if (pos >= param->interrupt_response_after_nbytes) { return MHD_CONTENT_READER_END_WITH_ERROR; } if (size_to_copy > (param->interrupt_response_after_nbytes - pos)) { size_to_copy = param->interrupt_response_after_nbytes - pos; } } memcpy(buf, param->response_data + pos, size_to_copy); return size_to_copy; } static void _free_callback_param(void *cls) { wget_free(cls); } #ifdef WITH_GNUTLS_OCSP static enum MHD_Result _ocsp_ahc( void *cls WGET_GCC_UNUSED, struct MHD_Connection *connection, const char *url WGET_GCC_UNUSED, const char *method WGET_GCC_UNUSED, const char *version WGET_GCC_UNUSED, const char *upload_data, size_t *upload_data_size, void **con_cls WGET_GCC_UNUSED) { static bool first = true; if (first && upload_data == NULL) { first = false; return MHD_YES; } else if (!first && upload_data == NULL) { int ret = 0; ocsp_resp_t *ocsp_resp = wget_vector_get(ocsp_responses, ocsp_response_pos++); if (ocsp_resp) { struct MHD_Response *response = MHD_create_response_from_buffer (ocsp_resp->size, ocsp_resp->data, MHD_RESPMEM_MUST_COPY); ret = MHD_queue_response (connection, MHD_HTTP_OK, response); MHD_destroy_response (response); } return ret; } *upload_data_size = 0; return MHD_YES; } static int _ocsp_cert_callback( gnutls_session_t session WGET_GCC_UNUSED, const gnutls_datum_t* req_ca_dn WGET_GCC_UNUSED, int nreqs WGET_GCC_UNUSED, const gnutls_pk_algorithm_t* pk_algos WGET_GCC_UNUSED, int pk_algos_length WGET_GCC_UNUSED, gnutls_pcert_st** pcert, unsigned int *pcert_length, gnutls_privkey_t *pkey) { *pcert = pcrt; *(pcert+1) = pcrt+1; *pkey = *privkey; *pcert_length = 2; return 0; } #if MHD_VERSION >= 0x00096502 && GNUTLS_VERSION_NUMBER >= 0x030603 static int _ocsp_stap_cert_callback( gnutls_session_t session WGET_GCC_UNUSED, const struct gnutls_cert_retr_st *info WGET_GCC_UNUSED, gnutls_pcert_st **certs, unsigned int *pcert_length, gnutls_ocsp_data_st **ocsp, unsigned int *ocsp_length, gnutls_privkey_t *pkey, unsigned int *flags WGET_GCC_UNUSED) { *certs = pcrt; *(certs+1) = pcrt+1; *pcert_length = 2; *pkey = *privkey; *ocsp = ocsp_stap_resp; *ocsp_length = 1; return 0; } #endif #endif static enum MHD_Result _answer_to_connection( void *cls WGET_GCC_UNUSED, struct MHD_Connection *connection, const char *url, const char *method, const char *version WGET_GCC_UNUSED, const char *upload_data WGET_GCC_UNUSED, size_t *upload_data_size WGET_GCC_UNUSED, void **con_cls WGET_GCC_UNUSED) { #if MHD_VERSION >= 0x00096302 && GNUTLS_VERSION_NUMBER >= 0x030603 if (post_handshake_auth) { gnutls_session_t tls_sess; const union MHD_ConnectionInfo *conn_info = MHD_get_connection_info (connection, MHD_CONNECTION_INFO_GNUTLS_SESSION); if (conn_info) { int check_auth; tls_sess = conn_info->tls_session; gnutls_certificate_server_set_request(tls_sess, GNUTLS_CERT_REQUEST); do check_auth = gnutls_reauth(tls_sess, 0); while (check_auth == GNUTLS_E_AGAIN); *post_handshake_auth = (check_auth == GNUTLS_E_SUCCESS) ? CHECK_PASSED : CHECK_FAILED; } } #endif struct MHD_Response *response = NULL; struct query_string query; int ret = 0; int64_t modified; const char *modified_val, *to_bytes_string = ""; ssize_t from_bytes, to_bytes; char content_len[100], content_range[100]; // whether or not this connection is HTTPS bool https = !!MHD_get_connection_info(connection, MHD_CONNECTION_INFO_PROTOCOL); // get query string query.params = wget_buffer_alloc(1024); query.it = 0; MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, _print_query_string, &query); // get if-modified-since header modified_val = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_IF_MODIFIED_SINCE); modified = 0; if (modified_val) modified = wget_http_parse_full_date(modified_val); // get header range wget_buffer *header_range = wget_buffer_alloc(1024); if (!strcmp(method, "GET")) MHD_get_connection_values(connection, MHD_HEADER_KIND, _print_header_range, header_range); from_bytes = to_bytes = 0; if (*header_range->data) { const char *from_bytes_string; const char *range_string = strchr(header_range->data, '='); to_bytes_string = strchr(range_string, '-'); if (strcmp(to_bytes_string, "-")) to_bytes = (ssize_t) atoi(to_bytes_string + 1); from_bytes_string = wget_strmemdup(range_string, to_bytes_string - range_string); from_bytes = (ssize_t) atoi(from_bytes_string + 1); wget_xfree(from_bytes_string); } // append 'index.html' to directory and append query string const char *url_full, *p; if ((p = strrchr(url, '/')) && p[1] == 0) { url_full = wget_aprintf("%sindex.html%s", url, query.params->data ? query.params->data : ""); } else { url_full = wget_aprintf("%s%s", url, query.params->data ? query.params->data : ""); } wget_buffer_free(&query.params); // iterate over test urls array bool found = false, chunked = false; char *url_iri = NULL; for (wget_test_url_t *request_url = urls; request_url < urls + nurls; request_url++) { if (request_url->http_only && https) continue; if (request_url->https_only && !https) continue; // convert remote url into escaped char for iri encoding wget_xfree(url_iri); url_iri = wget_strdup(request_url->name); MHD_http_unescape(url_iri); if (!strcmp(_parse_hostname(url_full), _parse_hostname(url_iri))) { size_t body_length = request_url->body_len ? request_url->body_len : (request_url->body ? strlen(request_url->body) : 0); // check request headers bool bad_request = false; if (request_url->expected_method && strcmp(method, request_url->expected_method)) { wget_debug_printf("%s: Expected request method '%s', but got '%s'\n", __func__, request_url->expected_method, method); bad_request = true; } for (const char **header = request_url->expected_req_headers; *header; header++) { const char *header_value = strchr(*header, ':'); const char *header_key = wget_strmemdup(*header, header_value - *header); const char *got_val = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, header_key); wget_xfree(header_key); // 400 Bad Request if (!got_val || strcmp(got_val, header_value + 2)) { wget_debug_printf("%s: Missing expected header '%s'\n", __func__, *header); bad_request = true; break; } } // check unexpected headers for (const char **header_key = request_url->unexpected_req_headers; *header_key; header_key++) { const char *got_val = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, *header_key); // 400 Bad Request if (got_val) { wget_debug_printf("%s: Got unexpected header '%s'\n", __func__, *header_key); bad_request = true; break; } } // return with "400 Bad Request" if (bad_request) { response = MHD_create_response_from_buffer(0, (void *) "", MHD_RESPMEM_PERSISTENT); ret = MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, response); found = true; break; } // chunked encoding if (!wget_strcmp(request_url->name + 3, "bad.txt")) { response = MHD_create_response_from_buffer(body_length, (void *) request_url->body, MHD_RESPMEM_MUST_COPY); ret = MHD_queue_response(connection, MHD_HTTP_OK, response); MHD_add_response_header(response, "Transfer-Encoding", "chunked"); MHD_add_response_header(response, "Connection", "close"); found = true; break; } for (const char **header = request_url->headers; *header; header++) { const char *header_value = strchr(*header, ':'); const char *header_key = wget_strmemdup(*header, header_value - *header); if (!strcmp(header_key, "Transfer-Encoding") && !strcmp(header_value + 2, "chunked")) chunked = true; wget_xfree(header_key); } if (chunked) { struct ResponseContentCallbackParam *callback_param = wget_malloc(sizeof(struct ResponseContentCallbackParam)); callback_param->response_data = request_url->body; callback_param->response_size = body_length; response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 1024, _callback, callback_param, _free_callback_param); ret = MHD_queue_response(connection, MHD_HTTP_OK, response); found = true; break; } // redirection if (atoi(request_url->code)/100 == 3) { response = MHD_create_response_from_buffer(0, (void *) "", MHD_RESPMEM_PERSISTENT); // add available headers for (const char **header = request_url->headers; *header; header++) { const char *header_value = strchr(*header, ':'); const char *header_key = wget_strmemdup(*header, header_value - *header); MHD_add_response_header(response, header_key, header_value + 2); wget_xfree(header_key); } ret = MHD_queue_response(connection, atoi(request_url->code), response); found = true; break; } // 404 with non-empty "body" if (atoi(request_url->code) != 200) { response = MHD_create_response_from_buffer(body_length, (void *) request_url->body, MHD_RESPMEM_MUST_COPY); ret = MHD_queue_response(connection, atoi(request_url->code), response); found = true; break; } // basic authentication if (!wget_strcmp(request_url->auth_method, "Basic")) { char *pass = NULL; char *user = MHD_basic_auth_get_username_password(connection, &pass); if ((user == NULL && pass == NULL) || wget_strcmp(user, request_url->auth_username) || wget_strcmp(pass, request_url->auth_password)) { response = MHD_create_response_from_buffer(strlen ("DENIED"), (void *) "DENIED", MHD_RESPMEM_PERSISTENT); ret = MHD_queue_basic_auth_fail_response(connection, "basic@example.com", response); MHD_free(user); MHD_free(pass); found = true; break; } MHD_free(user); MHD_free(pass); } // digest authentication if (!wget_strcmp(request_url->auth_method, "Digest")) { const char *realm = "digest@example.com"; char *user = MHD_digest_auth_get_username(connection); if (wget_strcmp(user, request_url->auth_username)) { response = MHD_create_response_from_buffer(strlen ("DENIED"), (void *) "DENIED", MHD_RESPMEM_PERSISTENT); ret = MHD_queue_auth_fail_response(connection, realm, TEST_OPAQUE_STR, response, MHD_NO); MHD_free(user); found = true; break; } ret = MHD_digest_auth_check(connection, realm, user, request_url->auth_password, 300); MHD_free(user); if ((ret == MHD_INVALID_NONCE) || (ret == MHD_NO)) { response = MHD_create_response_from_buffer(strlen ("DENIED"), (void *) "DENIED", MHD_RESPMEM_PERSISTENT); if (response) { ret = MHD_queue_auth_fail_response(connection, realm, TEST_OPAQUE_STR, response, (ret == MHD_INVALID_NONCE) ? MHD_YES : MHD_NO); found = true; } else ret = MHD_NO; break; } } if (modified && request_url->modified <= modified) { response = MHD_create_response_from_buffer(0, (void *) "", MHD_RESPMEM_PERSISTENT); ret = MHD_queue_response(connection, MHD_HTTP_NOT_MODIFIED, response); } else if (*header_range->data) { if (!strcmp(to_bytes_string, "-")) to_bytes = body_length - 1; size_t body_len = to_bytes - from_bytes + 1; if (from_bytes > to_bytes || from_bytes >= (int) body_length) { response = MHD_create_response_from_buffer(0, (void *) "", MHD_RESPMEM_PERSISTENT); ret = MHD_queue_response(connection, MHD_HTTP_RANGE_NOT_SATISFIABLE, response); } else { if (request_url->interrupt_response_mode != INTERRUPT_RESPONSE_DISABLED) { struct ResponseContentCallbackParam *callback_param = wget_malloc(sizeof(struct ResponseContentCallbackParam)); callback_param->response_data = (void *) (request_url->body + from_bytes); callback_param->response_size = body_len; callback_param->interrupt_response_mode = request_url->interrupt_response_mode; callback_param->interrupt_response_after_nbytes = request_url->interrupt_response_after_nbytes; response = MHD_create_response_from_callback(body_len, 1024, _callback_interruptable, callback_param, _free_callback_param); } else { response = MHD_create_response_from_buffer(body_len, (void *) (request_url->body + from_bytes), MHD_RESPMEM_MUST_COPY); } MHD_add_response_header(response, MHD_HTTP_HEADER_ACCEPT_RANGES, "bytes"); wget_snprintf(content_range, sizeof(content_range), "%zd-%zd/%zu", from_bytes, to_bytes, body_len); MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_RANGE, content_range); wget_snprintf(content_len, sizeof(content_len), "%zu", body_len); MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_LENGTH, content_len); ret = MHD_queue_response(connection, MHD_HTTP_PARTIAL_CONTENT, response); } } else { if (request_url->interrupt_response_mode != INTERRUPT_RESPONSE_DISABLED) { struct ResponseContentCallbackParam *callback_param = wget_malloc(sizeof(struct ResponseContentCallbackParam)); callback_param->response_data = request_url->body; callback_param->response_size = body_length; callback_param->interrupt_response_mode = request_url->interrupt_response_mode; callback_param->interrupt_response_after_nbytes = request_url->interrupt_response_after_nbytes; response = MHD_create_response_from_callback(body_length, 1024, _callback_interruptable, callback_param, _free_callback_param); } else { response = MHD_create_response_from_buffer(body_length, (void *) request_url->body, MHD_RESPMEM_MUST_COPY); } ret = MHD_queue_response(connection, MHD_HTTP_OK, response); } // switch off Content-Length sanity checks #if MHD_VERSION >= 0x00096800 MHD_set_response_options(response, MHD_RF_INSANITY_HEADER_CONTENT_LENGTH, MHD_RO_END); #endif // add available headers for (const char **header = request_url->headers; *header; header++) { const char *header_value = strchr(*header, ':'); const char *header_key = wget_strmemdup(*header, header_value - *header); MHD_add_response_header(response, header_key, header_value + 2); wget_xfree(header_key); } found = true; } } // 404 with empty "body" if (!found) { response = MHD_create_response_from_buffer(0, (void *) "", MHD_RESPMEM_PERSISTENT); ret = MHD_queue_response(connection, MHD_HTTP_NOT_FOUND, response); } wget_xfree(url_iri); wget_xfree(url_full); wget_buffer_free(&header_range); char server_version[50]; wget_snprintf(server_version, sizeof(server_version), "Libmicrohttpd/%08x", (unsigned int) MHD_VERSION); MHD_add_response_header(response, "Server", server_version); MHD_destroy_response(response); return ret; } #ifdef WITH_LIBNGHTTP2 static ssize_t h2_send_callback(nghttp2_session *session WGET_GCC_UNUSED, const uint8_t *data, size_t length, int flags WGET_GCC_UNUSED, void *user_data) { h2_session_data *session_data = (h2_session_data *) user_data; ssize_t nwrite = gnutls_record_send(session_data->tls_session, data, length); if (nwrite < 0) { if (nwrite == GNUTLS_E_AGAIN || nwrite == GNUTLS_E_INTERRUPTED) return NGHTTP2_ERR_WOULDBLOCK; return NGHTTP2_ERR_CALLBACK_FAILURE; } return nwrite; } static ssize_t h2_recv_callback(nghttp2_session *session WGET_GCC_UNUSED, uint8_t *buf, size_t length, int flags WGET_GCC_UNUSED, void *user_data) { h2_session_data *session_data = (h2_session_data *) user_data; ssize_t nread = gnutls_record_recv(session_data->tls_session, buf, length); if (nread < 0) { if (nread == GNUTLS_E_AGAIN || nread == GNUTLS_E_INTERRUPTED) return NGHTTP2_ERR_WOULDBLOCK; return NGHTTP2_ERR_CALLBACK_FAILURE; } if (nread == 0) return NGHTTP2_ERR_EOF; return nread; } static ssize_t h2_data_read_callback(nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t length, uint32_t *data_flags, nghttp2_data_source *source WGET_GCC_UNUSED, void *user_data WGET_GCC_UNUSED) { h2_session_data *stream_data = nghttp2_session_get_stream_user_data(session, stream_id); if (!stream_data || !stream_data->response_body) return 0; size_t remaining = stream_data->response_body_len - stream_data->response_body_offset; // Limit chunk size to 8KB to allow proper rate limiting on the client side // Without this limit, large responses are sent too quickly and bypass rate limiting // Smaller chunks allow better rate limiting granularity, especially with HTTP/2 multiplexing static const size_t max_chunk = 8 * 1024; if (length > max_chunk) length = max_chunk; size_t to_send = remaining < length ? remaining : length; if (to_send > 0) { memcpy(buf, stream_data->response_body + stream_data->response_body_offset, to_send); stream_data->response_body_offset += to_send; } if (stream_data->response_body_offset >= stream_data->response_body_len) *data_flags |= NGHTTP2_DATA_FLAG_EOF; return (ssize_t) to_send; } static int h2_on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, const uint8_t *name, size_t namelen, const uint8_t *value, size_t valuelen, uint8_t flags WGET_GCC_UNUSED, void *user_data WGET_GCC_UNUSED) { h2_session_data *session_data = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); if (!session_data || frame->hd.type != NGHTTP2_HEADERS) return 0; if (frame->headers.cat != NGHTTP2_HCAT_REQUEST) return 0; // Parse :method and :path pseudo-headers if (namelen == 7 && memcmp(name, ":method", 7) == 0) { session_data->method = wget_strmemdup((const char *) value, valuelen); } else if (namelen == 5 && memcmp(name, ":path", 5) == 0) { session_data->path = wget_strmemdup((const char *) value, valuelen); } return 0; } static int h2_on_begin_headers_callback(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) { if (frame->hd.type != NGHTTP2_HEADERS || frame->headers.cat != NGHTTP2_HCAT_REQUEST) return 0; h2_session_data *stream_data = wget_calloc(1, sizeof(h2_session_data)); if (!stream_data) return NGHTTP2_ERR_CALLBACK_FAILURE; h2_session_data *session_data = (h2_session_data *)user_data; stream_data->session = session; stream_data->tls_session = session_data->tls_session; stream_data->sockfd = session_data->sockfd; stream_data->stream_id = frame->hd.stream_id; stream_data->response_body = NULL; stream_data->response_body_len = 0; stream_data->response_body_offset = 0; nghttp2_session_set_stream_user_data(session, frame->hd.stream_id, stream_data); return 0; } static int h2_on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame, void *user_data WGET_GCC_UNUSED) { if (frame->hd.type != NGHTTP2_HEADERS || frame->headers.cat != NGHTTP2_HCAT_REQUEST) return 0; h2_session_data *stream_data = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); if (!stream_data || !stream_data->path) return 0; // Find matching URL in test definitions const char *path = stream_data->path; if (*path == '/') path++; wget_test_url_t *url = NULL; for (size_t i = 0; i < nurls; i++) { const char *url_name = urls[i].name; if (*url_name == '/') url_name++; if (strcmp(path, url_name) == 0) { url = &urls[i]; break; } } // Prepare response const char *status = "404 Not Found"; const char *body = "Not Found"; size_t body_len = strlen(body); if (url) { status = url->code; body = url->body; body_len = url->body_len ? url->body_len : (body ? strlen(body) : 0); } // Store response body in stream context stream_data->response_body = body; stream_data->response_body_len = body_len; stream_data->response_body_offset = 0; // Parse status code int status_code = 404; if (status) status_code = atoi(status); // Build response headers char status_str[16]; wget_snprintf(status_str, sizeof(status_str), "%d", status_code); #define MAX_HEADERS 20 nghttp2_nv hdrs[MAX_HEADERS]; int nhdrs = 0; hdrs[nhdrs].name = (uint8_t *) ":status"; hdrs[nhdrs].namelen = strlen((char *) hdrs[nhdrs].name); hdrs[nhdrs].value = (uint8_t *) status_str; hdrs[nhdrs].valuelen = strlen(status_str); hdrs[nhdrs].flags = NGHTTP2_NV_FLAG_NONE; nhdrs++; hdrs[nhdrs].name = (uint8_t *) "server"; hdrs[nhdrs].namelen = strlen((char *) hdrs[nhdrs].name); hdrs[nhdrs].value = (uint8_t *) "libwget-test-h2/1.0"; hdrs[nhdrs].valuelen = strlen((char *) hdrs[nhdrs].value); hdrs[nhdrs].flags = NGHTTP2_NV_FLAG_NONE; nhdrs++; // Add custom headers from test definition if (url) { for (int i = 0; i < 10 && url->headers[i] && nhdrs < MAX_HEADERS - 1; i++) { const char *header = url->headers[i]; const char *colon = strchr(header, ':'); if (colon) { size_t name_len = colon - header; const char *value = colon + 1; while (*value == ' ') value++; // Convert header name to lowercase for HTTP/2 char *name_lower = wget_strmemdup(header, name_len); for (size_t j = 0; j < name_len; j++) name_lower[j] = (char) tolower((unsigned char) name_lower[j]); hdrs[nhdrs].name = (uint8_t *) name_lower; hdrs[nhdrs].namelen = name_len; hdrs[nhdrs].value = (uint8_t *) wget_strdup(value); hdrs[nhdrs].valuelen = strlen(value); hdrs[nhdrs].flags = NGHTTP2_NV_FLAG_NONE; nhdrs++; } } } // Setup data provider for response body nghttp2_data_provider data_prov; data_prov.read_callback = h2_data_read_callback; // Store allocated headers in stream_data so we can free them later // (nghttp2 doesn't copy header data, so they must remain valid) if (nhdrs > 2) { stream_data->num_headers = nhdrs - 2; // Exclude :status and server headers stream_data->header_names = wget_malloc(stream_data->num_headers * sizeof(char *)); stream_data->header_values = wget_malloc(stream_data->num_headers * sizeof(char *)); for (int i = 0; i < stream_data->num_headers; i++) { stream_data->header_names[i] = (char *) hdrs[i + 2].name; stream_data->header_values[i] = (char *) hdrs[i + 2].value; } } else { stream_data->header_names = NULL; stream_data->header_values = NULL; stream_data->num_headers = 0; } // Submit response with data provider int rv = nghttp2_submit_response(session, stream_data->stream_id, hdrs, nhdrs, body_len > 0 ? &data_prov : NULL); return (rv == 0) ? 0 : NGHTTP2_ERR_CALLBACK_FAILURE; } static int h2_on_stream_close_callback(nghttp2_session *session, int32_t stream_id, uint32_t error_code WGET_GCC_UNUSED, void *user_data WGET_GCC_UNUSED) { h2_session_data *stream_data = nghttp2_session_get_stream_user_data(session, stream_id); if (stream_data) { wget_free(stream_data->method); wget_free(stream_data->path); // Free allocated header strings if (stream_data->header_names) { for (int i = 0; i < stream_data->num_headers; i++) { wget_free(stream_data->header_names[i]); wget_free(stream_data->header_values[i]); } wget_free(stream_data->header_names); wget_free(stream_data->header_values); } wget_free(stream_data); } return 0; } static void *h2_server_thread(void *arg WGET_GCC_UNUSED) { if (!nghttp2_server) return NULL; while (nghttp2_server->running) { struct sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); if (wget_ready_2_read(nghttp2_server->listen_fd, 100) <= 0) continue; int client_fd = accept(nghttp2_server->listen_fd, (struct sockaddr *) &client_addr, &client_len); if (client_fd < 0) continue; // Setup TLS (keep blocking for handshake) gnutls_session_t tls_session; gnutls_init(&tls_session, GNUTLS_SERVER); gnutls_priority_set_direct(tls_session, "NORMAL:-VERS-ALL:+VERS-TLS1.3:+VERS-TLS1.2", NULL); gnutls_credentials_set(tls_session, GNUTLS_CRD_CERTIFICATE, nghttp2_server->creds); // ALPN for h2 gnutls_datum_t alpn_protos[1]; alpn_protos[0].data = (unsigned char *)"h2"; alpn_protos[0].size = 2; gnutls_alpn_set_protocols(tls_session, alpn_protos, 1, 0); gnutls_transport_set_int(tls_session, client_fd); gnutls_handshake_set_timeout(tls_session, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT); // TLS handshake (blocking) int ret = gnutls_handshake(tls_session); if (ret < 0) { gnutls_deinit(tls_session); close(client_fd); continue; } // Make socket non-blocking after handshake int flags = fcntl(client_fd, F_GETFL, 0); fcntl(client_fd, F_SETFL, flags | O_NONBLOCK); gnutls_transport_set_int(tls_session, client_fd); // Verify ALPN selected h2 gnutls_datum_t alpn; ret = gnutls_alpn_get_selected_protocol(tls_session, &alpn); if (ret < 0 || alpn.size != 2 || memcmp(alpn.data, "h2", 2) != 0) { gnutls_bye(tls_session, GNUTLS_SHUT_RDWR); gnutls_deinit(tls_session); close(client_fd); continue; } // Create nghttp2 session h2_session_data session_data = { .session = NULL, .tls_session = tls_session, .sockfd = client_fd, .method = NULL, .path = NULL, .response_body = NULL, .response_body_len = 0, .response_body_offset = 0, .stream_id = 0 }; nghttp2_session_callbacks *callbacks; nghttp2_session_callbacks_new(&callbacks); nghttp2_session_callbacks_set_send_callback(callbacks, h2_send_callback); nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, h2_on_frame_recv_callback); nghttp2_session_callbacks_set_on_stream_close_callback(callbacks, h2_on_stream_close_callback); nghttp2_session_callbacks_set_on_header_callback(callbacks, h2_on_header_callback); nghttp2_session_callbacks_set_on_begin_headers_callback(callbacks, h2_on_begin_headers_callback); nghttp2_session_callbacks_set_recv_callback(callbacks, h2_recv_callback); nghttp2_session_server_new(&session_data.session, callbacks, &session_data); nghttp2_session_callbacks_del(callbacks); // Send server connection preface (SETTINGS frame) nghttp2_settings_entry iv[2] = { {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}, {NGHTTP2_SETTINGS_ENABLE_PUSH, 0} }; nghttp2_submit_settings(session_data.session, NGHTTP2_FLAG_NONE, iv, 2); // Event loop for this connection while (nghttp2_server->running) { // Check if we want to read or write int mode = nghttp2_session_want_read(session_data.session) != 0 ? WGET_IO_READABLE : 0; mode |= nghttp2_session_want_write(session_data.session) != 0 ? WGET_IO_WRITABLE : 0; if (mode == 0) break; int rc; if ((rc = wget_ready_2_transfer(client_fd, 1000, WGET_IO_READABLE | WGET_IO_WRITABLE)) <= 0) continue; if (rc & WGET_IO_READABLE) { int rv = nghttp2_session_recv(session_data.session); if (rv != 0 && rv != NGHTTP2_ERR_WOULDBLOCK) break; } if (rc & WGET_IO_WRITABLE) { int rv = nghttp2_session_send(session_data.session); if (rv != 0 && rv != NGHTTP2_ERR_WOULDBLOCK) break; } } // Cleanup nghttp2_session_del(session_data.session); gnutls_bye(tls_session, GNUTLS_SHUT_RDWR); gnutls_deinit(tls_session); close(client_fd); } return NULL; } static int h2_server_start(void) { if (nghttp2_server) return 0; // Already running nghttp2_server = wget_calloc(1, sizeof(h2_server_t)); if (!nghttp2_server) return -1; // Create listen socket nghttp2_server->listen_fd = socket(AF_INET, SOCK_STREAM, 0); if (nghttp2_server->listen_fd < 0) { wget_free(nghttp2_server); nghttp2_server = NULL; return -1; } int optval = 1; setsockopt(nghttp2_server->listen_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)); struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); server_addr.sin_port = 0; // Let OS assign port if (bind(nghttp2_server->listen_fd, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0) { close(nghttp2_server->listen_fd); wget_free(nghttp2_server); nghttp2_server = NULL; return -1; } // Get assigned port socklen_t addr_len = sizeof(server_addr); if (getsockname(nghttp2_server->listen_fd, (struct sockaddr *) &server_addr, &addr_len) < 0) { close(nghttp2_server->listen_fd); wget_free(nghttp2_server); nghttp2_server = NULL; return -1; } h2_server_port = ntohs(server_addr.sin_port); if (listen(nghttp2_server->listen_fd, 5) < 0) { close(nghttp2_server->listen_fd); wget_free(nghttp2_server); nghttp2_server = NULL; return -1; } // Setup TLS credentials gnutls_certificate_allocate_credentials(&nghttp2_server->creds); gnutls_certificate_set_x509_key_file(nghttp2_server->creds, SRCDIR "/certs/x509-server-cert.pem", SRCDIR "/certs/x509-server-key.pem", GNUTLS_X509_FMT_PEM); // Start server thread nghttp2_server->running = 1; if (pthread_create(&nghttp2_server->thread, NULL, h2_server_thread, NULL) != 0) { gnutls_certificate_free_credentials(nghttp2_server->creds); close(nghttp2_server->listen_fd); wget_free(nghttp2_server); nghttp2_server = NULL; return -1; } wget_info_printf("HTTP/2 server started on port %d\n", h2_server_port); return 0; } static void h2_server_stop(void) { if (!nghttp2_server) return; nghttp2_server->running = 0; // Shutdown the listen socket to unblock accept() shutdown(nghttp2_server->listen_fd, SHUT_RDWR); pthread_join(nghttp2_server->thread, NULL); gnutls_certificate_free_credentials(nghttp2_server->creds); close(nghttp2_server->listen_fd); wget_free(nghttp2_server); nghttp2_server = NULL; h2_server_port = 0; } #endif // WITH_LIBNGHTTP2 static void _http_server_stop(void) { MHD_stop_daemon(httpdaemon); MHD_stop_daemon(httpsdaemon); MHD_stop_daemon(ocspdaemon); #ifdef WITH_LIBNGHTTP2 h2_server_stop(); #endif wget_xfree(key_pem); wget_xfree(cert_pem); // Don't call gnutls_global_deinit() here as it breaks subsequent tests // that need TLS. GnuTLS will clean up automatically at process exit. } static enum MHD_Result _check_to_accept( void *cls, WGET_GCC_UNUSED const struct sockaddr *addr, WGET_GCC_UNUSED socklen_t addrlen) { int server_mode = (int) (ptrdiff_t) cls; if (server_mode == HTTP_MODE) return reject_http_connection ? MHD_NO : MHD_YES; return reject_https_connection ? MHD_NO : MHD_YES; } static int _http_server_start(int SERVER_MODE) { uint16_t port_num = 0; if (SERVER_MODE == HTTP_MODE) { static char rnd[8] = "realrnd"; // fixed 'random' value httpdaemon = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY, port_num, _check_to_accept, (void *) (ptrdiff_t) SERVER_MODE, _answer_to_connection, NULL, MHD_OPTION_DIGEST_AUTH_RANDOM, sizeof(rnd), rnd, MHD_OPTION_NONCE_NC_SIZE, 300, #if MHD_VERSION >= 0x00095400 MHD_OPTION_STRICT_FOR_CLIENT, 1, #endif #if MHD_VERSION >= 0x00096800 MHD_OPTION_SERVER_INSANITY, 1, #endif MHD_OPTION_END); if (!httpdaemon) return 1; } else if (SERVER_MODE == HTTPS_MODE || SERVER_MODE == H2_MODE) { size_t size; if (!ocspdaemon) { key_pem = wget_read_file(SRCDIR "/certs/x509-server-key.pem", &size); cert_pem = wget_read_file(SRCDIR "/certs/x509-server-cert.pem", &size); if ((key_pem == NULL) || (cert_pem == NULL)) { wget_error_printf("The key/certificate files could not be read.\n"); return 1; } if (SERVER_MODE == HTTPS_MODE) { httpsdaemon = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY | MHD_USE_TLS #if MHD_VERSION >= 0x00096302 | MHD_USE_POST_HANDSHAKE_AUTH_SUPPORT #endif , port_num, _check_to_accept, (void *) (ptrdiff_t) SERVER_MODE, _answer_to_connection, NULL, MHD_OPTION_HTTPS_MEM_KEY, key_pem, MHD_OPTION_HTTPS_MEM_CERT, cert_pem, #if MHD_VERSION >= 0x00095400 MHD_OPTION_STRICT_FOR_CLIENT, 1, #endif #if MHD_VERSION >= 0x00096800 MHD_OPTION_SERVER_INSANITY, 1, #endif MHD_OPTION_CONNECTION_MEMORY_LIMIT, (size_t) 1*1024*1024, MHD_OPTION_END); if (!httpsdaemon) { wget_error_printf("Cannot start the HTTPS server.\n"); return 1; } } } #ifdef WITH_GNUTLS_OCSP else { httpsdaemon = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY | MHD_USE_TLS #if MHD_VERSION >= 0x00096302 | MHD_USE_POST_HANDSHAKE_AUTH_SUPPORT #endif , port_num, _check_to_accept, (void *) (ptrdiff_t) SERVER_MODE, _answer_to_connection, NULL, MHD_OPTION_HTTPS_CERT_CALLBACK, _ocsp_cert_callback, #if MHD_VERSION >= 0x00095400 MHD_OPTION_STRICT_FOR_CLIENT, 1, #endif #if MHD_VERSION >= 0x00096800 MHD_OPTION_SERVER_INSANITY, 1, #endif MHD_OPTION_CONNECTION_MEMORY_LIMIT, (size_t) 1*1024*1024, MHD_OPTION_END); int rc; gnutls_datum_t data; privkey = wget_malloc(sizeof(gnutls_privkey_t)); gnutls_privkey_init(privkey); if ((rc = gnutls_load_file(SRCDIR "/certs/ocsp/x509-server-key.pem", &data)) < 0) file_load_err(SRCDIR "/certs/ocsp/x509-server-key.pem", gnutls_strerror(rc)); gnutls_privkey_import_x509_raw(*privkey, &data, GNUTLS_X509_FMT_PEM, NULL, 0); wget_xfree(data.data); pcrt = wget_malloc(sizeof(gnutls_pcert_st)*2); if ((rc = gnutls_load_file(SRCDIR "/certs/ocsp/x509-server-cert.pem", &data)) < 0) file_load_err(SRCDIR "/certs/ocsp/x509-server-cert.pem", gnutls_strerror(rc)); gnutls_pcert_import_x509_raw(pcrt, &data, GNUTLS_X509_FMT_PEM, 0); wget_xfree(data.data); if ((rc = gnutls_load_file(SRCDIR "/certs/ocsp/x509-interm-cert.pem", &data)) < 0) file_load_err(SRCDIR "/certs/ocsp/x509-interm-cert.pem", gnutls_strerror(rc)); gnutls_pcert_import_x509_raw(pcrt+1, &data, GNUTLS_X509_FMT_PEM, 0); wget_xfree(data.data); if (!httpsdaemon) { wget_error_printf("Cannot start the HTTPS server.\n"); return 1; } } #endif } else if (SERVER_MODE == OCSP_MODE) { #ifdef WITH_GNUTLS_OCSP static char rnd[8] = "realrnd"; // fixed 'random' value ocspdaemon = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY, port_num, NULL, NULL, _ocsp_ahc, NULL, MHD_OPTION_DIGEST_AUTH_RANDOM, sizeof(rnd), rnd, MHD_OPTION_NONCE_NC_SIZE, 300, #if MHD_VERSION >= 0x00095400 MHD_OPTION_STRICT_FOR_CLIENT, 1, #endif #if MHD_VERSION >= 0x00096800 MHD_OPTION_SERVER_INSANITY, 1, #endif MHD_OPTION_CONNECTION_MEMORY_LIMIT, (size_t) 1*1024*1024, MHD_OPTION_END); #endif if (!ocspdaemon) return 1; } #ifdef WITH_GNUTLS_OCSP #if MHD_VERSION >= 0x00096502 && GNUTLS_VERSION_NUMBER >= 0x030603 else if (SERVER_MODE == OCSP_STAP_MODE) { int rc; gnutls_datum_t data; /* Load private key */ privkey = wget_malloc(sizeof(gnutls_privkey_t)); gnutls_privkey_init(privkey); if ((rc = gnutls_load_file(SRCDIR "/certs/ocsp/x509-server-key.pem", &data)) < 0) file_load_err(SRCDIR "/certs/ocsp/x509-server-key.pem", gnutls_strerror(rc)); gnutls_privkey_import_x509_raw(*privkey, &data, GNUTLS_X509_FMT_PEM, NULL, 0); wget_xfree(data.data); /* Load certificate chain */ pcrt = wget_malloc(sizeof(gnutls_pcert_st) * 2); if ((rc = gnutls_load_file(SRCDIR "/certs/ocsp/x509-server-cert.pem", &data)) < 0) file_load_err(SRCDIR "/certs/ocsp/x509-server-cert.pem", gnutls_strerror(rc)); gnutls_pcert_import_x509_raw(pcrt, &data, GNUTLS_X509_FMT_PEM, 0); wget_xfree(data.data); if ((rc = gnutls_load_file(SRCDIR "/certs/ocsp/x509-interm-cert.pem", &data)) < 0) file_load_err(SRCDIR "/certs/ocsp/x509-interm-cert.pem", gnutls_strerror(rc)); gnutls_pcert_import_x509_raw(pcrt+1, &data, GNUTLS_X509_FMT_PEM, 0); wget_xfree(data.data); /* Load stapled OCSP response */ ocsp_stap_resp = wget_malloc(sizeof(gnutls_ocsp_data_st)); if ((rc = gnutls_load_file(SRCDIR "/certs/ocsp/ocsp_stapled_resp.der", &data)) < 0) file_load_err(SRCDIR "/certs/ocsp/ocsp_stapled_resp.der", gnutls_strerror(rc)); ocsp_stap_resp->response.data = data.data; ocsp_stap_resp->response.size = data.size; ocsp_stap_resp->exptime = 0; /* Start HTTPS daemon with stapled OCSP responses */ httpsdaemon = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY | MHD_USE_TLS | MHD_USE_POST_HANDSHAKE_AUTH_SUPPORT , port_num, _check_to_accept, (void *) (ptrdiff_t) SERVER_MODE, _answer_to_connection, NULL, MHD_OPTION_HTTPS_CERT_CALLBACK2, _ocsp_stap_cert_callback, #if MHD_VERSION >= 0x00095400 MHD_OPTION_STRICT_FOR_CLIENT, 1, #endif #if MHD_VERSION >= 0x00096800 MHD_OPTION_SERVER_INSANITY, 1, #endif MHD_OPTION_CONNECTION_MEMORY_LIMIT, (size_t) 1*1024*1024, MHD_OPTION_END); } #endif #endif // get open random port number if (0) {} #if MHD_VERSION >= 0x00095501 else if (MHD_NO != MHD_is_feature_supported(MHD_FEATURE_AUTODETECT_BIND_PORT)) { const union MHD_DaemonInfo *dinfo = NULL; if (SERVER_MODE == HTTP_MODE) dinfo = MHD_get_daemon_info(httpdaemon, MHD_DAEMON_INFO_BIND_PORT); else if (SERVER_MODE == HTTPS_MODE || SERVER_MODE == OCSP_STAP_MODE) dinfo = MHD_get_daemon_info(httpsdaemon, MHD_DAEMON_INFO_BIND_PORT); #ifdef WITH_GNUTLS_OCSP else if (SERVER_MODE == OCSP_MODE) dinfo = MHD_get_daemon_info(ocspdaemon, MHD_DAEMON_INFO_BIND_PORT); #endif if (!dinfo || dinfo->port == 0) return 1; port_num = dinfo->port; if (SERVER_MODE == HTTP_MODE) http_server_port = port_num; else if (SERVER_MODE == HTTPS_MODE || SERVER_MODE == OCSP_STAP_MODE) https_server_port = port_num; #ifdef WITH_GNUTLS_OCSP else if (SERVER_MODE == OCSP_MODE) ocsp_server_port = port_num; #endif #ifdef WITH_LIBNGHTTP2 else if (SERVER_MODE == H2_MODE) { h2_server_port = port_num; } #endif } #endif /* MHD_VERSION >= 0x00095501 */ else { const union MHD_DaemonInfo *dinfo = NULL; int sock_fd; if (SERVER_MODE == HTTP_MODE) dinfo = MHD_get_daemon_info(httpdaemon, MHD_DAEMON_INFO_LISTEN_FD); else if (SERVER_MODE == HTTPS_MODE || SERVER_MODE == OCSP_STAP_MODE) dinfo = MHD_get_daemon_info(httpsdaemon, MHD_DAEMON_INFO_LISTEN_FD); #ifdef WITH_GNUTLS_OCSP else if (SERVER_MODE == OCSP_MODE) dinfo = MHD_get_daemon_info(ocspdaemon, MHD_DAEMON_INFO_LISTEN_FD); #endif if (!dinfo) return 1; #ifdef _WIN32 sock_fd = _open_osfhandle(dinfo->listen_fd, O_RDWR | O_BINARY); #else sock_fd = dinfo->listen_fd; #endif struct sockaddr_storage addr_store; struct sockaddr *addr = (struct sockaddr *)&addr_store; socklen_t addr_len = sizeof(addr_store); // get automatic retrieved port number if (getsockname(sock_fd, addr, &addr_len) == 0) { char s_port[NI_MAXSERV]; if (getnameinfo(addr, addr_len, NULL, 0, s_port, sizeof(s_port), NI_NUMERICSERV) == 0) { port_num = (uint16_t)atoi(s_port); if (SERVER_MODE == HTTP_MODE) http_server_port = port_num; else if (SERVER_MODE == HTTPS_MODE || SERVER_MODE == OCSP_STAP_MODE) https_server_port = port_num; #ifdef WITH_GNUTLS_OCSP else if (SERVER_MODE == OCSP_MODE) ocsp_server_port = port_num; #endif #ifdef WITH_LIBNGHTTP2 else if (SERVER_MODE == H2_MODE) h2_server_port = port_num; #endif } } } return 0; } #if defined __CYGWIN__ // Using opendir/readdir loop plus unlink() has a race condition // with CygWin. Not sure if this also happens on other systems as well. // Since we don't have valgrind, we can use system() without issues. static void _remove_directory(const char *dirname) { char cmd[strlen(dirname) + 16]; wget_snprintf(cmd, sizeof(cmd), "rm -rf %s", dirname); system(cmd); } static void _empty_directory(const char *dirname) { _remove_directory(dirname); if (mkdir(dirname, 0755) != 0) wget_error_printf_exit("Failed to re-create directory (%d)\n", errno); } #else // To reduce the verbosity of 'valgrind --trace-children=yes' output, // we avoid system("rm -rf ...") calls. static void _remove_directory(const char *dirname); static void _empty_directory(const char *dirname) { DIR *dir; if ((dir = opendir(dirname))) { struct dirent *dp; while ((dp = readdir(dir))) { if (*dp->d_name == '.' && (dp->d_name[1] == 0 || (dp->d_name[1] == '.' && dp->d_name[2] == 0))) continue; char *fname = wget_aprintf("%s/%s", dirname, dp->d_name); if (unlink(fname) == -1) { // in case fname is a directory glibc returns EISDIR but correct POSIX value would be EPERM. // MinGW + Wine returns EACCESS here. if (errno == EISDIR || errno == EPERM || errno == EACCES) _remove_directory(fname); else wget_error_printf("Failed to unlink %s (%d)\n", fname, errno); } wget_xfree(fname); } closedir(dir); wget_debug_printf("Removed test directory '%s'\n", dirname); } else if (errno != ENOENT) wget_error_printf("Failed to opendir %s (%d)\n", dirname, errno); } static void _remove_directory(const char *dirname) { _empty_directory(dirname); if (rmdir(dirname) == -1 && errno != ENOENT) wget_error_printf("Failed to rmdir %s (%d)\n", dirname, errno); } #endif void wget_test_stop_server(void) { // wget_vector_free(&response_headers); wget_vector_free(&request_urls); wget_vector_free(&ocsp_responses); for (wget_test_url_t *url = urls; url < urls + nurls; url++) { if (url->body_original) { wget_xfree(url->body); url->body_original = NULL; } for (size_t it = 0; it < countof(url->headers); it++) { if (url->headers_original[it]) { wget_xfree(url->headers[it]); url->headers_original[it] = NULL; } } } if (chdir("..") != 0) wget_error_printf("Failed to chdir ..\n"); if (!keep_tmpfiles) _remove_directory(tmpdir); wget_global_deinit(); _http_server_stop(); } static char *_insert_ports(const char *src) { if (!src || (!strstr(src, "{{port}}") && !strstr(src, "{{sslport}}") && !strstr(src, "{{ocspport}}"))) return NULL; size_t srclen = strlen(src) + 1; char *ret = wget_malloc(srclen); char *dst = ret; while (*src) { if (*src == '{') { if (!strncmp(src, "{{port}}", 8)) { if (proto_pass == HTTP_1_1_PASS) { dst += wget_snprintf(dst, srclen - (dst - ret), "%d", http_server_port); } #ifdef WITH_LIBNGHTTP2 else { dst += wget_snprintf(dst, srclen - (dst - ret), "%d", reject_https_connection ? http_server_port : h2_server_port); } #endif src += 8; continue; } else if (!strncmp(src, "{{sslport}}", 11)) { if (proto_pass == HTTP_1_1_PASS) { dst += wget_snprintf(dst, srclen - (dst - ret), "%d", https_server_port); } #ifdef WITH_LIBNGHTTP2 else { dst += wget_snprintf(dst, srclen - (dst - ret), "%d", h2_server_port); } #endif src += 11; continue; } else if (!strncmp(src, "{{ocspport}}", 12)) { dst += wget_snprintf(dst, srclen - (dst - ret), "%d", ocsp_server_port); src += 12; continue; } } *dst++ = *src++; } *dst = 0; return ret; } static void _write_msg(const char *msg, size_t len) { #ifdef _WIN32 fwrite(msg, 1, len, stderr); #else if (isatty(fileno(stderr))) { if (len && msg[len - 1] == '\n') len--; wget_fprintf(stderr, "\033[33m%.*s\033[m\n", (int) len, msg); } else fwrite(msg, 1, len, stderr); #endif } void wget_test_start_server(int first_key, ...) { int rc, key; va_list args; bool start_http = 1; #ifdef WITH_TLS bool start_https = 1; #ifdef WITH_GNUTLS_OCSP bool ocsp_stap = 0; bool start_ocsp = 0; #endif #ifdef WITH_LIBNGHTTP2 bool start_h2 = 0; // Changed to 0: HTTP/2 is now opt-in with WGET_TEST_H2_ONLY #endif #endif wget_global_init( WGET_DEBUG_FUNC, _write_msg, WGET_ERROR_FUNC, _write_msg, WGET_INFO_FUNC, _write_msg, 0); wget_debug_printf("MHD compiled with 0x%08x, linked with %s\n", (unsigned) MHD_VERSION, MHD_get_version()); #if MHD_VERSION >= 0x00095400 wget_debug_printf("MHD_OPTION_STRICT_FOR_CLIENT: yes\n"); #else wget_debug_printf("MHD_OPTION_STRICT_FOR_CLIENT: no\n"); #endif #if MHD_VERSION >= 0x00096800 wget_debug_printf("MHD_OPTION_SERVER_INSANITY: yes\n"); #else wget_debug_printf("MHD_OPTION_SERVER_INSANITY: no\n"); #endif #ifdef WITH_LIBNGHTTP2 wget_debug_printf("WITH_LIBNGHTTP2: yes\n"); #else wget_debug_printf("WITH_LIBNGHTTP2: no\n"); #endif #ifdef HAVE_GNUTLS_OCSP_H wget_debug_printf("HAVE_GNUTLS_OCSP_H: yes\n"); #else wget_debug_printf("HAVE_GNUTLS_OCSP_H: no\n"); #endif wget_debug_printf("\n"); va_start(args, first_key); for (key = first_key; key; key = va_arg(args, int)) { switch (key) { /* case WGET_TEST_RESPONSE_BODY: response_body = va_arg(args, const char *); break; case WGET_TEST_RESPONSE_HEADER: if (!response_headers) response_headers = wget_vector_create(4,4,NULL); wget_vector_add_str(response_headers, va_arg(args, const char *)); break; case WGET_TEST_RESPONSE_CODE: response_code = va_arg(args, const char *); break; */ case WGET_TEST_RESPONSE_URLS: urls = va_arg(args, wget_test_url_t *); nurls = va_arg(args, size_t); break; case WGET_TEST_SERVER_SEND_CONTENT_LENGTH: server_send_content_length = !!va_arg(args, int); break; case WGET_TEST_HTTPS_ONLY: start_http = 0; break; case WGET_TEST_HTTP_ONLY: #ifdef WITH_TLS start_https = 0; #ifdef WITH_LIBNGHTTP2 start_h2 = 0; #endif #endif break; case WGET_TEST_H2_ONLY: start_http = 0; #ifdef WITH_TLS start_https = 0; #endif #ifdef WITH_LIBNGHTTP2 start_h2 = 1; #endif break; case WGET_TEST_HTTP_REJECT_CONNECTIONS: reject_http_connection = 1; break; case WGET_TEST_HTTPS_REJECT_CONNECTIONS: reject_https_connection = 1; break; case WGET_TEST_FEATURE_MHD: break; case WGET_TEST_FEATURE_TLS: #if !defined WITH_TLS wget_error_printf("Test requires TLS. Skipping\n"); exit(WGET_TEST_EXIT_SKIP); #endif break; case WGET_TEST_FEATURE_IDN: #if !defined WITH_LIBIDN && !defined WITH_LIBIDN2 wget_error_printf("Support for LibIDN not found. Skipping\n"); exit(WGET_TEST_EXIT_SKIP); #endif break; case WGET_TEST_FEATURE_PLUGIN: #ifndef PLUGIN_SUPPORT wget_error_printf("Plugin Support Disabled. Skipping\n"); exit(WGET_TEST_EXIT_SKIP); #endif break; case WGET_TEST_FEATURE_OCSP: #if !defined WITH_GNUTLS_OCSP wget_error_printf("Test requires GnuTLS with OCSP support. Skipping\n"); exit(WGET_TEST_EXIT_SKIP); #else start_http = 0; #ifdef WITH_LIBNGHTTP2 start_h2 = 0; #endif #ifdef WITH_TLS #ifdef WITH_GNUTLS_OCSP start_ocsp = 1; #endif #endif break; #endif case WGET_TEST_FEATURE_OCSP_STAPLING: #if !defined WITH_GNUTLS_OCSP || MHD_VERSION < 0x00096502 || GNUTLS_VERSION_NUMBER < 0x030603 wget_error_printf("MHD or GnuTLS version insufficient. Skipping\n"); exit(WGET_TEST_EXIT_SKIP); #else start_http = 0; #ifdef WITH_TLS start_https = 0; #endif #ifdef WITH_LIBNGHTTP2 start_h2 = 0; #endif #ifdef WITH_TLS #ifdef WITH_GNUTLS_OCSP ocsp_stap = 1; #endif #endif break; #endif case WGET_TEST_SKIP_H2: #ifdef WITH_LIBNGHTTP2 start_h2 = 0; #endif break; default: wget_error_printf("Unknown option %d\n", key); } } va_end(args); atexit(wget_test_stop_server); wget_snprintf(tmpdir, sizeof(tmpdir), ".test_%d", (int) getpid()); // remove tmpdir if exists from previous tests _remove_directory(tmpdir); if (mkdir(tmpdir, 0755) != 0) wget_error_printf_exit("Failed to create tmpdir (%d)\n", errno); if (chdir(tmpdir) != 0) wget_error_printf_exit("Failed to change to tmpdir (%d)\n", errno); // start HTTP server if (start_http) { if ((rc = _http_server_start(HTTP_MODE)) != 0) wget_error_printf_exit("Failed to start HTTP server, error %d\n", rc); } #ifdef WITH_TLS #ifdef WITH_GNUTLS_OCSP // start OCSP responder if (start_ocsp) { if ((rc = _http_server_start(OCSP_MODE)) != 0) wget_error_printf_exit("Failed to start OCSP server, error %d\n", rc); } // start OCSP server (stapling) if (ocsp_stap) { if ((rc = _http_server_start(OCSP_STAP_MODE)) != 0) wget_error_printf_exit("Failed to start OCSP Stapling server, error %d\n", rc); } #endif // start HTTPS server if (start_https) { if ((rc = _http_server_start(HTTPS_MODE)) != 0) wget_error_printf_exit("Failed to start HTTPS server, error %d\n", rc); } #ifdef WITH_LIBNGHTTP2 // start libnghttp2 h2 server if (start_h2) { if ((rc = h2_server_start()) != 0) wget_error_printf_exit("Failed to start HTTP/2 server, error %d\n", rc); } #endif #endif } static void _scan_for_unexpected(const char *dirname, const wget_test_file_t *expected_files) { DIR *dir; struct stat st; wget_info_printf("Entering %s\n", dirname); if ((dir = opendir(dirname))) { struct dirent *dp; while ((dp = readdir(dir))) { if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) continue; char *fname; if (*dirname == '.' && dirname[1] == 0) fname = wget_strdup(dp->d_name); else fname = wget_aprintf("%s/%s", dirname, dp->d_name); wget_info_printf(" - %s/%s\n", dirname, dp->d_name); if (stat(fname, &st) == 0 && S_ISDIR(st.st_mode)) { _scan_for_unexpected(fname, expected_files); wget_xfree(fname); continue; } if (expected_files) { // Mac OS X converts to NFD, so we might find an unexpected file name, e.g. when using accents. // Example: cedilla (%C3%A7) will be converted to c+composed_cedilla (%63%CC%A7) // Since there are a few pitfalls with Apple's NFD, just skip the check here. #if !(defined __APPLE__ && defined __MACH__) size_t it; wget_info_printf("search %s\n", fname); for (it = 0; expected_files[it].name; it++) { #ifdef _WIN32 char buf[strlen(expected_files[it].name) * 3 + 1]; const char *restricted_fname = wget_restrict_file_name(expected_files[it].name, buf, expected_files[it].restricted_mode ? expected_files[it].restricted_mode : WGET_RESTRICT_NAMES_WINDOWS); #else const char *restricted_fname = expected_files[it].name; #endif /* { char b[256]; if (it==0) { wget_memtohex(fname, strlen(fname), b, sizeof(b)); wget_debug_printf("f %s\n", b); } wget_memtohex(restricted_fname, strlen(restricted_fname), b, sizeof(b)); wget_debug_printf("r %s\n", b); } */ if (!strcmp(restricted_fname, fname)) break; } if (!expected_files[it].name) wget_error_printf_exit("Unexpected file %s/%s found\n", tmpdir, fname); #endif } else wget_error_printf_exit("Unexpected file %s/%s found\n", tmpdir, fname); wget_xfree(fname); } closedir(dir); } else wget_error_printf_exit("Failed to diropen %s\n", dirname); } static const char *global_executable; void wget_test_set_executable(const char *program) { global_executable = program; } void wget_test(int first_key, ...) { #ifndef WITH_LIBNGHTTP2 if (!httpdaemon && !httpsdaemon) exit(WGET_TEST_EXIT_SKIP); #endif for (proto_pass = 0; proto_pass < END_PASS; proto_pass++) { if (proto_pass == HTTP_1_1_PASS && !httpdaemon && !httpsdaemon) continue; if (proto_pass == H2_PASS) { #ifndef WITH_LIBNGHTTP2 continue; #endif #ifdef WITH_LIBNGHTTP2 if (!nghttp2_server) continue; #else continue; // No HTTP/2 support without libnghttp2 #endif } // now replace {{port}} in the body by the actual server port for (wget_test_url_t *url = urls; url < urls + nurls; url++) { char *p = _insert_ports(url->body); if (p) { url->body_original = url->body; url->body = p; } for (unsigned it = 0; it < countof(url->headers) && url->headers[it]; it++) { p = _insert_ports(url->headers[it]); if (p) { url->headers_original[it] = url->headers[it]; url->headers[it] = p; } } } const char *request_url, *options = "", *executable = global_executable; const wget_test_file_t *expected_files = NULL, *existing_files = NULL; wget_buffer *cmd = wget_buffer_alloc(1024); unsigned it; int key, fd, rc, expected_error_code2 = -1, expected_error_code = 0; va_list args; char server_send_content_length_old = server_send_content_length; bool options_alloc = 0; if (!executable) { #if defined _WIN32 && !defined __MINGW32__ if (proto_pass == H2_PASS) executable = BUILDDIR "\\..\\src\\wget2_noinstall" EXEEXT " -d --no-config --no-local-db --max-threads=1 --prefer-family=ipv4 --no-proxy --timeout 3 --tries=1 --https-enforce=hard --ca-certificate=" SRCDIR "/certs/x509-ca-cert.pem --no-ocsp"; else executable = BUILDDIR "\\..\\src\\wget2_noinstall" EXEEXT " -d --no-config --no-local-db --max-threads=1 --prefer-family=ipv4 --no-proxy --timeout 3 --tries=1"; #else if (proto_pass == H2_PASS) executable = BUILDDIR "/../src/wget2_noinstall" EXEEXT " -d --no-config --no-local-db --max-threads=1 --prefer-family=ipv4 --no-proxy --timeout 3 --tries=1 --https-enforce=hard --ca-certificate=" SRCDIR "/certs/x509-ca-cert.pem --no-ocsp"; else executable = BUILDDIR "/../src/wget2_noinstall" EXEEXT " -d --no-config --no-local-db --max-threads=1 --prefer-family=ipv4 --no-proxy --timeout 3 --tries=1"; #endif } keep_tmpfiles = 0; clean_directory = 1; h2_min_concurrent_streams = 0; if (!request_urls) { request_urls = wget_vector_create(8, NULL); wget_vector_set_destructor(request_urls, NULL); } #ifdef WITH_GNUTLS_OCSP if (!ocsp_responses) { ocsp_responses = wget_vector_create(2, NULL); } #endif va_start (args, first_key); for (key = first_key; key; key = va_arg(args, int)) { switch (key) { case WGET_TEST_REQUEST_URL: if ((request_url = va_arg(args, const char *))) wget_vector_add(request_urls, request_url); break; case WGET_TEST_REQUEST_URLS: while ((request_url = va_arg(args, const char *))) wget_vector_add(request_urls, request_url); break; case WGET_TEST_EXPECTED_ERROR_CODE: expected_error_code = va_arg(args, int); break; case WGET_TEST_EXPECTED_ERROR_CODE2: expected_error_code2 = va_arg(args, int); break; case WGET_TEST_EXPECTED_FILES: expected_files = va_arg(args, const wget_test_file_t *); break; case WGET_TEST_EXISTING_FILES: existing_files = va_arg(args, const wget_test_file_t *); break; case WGET_TEST_OPTIONS: { options = va_arg(args, const char *); const char *tmp = _insert_ports(options); if (tmp) { options = tmp; options_alloc = 1; } break; } case WGET_TEST_KEEP_TMPFILES: keep_tmpfiles = va_arg(args, int); break; case WGET_TEST_CLEAN_DIRECTORY: clean_directory = va_arg(args, int); break; case WGET_TEST_EXECUTABLE: executable = va_arg(args, const char *); break; case WGET_TEST_SERVER_SEND_CONTENT_LENGTH: server_send_content_length = !!va_arg(args, int); break; case WGET_TEST_H2_MIN_CONCURRENT_STREAMS: h2_min_concurrent_streams = va_arg(args, int); break; case WGET_TEST_POST_HANDSHAKE_AUTH: if (va_arg(args, int)) { #if MHD_VERSION >= 0x00096302 && GNUTLS_VERSION_NUMBER >= 0x030603 post_handshake_auth = wget_malloc(sizeof(enum CHECK_POST_HANDSHAKE_AUTH)); #endif } break; case WGET_TEST_OCSP_RESP_FILES: #ifdef WITH_GNUTLS_OCSP { const char *ocsp_resp_file = NULL; while ((ocsp_resp_file = va_arg(args, const char *))) { if (ocspdaemon) { ocsp_resp_t ocsp_resp = { .data = NULL, .size = 0 }; if (*ocsp_resp_file) { ocsp_resp.data = wget_read_file(ocsp_resp_file, &ocsp_resp.size); if (ocsp_resp.data == NULL) { wget_error_printf_exit("Couldn't read the response from '%s'.\n", ocsp_resp_file); } } wget_vector_add_memdup(ocsp_responses, &ocsp_resp, sizeof(ocsp_resp)); } } ocsp_response_pos = 0; } #endif break; default: wget_error_printf_exit("Unknown option %d [%s]\n", key, options); } } va_end(args); if (clean_directory) { // clean directory wget_buffer_printf(cmd, "../%s", tmpdir); _empty_directory(cmd->data); } // create files if (existing_files) { for (it = 0; existing_files[it].name; it++) { mkdir_path(existing_files[it].name, 1); if (existing_files[it].hardlink) { if (link(existing_files[it].hardlink, existing_files[it].name) != 0) { wget_error_printf_exit("Failed to link %s/%s -> %s/%s [%s]\n", tmpdir, existing_files[it].hardlink, tmpdir, existing_files[it].name, options); } } else if ((fd = open(existing_files[it].name, O_CREAT|O_WRONLY|O_TRUNC|O_BINARY, 0644)) != -1) { const char *existing_content = _insert_ports(existing_files[it].content); if (!existing_content) existing_content = existing_files[it].content; ssize_t nbytes = write(fd, existing_content, strlen(existing_content)); close(fd); if (nbytes != (ssize_t)strlen(existing_content)) wget_error_printf_exit("Failed to write %zu bytes to file %s/%s [%s]\n", strlen(existing_content), tmpdir, existing_files[it].name, options); if (existing_files[it].timestamp) { // take the old utime() instead of utimes() if (utime(existing_files[it].name, &(struct utimbuf){ 0, existing_files[it].timestamp })) wget_error_printf_exit("Failed to set mtime of %s/%s [%s]\n", tmpdir, existing_files[it].name, options); } if (existing_content != existing_files[it].content) wget_xfree(existing_content); } else { wget_error_printf_exit("Failed to write open file %s/%s [%s] (%d,%s)\n", tmpdir, *existing_files[it].name == '/' ? existing_files[it].name + 1 : existing_files[it].name , options, errno, strerror(errno)); } } } const char *valgrind = getenv("VALGRIND_TESTS"); if (!valgrind || !*valgrind || !strcmp(valgrind, "0")) { // On some system we get random IP order (v4, v6) for localhost, so we need --prefer-family for testing since // the test servers will listen only on the first IP and also prefers IPv4 const char *emulator = getenv("EMULATOR"); if (emulator && *emulator) wget_buffer_printf(cmd, "%s %s %s", emulator, executable, options); else wget_buffer_printf(cmd, "%s %s", executable, options); } else if (!strcmp(valgrind, "1")) { wget_buffer_printf(cmd, "valgrind --error-exitcode=301 --leak-check=yes --show-reachable=yes --track-origins=yes --child-silent-after-fork=yes --suppressions=" SRCDIR "/valgrind-suppressions --gen-suppressions=all %s %s", executable, options); } else wget_buffer_printf(cmd, "%s %s %s", valgrind, executable, options); for (it = 0; it < (size_t)wget_vector_size(request_urls); it++) { request_url = wget_vector_get(request_urls, it); if (!wget_strncasecmp_ascii(request_url, "http://", 7) || !wget_strncasecmp_ascii(request_url, "https://", 8)) { char *tmp = _insert_ports(request_url); wget_buffer_printf_append(cmd, " \"%s\"", tmp ? tmp : request_url); wget_xfree(tmp); } else { if (proto_pass == HTTP_1_1_PASS) { wget_buffer_printf_append(cmd, " \"http://localhost:%d/%s\"", http_server_port, request_url); } #ifdef WITH_LIBNGHTTP2 else { wget_buffer_printf_append(cmd, " \"https://localhost:%d/%s\"", h2_server_port, request_url); } #endif } } wget_buffer_strcat(cmd, " 2>&1"); wget_error_printf("\n##### Testing '%s'\n", cmd->data); // catch stdout and write to stderr so all output is in sync FILE *pp; if ((pp = popen(cmd->data, "r"))) { char buf[4096]; while (fgets(buf, sizeof(buf), pp)) { fputs(buf, stderr); fflush(stderr); } rc = pclose(pp); } else wget_error_printf_exit("Failed to execute test (%d) [%s]\n", errno, options); /* rc = system(cmd->data); */ if (!WIFEXITED(rc)) { wget_error_printf_exit("Unexpected error code %d, expected %d [%s]\n", rc, expected_error_code, options); } else if (WEXITSTATUS(rc) != expected_error_code) { if (expected_error_code2 >= 0) { if (WEXITSTATUS(rc) != expected_error_code2) wget_error_printf_exit("Unexpected error code %d, expected %d or %d [%s]\n", WEXITSTATUS(rc), expected_error_code, expected_error_code2, options); } else wget_error_printf_exit("Unexpected error code %d, expected %d [%s]\n", WEXITSTATUS(rc), expected_error_code, options); } if (expected_files) { for (it = 0; expected_files[it].name; it++) { struct stat st; #ifdef _WIN32 char buf[strlen(expected_files[it].name) * 3 + 1]; const char *fname = wget_restrict_file_name(expected_files[it].name, buf, expected_files[it].restricted_mode ? expected_files[it].restricted_mode : WGET_RESTRICT_NAMES_WINDOWS); #else const char *fname = expected_files[it].name; #endif if (stat(fname, &st) != 0) wget_error_printf_exit("Missing expected file '%s/%s' [%s]\n", tmpdir, fname, options); if (expected_files[it].content) { size_t nbytes; char *content = wget_read_file(fname, &nbytes); if (content) { const char *expected_content = _insert_ports(expected_files[it].content); bool expected_content_alloc = 0; if (!expected_content) expected_content = expected_files[it].content; else expected_content_alloc = 1; size_t content_length = expected_files[it].content_length ? expected_files[it].content_length : strlen(expected_content); if (content_length != nbytes || memcmp(expected_content, content, nbytes) != 0) { wget_error_printf("Unexpected content in %s [%s]\n", fname, options); wget_error_printf(" Expected %zu bytes:\n%s\n", content_length, expected_content); wget_error_printf(" Got %zu bytes:\n%s\n", nbytes, content); exit(EXIT_FAILURE); } if (expected_content_alloc) wget_xfree(expected_content); } wget_xfree(content); } if (expected_files[it].timestamp && st.st_mtime != expected_files[it].timestamp) wget_error_printf_exit("Unexpected timestamp '%s/%s' (%ld) [%s]\n", tmpdir, fname, st.st_mtime, options); } } // look if there are unexpected files in our working dir _scan_for_unexpected(".", expected_files); #if MHD_VERSION >= 0x00096302 && GNUTLS_VERSION_NUMBER >= 0x030603 if (post_handshake_auth && *post_handshake_auth == CHECK_FAILED) { wget_free(post_handshake_auth); wget_error_printf_exit("Post-handshake authentication failed\n"); } else if (post_handshake_auth) wget_free(post_handshake_auth); #endif #ifdef WITH_GNUTLS_OCSP for (int i = 0; i < wget_vector_size(ocsp_responses); i++) { ocsp_resp_t *r = wget_vector_get(ocsp_responses, i); wget_xfree(r->data); } wget_vector_clear(ocsp_responses); #endif wget_vector_clear(request_urls); wget_buffer_free(&cmd); if (options_alloc) wget_xfree(options); server_send_content_length = server_send_content_length_old; // system("ls -la"); // cleanup for next iteration for (wget_test_url_t *url = urls; url < urls + nurls; url++) { if (url->body_original) { wget_xfree(url->body); url->body = url->body_original; url->body_original = NULL; } for (it = 0; it < countof(url->headers) && url->headers[it]; it++) { if (url->headers_original[it]) { wget_xfree(url->headers[it]); url->headers[it] = url->headers_original[it]; url->headers_original[it] = NULL; } } } } } int wget_test_get_http_server_port(void) { return proto_pass == H2_PASS ? h2_server_port : http_server_port; } int wget_test_get_https_server_port(void) { return proto_pass == H2_PASS ? h2_server_port : https_server_port; } int wget_test_get_h2_server_port(void) { #ifdef WITH_LIBNGHTTP2 return h2_server_port; #else return -1; #endif } int wget_test_get_ocsp_server_port(void) { return ocsp_server_port; } // assume that we are in 'tmpdir' int wget_test_check_file_system(void) { static char fname[3][3] = { "Ab", "ab", "AB" }; char buf[sizeof(fname[0])]; int flags = 0, fd; ssize_t rc; _empty_directory(tmpdir); // Create 3 files with differently cased names with different content. // On a case-mangling file system like HFS+ there will be just one file with the contents of the last write. for (unsigned it = 0; it < countof(fname); it++) { if ((fd = open(fname[it], O_WRONLY | O_TRUNC | O_CREAT | O_BINARY, 0644)) != -1) { rc = write(fd, fname[it], sizeof(fname[0])); close(fd); if (rc != sizeof(fname[0])) { wget_debug_printf("%s: Failed to write to '%s/%s' (%d) %zd %zu\n", __func__, tmpdir, fname[it], errno, rc, sizeof(fname[0])); goto out; } } else { wget_debug_printf("%s: Failed to write open '%s/%s'\n", __func__, tmpdir, fname[it]); goto out; } } // Check file content to see if FS is case-mangling for (unsigned it = 0; it < countof(fname); it++) { if ((fd = open(fname[it], O_RDONLY | O_BINARY, 0644)) != -1) { rc = read(fd, buf, sizeof(fname[0])); close(fd); if (rc != sizeof(fname[0])) { wget_debug_printf("%s: Failed to read from '%s/%s'\n", __func__, tmpdir, fname[it]); goto out; } if (memcmp(buf, fname[it], sizeof(fname[0]))) { wget_debug_printf("%s: Found case-mangling file system\n", __func__); flags = WGET_TEST_FS_CASEMATTERS; goto out; // we can stop here } } else { wget_debug_printf("%s: Failed to read open '%s/%s'\n", __func__, tmpdir, fname[it]); goto out; } } wget_debug_printf("%s: Found case-respecting file system\n", __func__); out: _empty_directory(tmpdir); return flags; }