/* * Copyright(c) 2013-2014 Tim Ruehsen * Copyright(c) 2015-2016 Free Software Foundation, Inc. * * This file is part of libwget. * * Libwget is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Libwget 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with libwget. 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 #include "libtest.h" static wget_thread_t http_server_tid, https_server_tid, ftp_server_tid, ftps_server_tid; static int http_server_port, https_server_port, ftp_server_port, ftps_server_port, ftps_implicit, terminate, keep_tmpfiles; /*static const char *response_code = "200 Dontcare", *response_body = ""; static WGET_VECTOR *response_headers; */ static wget_vector_t *request_urls; static wget_test_url_t *urls; static size_t nurls; static wget_test_ftp_io_t *ios; static size_t nios; static int ios_ordered; static char tmpdir[128]; static const char *server_hello; static char server_send_content_length = 1; static void sigterm_handler(int sig G_GNUC_WGET_UNUSED) { terminate = 1; } static void *_http_server_thread(void *ctx) { wget_tcp_t *tcp=NULL, *parent_tcp = ctx; wget_test_url_t *url = NULL; char buf[4096], method[32], request_url[256], tag[64], value[256], *p; ssize_t from_bytes, to_bytes, n; size_t nbytes, body_len, request_url_length; unsigned it; int byterange, authorized, https; time_t modified; #ifdef _WIN32 signal(SIGTERM, sigterm_handler); #else sigaction(SIGTERM, &(struct sigaction) { .sa_handler = sigterm_handler }, NULL); #endif https = wget_tcp_get_ssl(parent_tcp); while (!terminate) { wget_tcp_deinit(&tcp); if ((tcp = wget_tcp_accept(parent_tcp))) { authorized = 0; nbytes = 0; while ((n = wget_tcp_read(tcp, buf + nbytes, sizeof(buf) - 1 - nbytes)) > 0) { nbytes += n; buf[nbytes]=0; wget_info_printf(_("[SERVER] got %zd bytes (total %zu)\n"), n, nbytes); if (strstr(buf,"\r\n\r\n")) break; } wget_info_printf(_("[SERVER] total %zd bytes (total %zu) (errno=%d)\n"), n, nbytes, errno); if (nbytes > 0) { if (sscanf(buf, "%31s %255s", method, request_url) !=2) { wget_tcp_printf(tcp, "HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n"); continue; } byterange = from_bytes = to_bytes = 0; modified = 0; for (p = strstr(buf, "\r\n"); p && sscanf(p, "\r\n%63[^:]: %255[^\r]", tag, value) == 2; p = strstr(p + 2, "\r\n")) { if (!wget_strcasecmp_ascii(tag, "Range")) { if ((byterange = sscanf(value, "bytes=%zd-%zd", &from_bytes, &to_bytes)) < 1) byterange = 0; } else if (url && !wget_strcasecmp_ascii(tag, "Authorization")) { const char *auth_scheme, *s; s=wget_http_parse_token(value, &auth_scheme); while (c_isblank(*s)) s++; if (!wget_strcasecmp_ascii(auth_scheme, "basic")) { const char *encoded = wget_base64_encode_printf_alloc("%s:%s", url->auth_username, url->auth_password); wget_error_printf("Auth check '%s' <-> '%s'\n", encoded, s); if (!strcmp(encoded, s)) authorized = 1; wget_xfree(encoded); } wget_xfree(auth_scheme); } else if (!wget_strcasecmp_ascii(tag, "If-Modified-Since")) { modified = wget_http_parse_full_date(value); wget_info_printf("modified = %ld\n", modified); } } url = NULL; request_url_length = strlen(request_url); if (request_url[request_url_length - 1] == '/') { // access a directory for (it = 0; it < nurls; it++) { if (!strcmp(request_url, urls[it].name) || (!strncmp(request_url, urls[it].name, request_url_length) && !strcmp(urls[it].name + request_url_length, "index.html"))) { if (urls[it].scope == 0 || (https && (urls[it].scope & WGET_TEST_URL_SCOPE_HTTPS)) || (!https && (urls[it].scope & WGET_TEST_URL_SCOPE_HTTP))) { url = &urls[it]; break; } } } } else { // access a file for (it = 0; it < nurls; it++) { // printf("%s %s\n", request_url, urls[it].name); if (!strcmp(request_url, urls[it].name)) { if (urls[it].scope == 0 || (https && (urls[it].scope & WGET_TEST_URL_SCOPE_HTTPS)) || (!https && (urls[it].scope & WGET_TEST_URL_SCOPE_HTTP))) { url = &urls[it]; break; } } } } if (!url) { wget_tcp_printf(tcp, "HTTP/1.1 404 Not Found\r\nConnection: close\r\n\r\n"); continue; } if (url->auth_method && !authorized) { if (!wget_strcasecmp_ascii(url->auth_method, "basic")) wget_tcp_printf(tcp, "HTTP/1.1 401 Unauthorized\r\n" \ "WWW-Authenticate: %s realm=\"Protected Page\"\r\n" \ "Connection: close\r\n\r\n", url->auth_method); else wget_error_printf(_("Unknown authentication scheme '%s'\n"), url->auth_method); continue; } if (modified && url->modified<=modified) { wget_tcp_printf(tcp,"HTTP/1.1 304 Not Modified\r\n\r\n"); continue; } if (byterange == 1) { to_bytes = strlen(url->body) - 1; } if (byterange) { if (from_bytes > to_bytes || from_bytes >= (int)strlen(url->body)) { wget_tcp_printf(tcp, "HTTP/1.1 416 Range Not Satisfiable\r\nConnection: close\r\n\r\n"); continue; } // create response body_len = to_bytes - from_bytes + 1; nbytes = snprintf(buf, sizeof(buf), "HTTP/1.1 206 Partial Content\r\n"); nbytes += snprintf(buf + nbytes, sizeof(buf) - nbytes, "Content-Length: %zu\r\n", body_len); nbytes += snprintf(buf + nbytes, sizeof(buf) - nbytes, "Accept-Ranges: bytes\r\n"); nbytes += snprintf(buf + nbytes, sizeof(buf) - nbytes, "Content-Range: %zd-%zd/%zu\r\n", from_bytes, to_bytes, body_len); for (it = 0; it < countof(url->headers) && url->headers[it]; it++) { nbytes += snprintf(buf + nbytes, sizeof(buf) - nbytes, "%s\r\n", url->headers[it]); } nbytes += snprintf(buf + nbytes, sizeof(buf) - nbytes, "\r\n"); if (!strcmp(method, "GET") || !strcmp(method, "POST")) nbytes += snprintf(buf + nbytes, sizeof(buf) - nbytes, "%.*s", (int)body_len, url->body + from_bytes); } else { // create response body_len = strlen(url->body ? url->body : ""); nbytes = snprintf(buf, sizeof(buf), "HTTP/1.1 %s\r\n", url->code ? url->code : "200 OK"); if (server_send_content_length) nbytes += snprintf(buf + nbytes, sizeof(buf) - nbytes, "Content-Length: %zu\r\n", body_len); for (it = 0; it < countof(url->headers) && url->headers[it]; it++) { nbytes += snprintf(buf + nbytes, sizeof(buf) - nbytes, "%s\r\n", url->headers[it]); } nbytes += snprintf(buf + nbytes, sizeof(buf) - nbytes, "\r\n"); if (!strcmp(method, "GET") || !strcmp(method, "POST")) nbytes += snprintf(buf + nbytes, sizeof(buf) - nbytes, "%s", url->body ? url->body : ""); } // send response wget_tcp_write(tcp, buf, nbytes); } } else if (!terminate) wget_error_printf(_("Failed to get connection (%d)\n"), errno); } wget_tcp_deinit(&parent_tcp); return NULL; } static void *_ftp_server_thread(void *ctx) { wget_tcp_t *tcp = NULL, *parent_tcp = ctx, *pasv_parent_tcp = NULL, *pasv_tcp = NULL; char buf[4096]; ssize_t nbytes; int pasv_port, found; unsigned io_pos; #ifdef _WIN32 signal(SIGTERM, sigterm_handler); #else sigaction(SIGTERM, &(struct sigaction) { .sa_handler = sigterm_handler }, NULL); #endif while (!terminate) { wget_tcp_deinit(&tcp); wget_tcp_deinit(&pasv_tcp); wget_tcp_deinit(&pasv_parent_tcp); if ((tcp = wget_tcp_accept(parent_tcp))) { io_pos = 0; if (server_hello) wget_tcp_printf(tcp, "%s\r\n", server_hello); // as a quick hack, just assume that each line comes in one packet while ((nbytes = wget_tcp_read(tcp, buf, sizeof(buf)-1)) > 0) { buf[nbytes] = 0; while (--nbytes >= 0 && (buf[nbytes] == '\r' || buf[nbytes] == '\n')) buf[nbytes] = 0; wget_debug_printf("### Got: '%s'\n", buf); found = 0; if (ios_ordered) { if (!strcmp(buf, ios[io_pos].in)) found = 1; } else { for (io_pos = 0; io_pos < nios; io_pos++) { if (!strcmp(buf, ios[io_pos].in)) { found = 1; break; } } } if (!found) { wget_error_printf(_("Unexpected input: '%s'\n"), buf); wget_tcp_printf(tcp, "500 Unknown command\r\n"); continue; } if (!strncmp(buf, "AUTH", 4)) { // assume TLS auth type wget_tcp_printf(tcp, "%s\r\n", ios[io_pos].out); if (atoi(ios[io_pos].out)/100 == 2) wget_tcp_tls_start(tcp); io_pos++; continue; } if (!strncmp(buf, "PASV", 4) || !strncmp(buf, "EPSV", 4)) { // init FTP PASV/EPSV socket // we ignore EPSV address type here, we listen on IPv4 and IPv6 anyways pasv_parent_tcp=wget_tcp_init(); wget_tcp_set_timeout(pasv_parent_tcp, -1); // INFINITE timeout if (!strncmp(buf, "EPSV", 4)) { switch (atoi(buf+4)) { case 1: wget_tcp_set_family(pasv_parent_tcp, WGET_NET_FAMILY_IPV4); break; case 2: wget_tcp_set_family(pasv_parent_tcp, WGET_NET_FAMILY_IPV6); break; default: wget_tcp_set_family(pasv_parent_tcp, WGET_NET_FAMILY_ANY); break; } } if (wget_tcp_listen(pasv_parent_tcp, "localhost", "0", 5) != 0) { wget_tcp_printf(tcp, "500 failed to open port\r\n"); break; } pasv_port = wget_tcp_get_local_port(pasv_parent_tcp); const char *src = ios[io_pos].out; char *response = wget_malloc(strlen(src) + 32 + 1); char *dst = response; while (*src) { if (*src == '{') { if (!strncmp(src, "{{pasvdata}}", 12)) { if (!strncmp(buf, "EPSV", 4)) dst += sprintf(dst, "(|||%d|)", pasv_port); else dst += sprintf(dst, "(127,0,0,1,%d,%d)", pasv_port / 256, pasv_port % 256); src += 12; continue; } } *dst++ = *src++; } *dst = 0; wget_tcp_printf(tcp, "%s\r\n", response); wget_xfree(response); if (!(pasv_tcp = wget_tcp_accept(pasv_parent_tcp))) { wget_error_printf(_("Failed to get PASV connection\n")); break; } } else { wget_tcp_printf(tcp, "%s\r\n", ios[io_pos].out); } if (ios[io_pos].send_url && pasv_tcp) { // send data wget_tcp_printf(pasv_tcp, "%s", ios[io_pos].send_url->body); wget_tcp_deinit(&pasv_tcp); wget_tcp_printf(tcp, "226 Transfer complete\r\n"); } io_pos++; } } else if (!terminate) wget_error_printf(_("Failed to get connection (%d)\n"), errno); } wget_tcp_deinit(&parent_tcp); return NULL; } #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]; 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; struct dirent *dp; size_t dirlen = strlen(dirname); if ((dir = opendir(dirname))) { 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[dirlen + 1 + strlen(dp->d_name) + 1]; snprintf(fname, sizeof(fname), "%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 if (errno == EISDIR || errno == EPERM) _remove_directory(fname); else wget_error_printf(_("Failed to unlink %s (%d)\n"), fname, errno); } } 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); for (wget_test_url_t *url = urls; url < urls + nurls; url++) { if (url->body_alloc) { wget_xfree(url->body); url->body_alloc = 0; } for (size_t it = 0; it < countof(url->headers); it++) { if (url->header_alloc[it]) { wget_xfree(url->headers[it]); url->header_alloc[it] = 0; } } } // free resources - needed for valgrind testing pthread_kill(http_server_tid, SIGTERM); pthread_kill(https_server_tid, SIGTERM); pthread_kill(ftp_server_tid, SIGTERM); if (ftps_implicit) pthread_kill(ftps_server_tid, SIGTERM); wget_thread_join(http_server_tid); wget_thread_join(https_server_tid); wget_thread_join(ftp_server_tid); if (ftps_implicit) wget_thread_join(ftps_server_tid); if (chdir("..") != 0) wget_error_printf(_("Failed to chdir ..\n")); if (!keep_tmpfiles) _remove_directory(tmpdir); wget_global_deinit(); } static char *_insert_ports(const char *src) { if (!src || (!strstr(src, "{{port}}") && !strstr(src, "{{sslport}}") && !strstr(src, "{{ftpport}}") && !strstr(src, "{{ftpsport}}"))) return NULL; char *ret = wget_malloc(strlen(src) + 1); char *dst = ret; while (*src) { if (*src == '{') { if (!strncmp(src, "{{port}}", 8)) { dst += sprintf(dst, "%d", http_server_port); src += 8; continue; } else if (!strncmp(src, "{{sslport}}", 11)) { dst += sprintf(dst, "%d", https_server_port); src += 11; continue; } else if (!strncmp(src, "{{ftpport}}", 11)) { dst += sprintf(dst, "%d", ftp_server_port); src += 11; continue; } else if (!strncmp(src, "{{ftpsport}}", 12)) { dst += sprintf(dst, "%d", ftps_server_port); src += 12; continue; } } *dst++ = *src++; } *dst = 0; return ret; } static void _write_msg(const char *msg, size_t len) { if (isatty(fileno(stderr))) { if (len && msg[len - 1] == '\n') len--; fprintf(stderr, "\033[33m%.*s\033[m\n", (int) len, msg); } else fwrite(msg, 1, len, stderr); } void wget_test_start_server(int first_key, ...) { static wget_tcp_t *http_parent_tcp, *https_parent_tcp, *ftp_parent_tcp, *ftps_parent_tcp; int rc, key; size_t it; va_list args; /* Skip any test that use this function if threads are not present. */ if (!wget_thread_support()) { wget_error_printf("THREADS NOT SUPPORTED: Skip\n"); exit(77); } wget_global_init( WGET_DEBUG_FUNC, _write_msg, WGET_ERROR_FUNC, _write_msg, WGET_INFO_FUNC, _write_msg, NULL); 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_EXPECTED_REQUEST_HEADER: break; case WGET_TEST_RESPONSE_URLS: urls = va_arg(args, wget_test_url_t *); nurls = va_arg(args, size_t); break; case WGET_TEST_FTP_SERVER_HELLO: server_hello = va_arg(args, const char *); break; case WGET_TEST_FTP_IO_ORDERED: ios_ordered = 1; ios = va_arg(args, wget_test_ftp_io_t *); nios = va_arg(args, size_t); break; case WGET_TEST_FTP_IO_UNORDERED: ios_ordered = 0; ios = va_arg(args, wget_test_ftp_io_t *); nios = va_arg(args, size_t); break; case WGET_TEST_FTPS_IMPLICIT: ftps_implicit = va_arg(args, int); break; case WGET_TEST_SERVER_SEND_CONTENT_LENGTH: server_send_content_length = va_arg(args, int); break; default: wget_error_printf(_("Unknown option %d\n"), key); } } va_end(args); atexit(wget_test_stop_server); 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); // init server SSL layer (default cert and key file types are PEM) wget_ssl_set_config_string(WGET_SSL_CA_FILE, SRCDIR "/certs/x509-ca-cert.pem"); wget_ssl_set_config_string(WGET_SSL_CERT_FILE, SRCDIR "/certs/x509-server-cert.pem"); wget_ssl_set_config_string(WGET_SSL_KEY_FILE, SRCDIR "/certs/x509-server-key.pem"); // init HTTP server socket http_parent_tcp = wget_tcp_init(); wget_tcp_set_timeout(http_parent_tcp, -1); // INFINITE timeout wget_tcp_set_preferred_family(http_parent_tcp, WGET_NET_FAMILY_IPV4); // to have a defined order of IPs if (wget_tcp_listen(http_parent_tcp, "localhost", NULL, 5) != 0) exit(1); http_server_port = wget_tcp_get_local_port(http_parent_tcp); // init HTTPS server socket https_parent_tcp = wget_tcp_init(); wget_tcp_set_ssl(https_parent_tcp, 1); // switch SSL on wget_tcp_set_timeout(https_parent_tcp, -1); // INFINITE timeout wget_tcp_set_preferred_family(https_parent_tcp, WGET_NET_FAMILY_IPV4); // to have a defined order of IPs if (wget_tcp_listen(https_parent_tcp, "localhost", NULL, 5) != 0) exit(1); https_server_port = wget_tcp_get_local_port(https_parent_tcp); // init FTP server socket ftp_parent_tcp = wget_tcp_init(); wget_tcp_set_timeout(ftp_parent_tcp, -1); // INFINITE timeout wget_tcp_set_preferred_family(ftp_parent_tcp, WGET_NET_FAMILY_IPV4); // to have a defined order of IPs if (wget_tcp_listen(ftp_parent_tcp, "localhost", NULL, 5) != 0) exit(1); ftp_server_port = wget_tcp_get_local_port(ftp_parent_tcp); if (ftps_implicit) { // init FTPS server socket ftps_parent_tcp = wget_tcp_init(); wget_tcp_set_ssl(ftps_parent_tcp, 1); // switch SSL on wget_tcp_set_timeout(ftps_parent_tcp, -1); // INFINITE timeout wget_tcp_set_preferred_family(ftps_parent_tcp, WGET_NET_FAMILY_IPV4); // to have a defined order of IPs if (wget_tcp_listen(ftps_parent_tcp, "localhost", NULL, 5) != 0) exit(1); ftps_server_port = wget_tcp_get_local_port(ftps_parent_tcp); } // 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 = p; url->body_alloc = 1; } for (it = 0; it < countof(url->headers) && url->headers[it]; it++) { p = _insert_ports(url->headers[it]); if (p) { url->headers[it] = p; url->header_alloc[it] = 1; } } } // start thread for HTTP if ((rc = wget_thread_start(&http_server_tid, _http_server_thread, http_parent_tcp, 0)) != 0) wget_error_printf_exit(_("Failed to start HTTP server, error %d\n"), rc); // start thread for HTTPS if ((rc = wget_thread_start(&https_server_tid, _http_server_thread, https_parent_tcp, 0)) != 0) wget_error_printf_exit(_("Failed to start HTTPS server, error %d\n"), rc); // start thread for FTP if ((rc = wget_thread_start(&ftp_server_tid, _ftp_server_thread, ftp_parent_tcp, 0)) != 0) wget_error_printf_exit(_("Failed to start FTP server, error %d\n"), rc); // start thread for FTPS if (ftps_implicit) { if ((rc = wget_thread_start(&ftps_server_tid, _ftp_server_thread, ftps_parent_tcp, 0)) != 0) wget_error_printf_exit(_("Failed to start FTP server, error %d\n"), rc); } } static void _scan_for_unexpected(const char *dirname, const wget_test_file_t *expected_files) { DIR *dir; struct dirent *dp; struct stat st; size_t it, dirlen = strlen(dirname); wget_info_printf("Entering %s\n", dirname); if ((dir = opendir(dirname))) { while ((dp = readdir(dir))) { char fname[dirlen + 1 + strlen(dp->d_name) + 1]; if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) continue; if (*dirname == '.' && dirname[1] == 0) sprintf(fname, "%s", dp->d_name); else sprintf(fname, "%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); 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__) wget_info_printf("search %s\n", fname); for (it = 0; expected_files[it].name && strcmp(expected_files[it].name, fname); it++); 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); } closedir(dir); } else wget_error_printf_exit(_("Failed to diropen %s\n"), dirname); } void wget_test(int first_key, ...) { const char *request_url, *options="", *executable="../../src/wget2_noinstall" EXEEXT " -d --no-config --max-threads=1 --prefer-family=ipv4 --no-proxy"; const wget_test_file_t *expected_files = NULL, *existing_files = NULL; wget_buffer_t *cmd = wget_buffer_alloc(1024); unsigned it; int key, fd, rc, expected_error_code = 0; va_list args; char server_send_content_length_old = server_send_content_length; keep_tmpfiles = 0; server_hello = "220 FTP server ready"; if (!request_urls) request_urls = wget_vector_create(8,8,NULL); 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_str(request_urls, request_url); break; case WGET_TEST_REQUEST_URLS: while ((request_url = va_arg(args, const char *))) wget_vector_add_str(request_urls, request_url); break; case WGET_TEST_EXPECTED_ERROR_CODE: expected_error_code = 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 *); break; case WGET_TEST_KEEP_TMPFILES: keep_tmpfiles = va_arg(args, int); break; case WGET_TEST_EXECUTABLE: executable = va_arg(args, const char *); break; case WGET_TEST_FTP_SERVER_HELLO: server_hello = va_arg(args, const char *); break; case WGET_TEST_SERVER_SEND_CONTENT_LENGTH: server_send_content_length = va_arg(args, int); break; default: wget_error_printf_exit(_("Unknown option %d [%s]\n"), key, options); } } va_end(args); // 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++) { if ((fd = open(existing_files[it].name, O_CREAT|O_WRONLY|O_TRUNC, 0644)) != -1) { ssize_t nbytes = write(fd, existing_files[it].content, strlen(existing_files[it].content)); close(fd); if (nbytes != (ssize_t)strlen(existing_files[it].content)) wget_error_printf_exit(_("Failed to write %zu bytes to file %s/%s [%s]\n"), strlen(existing_files[it].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); } } 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 %s", emulator, executable, EXEEXT, options); else wget_buffer_printf(cmd, "%s%s %s", executable, EXEEXT, options); wget_info_printf("cmd=%s\n", cmd->data); } else if (!strcmp(valgrind, "1")) { wget_buffer_printf(cmd, "valgrind --error-exitcode=301 --leak-check=yes --show-reachable=yes --track-origins=yes --suppressions=" SRCDIR "/valgrind-suppressions %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++) { wget_buffer_printf_append(cmd, " 'http://localhost:%d/%s'", http_server_port, (char *)wget_vector_get(request_urls, it)); } // for (it = 0; it < (size_t)wget_vector_size(ftp_files); it++) { // wget_buffer_printf_append2(cmd, " 'ftp://localhost:%d/%s'", // ftp_server_port, (char *)wget_vector_get(ftp_files, it)); // } wget_buffer_strcat(cmd, " 2>&1"); wget_error_printf("\n Testing '%s'\n", cmd->data); 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) { 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; if (stat(expected_files[it].name, &st) != 0) wget_error_printf_exit(_("Missing expected file %s/%s [%s]\n"), tmpdir, expected_files[it].name, options); if (expected_files[it].content) { char content[st.st_size ? st.st_size : 1]; if ((fd = open(expected_files[it].name, O_RDONLY)) != -1) { ssize_t nbytes = read(fd, content, st.st_size); close(fd); if (nbytes != st.st_size) wget_error_printf_exit(_("Failed to read %lld bytes from file %s/%s [%s]\n"), (long long)st.st_size, tmpdir, expected_files[it].name, options); if (strlen(expected_files[it].content) != (size_t)nbytes || memcmp(expected_files[it].content, content, nbytes) != 0) wget_error_printf_exit(_("Unexpected content in %s [%s]\n"), expected_files[it].name, options); } } if (expected_files[it].timestamp && st.st_mtime != expected_files[it].timestamp) wget_error_printf_exit(_("Unexpected timestamp %s/%s [%s]\n"), tmpdir, expected_files[it].name, options); } // look if there are unexpected files in our working dir _scan_for_unexpected(".", expected_files); } wget_vector_clear(request_urls); wget_buffer_free(&cmd); server_send_content_length = server_send_content_length_old; // system("ls -la"); } int wget_test_get_http_server_port(void) { return http_server_port; } int wget_test_get_https_server_port(void) { return https_server_port; } int wget_test_get_ftp_server_port(void) { return ftp_server_port; } int wget_test_get_ftps_server_port(void) { return ftps_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-sensitive 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, 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-sensitive for (unsigned it = 0; it < countof(fname); it++) { if ((fd = open(fname[it], O_RDONLY, 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 (strcmp(buf, fname[it])) { wget_debug_printf("%s: Found case-sensitive 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-insensitive file system\n", __func__); out: _empty_directory(tmpdir); return flags; }