libmicrohttpd2

HTTP server C library (MHD 2.x, alpha)
Log | Files | Refs | README | LICENSE

commit e5dc09c4c1c67b426b2dc87390e7f51d8f707008
parent f1cac449f1fb07c9f386e5be8903b61520fde283
Author: Evgeny Grin (Karlson2k) <k2k@drgrin.dev>
Date:   Sun, 19 Jul 2026 12:43:31 +0200

Implemented portable code for temporary files for the test suite

Diffstat:
Mconfigure.ac | 43+++++++++++++++++++++++++++++++++++++++++++
Msrc/tests/.gitignore | 1+
Msrc/tests/client_server/Makefile.am | 3++-
Asrc/tests/client_server/mhdt_tmpfile.c | 796+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/tests/client_server/mhdt_tmpfile.h | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 912 insertions(+), 1 deletion(-)

diff --git a/configure.ac b/configure.ac @@ -8364,6 +8364,49 @@ AC_LINK_IFELSE( AM_CONDITIONAL([HAVE_FORK_WAITPID], [test "x$mhd_have_fork_waitpid" = "xyes"]) + +# Other stuff used in testing + +MHD_CHECK_FUNC([mkostemp], + [[ +#if defined(HAVE_STDLIB_H) +# include <stdlib.h> +#endif +#if defined(HAVE_UNISTD_H) +/* On Darwin mkostemp() is declared in unistd.h */ +# include <unistd.h> +#endif +#include <fcntl.h> + ]], + [[ + char tmp_fname[] = "/tmp/mhd-test-XXXXXX"; + int tmp_fd = mkostemp(tmp_fname, 0); + i][f (0 > tmp_fd) + return 3; + close(tmp_fd); + ]], + [], + [ + MHD_CHECK_FUNC([mkstemp], + [[ +#if defined(HAVE_STDLIB_H) +# include <stdlib.h> +#endif +#if defined(HAVE_UNISTD_H) +# include <unistd.h> +#endif + ]], + [[ + char tmp_fname[] = "/tmp/mhd-test-XXXXXX"; + int tmp_fd = mkstemp(tmp_fname); + i][f (0 > tmp_fd) + return 3; + close(tmp_fd); + ]] + ) + ] +) + # gcov compilation AC_MSG_CHECKING(whether to compile with support for code coverage analysis) AC_ARG_ENABLE([coverage], diff --git a/src/tests/.gitignore b/src/tests/.gitignore @@ -1,3 +1,4 @@ test_*[a-z0-9_][a-z0-9_][a-z0-9_] !*.c !*.h +mhdt-tmp-* diff --git a/src/tests/client_server/Makefile.am b/src/tests/client_server/Makefile.am @@ -56,7 +56,8 @@ libmhdt_la_SOURCES = \ libtest.c libtest.h \ libtest_convenience.c \ libtest_convenience_client_request.c \ - libtest_convenience_server_reply.c + libtest_convenience_server_reply.c \ + mhdt_tmpfile.c mhdt_tmpfile.h # TODO: fix out-of-tree 'make check' EXTRA_DIST = \ diff --git a/src/tests/client_server/mhdt_tmpfile.c b/src/tests/client_server/mhdt_tmpfile.c @@ -0,0 +1,796 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2026 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd 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 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/tests/client_server/mhdt_tmpfile.c + * @brief MHD test framework temporary file helpers + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" + +#include <stdint.h> +#include <string.h> +#include <stdlib.h> +#include <fcntl.h> +#include <errno.h> + +#if !defined(_WIN32) || defined(__CYGWIN__) +# ifdef HAVE_UNISTD_H +# include <unistd.h> +# endif +# include <stdio.h> /* for P_tmpdir */ +#else +# define mhdt_USE_W32 1 +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN 1 +# endif +# include <windows.h> +# include <wchar.h> +# include <sys/types.h> +# include <sys/stat.h> +# include <io.h> +#endif + +#include "mhdt_tmpfile.h" + +#ifndef mhdt_ARR_CNT +# define mhdt_ARR_CNT(arr) (sizeof(arr)/sizeof(arr[0])) +#endif + +#ifndef MHDT_TMP_FILE_BASENAME_PREFIX +# define MHDT_TMP_FILE_BASENAME_PREFIX "mhdt-tmp-" +#endif + +/* The length of the unique file suffix */ +#ifndef MHDT_TMP_FILE_UNIQUE_SUFFIX_LEN +# define MHDT_TMP_FILE_UNIQUE_SUFFIX_LEN 6 +#endif + +#if MHDT_TMP_FILE_UNIQUE_SUFFIX_LEN < 6 +# error MHDT_TMP_FILE_UNIQUE_SUFFIX_LEN is less than 6 +#endif + +/* Maximum length of a normalised temporary directory name, + including the trailing slash but excluding the terminating NUL. */ +#if !defined(MHDT_FILENAME_MAX) +# if defined(FILENAME_MAX) +# define MHDT_FILENAME_MAX FILENAME_MAX +# elif defined(MAX_PATH) +# define MHDT_FILENAME_MAX MAX_PATH +# endif +#endif + +#if defined(MHDT_FILENAME_MAX) && (MHDT_FILENAME_MAX + 0) > 4096 +# undef MHDT_FILENAME_MAX +#endif + +#ifndef MHDT_FILENAME_MAX +# define MHDT_FILENAME_MAX 4096 +#endif + +#ifndef mhdt_MAX_TRIES +# define mhdt_MAX_TRIES 127 +#endif + +#define MHDTL_UINT32_MASK 0xFFFFFFFFu + + +static inline uint_least32_t +mhdtl_rotl32 (uint_least32_t val, + unsigned int rot) +{ + uint_fast32_t v; + + rot &= 31u; + v = ((uint_fast32_t)val) & MHDTL_UINT32_MASK; + + return (uint_least32_t) + (((v << rot) + | (v >> ((32u - rot) & 31u))) + & MHDTL_UINT32_MASK); +} + + +/** + * Mix bits of two 32-bit values and produce a pseudo-random 32-bit value + * based on them. + * @param a the first value to mix + * @param b the second value to mix + * @return the pseudo-random value produced from the input values + */ +static inline uint_least32_t +mhdtl_mix32 (uint_fast32_t a, + uint_fast32_t b) +{ + uint_fast32_t res; + + /* Combine the inputs: multiplication by the Golden Ratio's fractional + * part diffuses the bits of 'a' before they meet the bits of 'b', + * while the rotation breaks the symmetry between the two inputs. */ + res = + ((((a & MHDTL_UINT32_MASK) * 0x9E3779B9u) & MHDTL_UINT32_MASK) + ^ mhdtl_rotl32 ((uint_least32_t)b, 13u)); + /* Re-mix bits better. + * The shifts and the multipliers are the MurmurHash3 32-bit finaliser. */ + res ^= res >> 16u; + res = (res * 0x85EBCA6Bu) & MHDTL_UINT32_MASK; + res ^= res >> 13u; + res = (res * 0xC2B2AE35u) & MHDTL_UINT32_MASK; + res ^= res >> 16u; + + return (uint_least32_t)res; +} + + +#ifndef mhdt_USE_W32 + +/** + * Try to add the specified directory to the list of the directories used + * for temporary files. + * + * The candidate name is normalised before use: a trailing slash is appended + * if the name does not end with a slash already. + * The candidate is silently ignored if the name is NULL or empty, if + * the normalised name is too long to fit into the list entry, or if + * the normalised name duplicates any name already present in the list. + * The @p tmp_dirs array must have a free entry at the index + * @p *dirs_have_ptr. + * + * @param tmp_dir_name the name of the candidate directory, can be NULL + * @param[in,out] tmp_dirs the array of the names of the directories used + * for temporary files + * @param[in,out] dirs_have_ptr the pointer to the variable with the number + * of the used entries in the @p tmp_dirs array, + * the value is incremented if the candidate + * is added + */ +static void +tmpdirs_try_add (const char *tmp_dir_name, + char (*tmp_dirs)[MHDT_FILENAME_MAX + 1], + unsigned int *dirs_have_ptr) +{ + unsigned int i; + char tmp_dir[MHDT_FILENAME_MAX + 1]; + size_t tmp_dir_name_len; + + if (NULL == tmp_dir_name) + return; + tmp_dir_name_len = strlen (tmp_dir_name); + if (0u == tmp_dir_name_len) + return; + if (((size_t)MHDT_FILENAME_MAX) < tmp_dir_name_len) + return; /* String 'tmp_dir_name' is too long to use */ + + memcpy (tmp_dir, + tmp_dir_name, + tmp_dir_name_len + 1); + + if ('/' != tmp_dir[tmp_dir_name_len - 1u]) + { + if (((size_t)MHDT_FILENAME_MAX) == tmp_dir_name_len) + return; /* String 'tmp_dir_name' is too long to use */ + tmp_dir[tmp_dir_name_len++] = '/'; + tmp_dir[tmp_dir_name_len] = '\0'; + } + + for (i = 0; i < *dirs_have_ptr; ++i) + if (0 == strcmp (tmp_dir, tmp_dirs[i])) + return; /* Found duplicate */ + + memcpy (tmp_dirs[*dirs_have_ptr], tmp_dir, tmp_dir_name_len + 1u); + ++(*dirs_have_ptr); +} + + +/** + * Fill the buffer with the hexadecimal representation of the number. + * + * Exactly @p buf_size - 1 lower hexadecimal digits of the @p number are + * written (zero-padded), followed by the terminating NUL. + * @param buf_size the size of the @p buf buffer in characters, + * must not be zero + * @param[out] buf the buffer to fill + * @param number the number to convert + */ +static void +mhdt_num_to_hex (size_t buf_size, + char *buf, + uint_least32_t number) +{ + static const char hex_map[] = "0123456789abcdef"; + size_t p = buf_size - 1u; + if (p > buf_size) + abort (); + buf[p] = '\0'; + while (--p < buf_size) + { + buf[p] = hex_map[number & 0xFu]; + number >>= 4u; + } +} + + +/** + * Get a directory for temporary files + * @param fallback_num the number of the fallback location, + * zero for the main location + * @return the temporary directory string (always ends with a slash, the length + * of the string is not greater than #MHDT_FILENAME_MAX), + * NULL if @p fallback_num is too high + */ +static const char * +mhdt_get_tmpfile_dir (unsigned int fallback_num) +{ + /* 'volatile' does not provide thread-safety, but the stored values are + identical for every initialisation. + This simplification is considered acceptable for the testing code. */ + static volatile unsigned int dirs_have = 0u; + static char tmp_dirs[7][MHDT_FILENAME_MAX + 1] = + { "", "", "", "", "", "", "" }; + + if (0u == dirs_have) + { + unsigned int dirs_found = 0u; + + tmpdirs_try_add (getenv ("TMPDIR"), tmp_dirs, &dirs_found); + + tmpdirs_try_add (getenv ("TMP"), tmp_dirs, &dirs_found); + + tmpdirs_try_add (getenv ("TEMP"), tmp_dirs, &dirs_found); + + tmpdirs_try_add (".", tmp_dirs, &dirs_found); + +# ifdef P_tmpdir + tmpdirs_try_add (P_tmpdir, tmp_dirs, &dirs_found); +# endif + + tmpdirs_try_add ("/tmp", tmp_dirs, &dirs_found); + + if (sizeof(tmp_dirs) / sizeof(tmp_dirs[0]) < dirs_found) + abort (); + + if (0u == dirs_found) + abort (); + + dirs_have = dirs_found; + } + + if (dirs_have <= fallback_num) + return NULL; + + return tmp_dirs[fallback_num]; +} + + +/** + * Create a temporary file that is deleted automatically when the FD is + * closed. + * + * The file is opened for both reading and writing. + * The close-on-exec flag is set for the FD (if supported by the platform). + * + * The locations for temporary files are tried in the following order: + * + the directories set by the "TMPDIR", the "TMP" and the "TEMP" + * environment variables, + * + the current directory, + * + the directory specified by the P_tmpdir macro (if defined), + * + and the "/tmp" directory. + * The locations that are unset, too long or duplicated are skipped. + * + * @return the FD of the created file on success, + * -1 if the file cannot be created in any of the locations + */ +static int +mhdt_create_tmpfile_posix (void) +{ + static const char basename_prfx[] = MHDT_TMP_FILE_BASENAME_PREFIX; + const char *tmp_dir; + unsigned int i; + + for (i = 0u; NULL != (tmp_dir = mhdt_get_tmpfile_dir (i)); ++i) + { + char tmp_filename[MHDT_FILENAME_MAX + + mhdt_ARR_CNT (basename_prfx) + + MHDT_TMP_FILE_UNIQUE_SUFFIX_LEN]; + int tmpfile_fd; + int need_cloexec; + int need_unlink; + size_t tmp_dir_len; + + tmp_dir_len = strlen (tmp_dir); + memcpy (tmp_filename, tmp_dir, tmp_dir_len); + memcpy (tmp_filename + tmp_dir_len, + basename_prfx, + mhdt_ARR_CNT (basename_prfx) - 1u); + memset (tmp_filename + tmp_dir_len + mhdt_ARR_CNT (basename_prfx) - 1u, + 'X', + MHDT_TMP_FILE_UNIQUE_SUFFIX_LEN); + tmp_filename[tmp_dir_len + mhdt_ARR_CNT (basename_prfx) - 1u + + MHDT_TMP_FILE_UNIQUE_SUFFIX_LEN] = '\0'; + + tmpfile_fd = -1; + need_cloexec = !0; + need_unlink = !0; + +# ifdef O_TMPFILE + if (0 > tmpfile_fd) + { + int flags; + flags = O_RDWR | O_TMPFILE; +# ifdef O_EXCL + flags |= O_EXCL; +# else + /* The missing O_EXCL support cannot be replaced with anything. + Open file without O_EXCL as this is only the testing code. */ +# endif +# ifdef O_CLOEXEC + flags |= O_CLOEXEC; +# endif +# if defined(O_CLOFORK) && !defined(__linux__) + flags |= O_CLOFORK; +# endif + + tmpfile_fd = open (tmp_dir, + flags, + 0600); + if (0 <= tmpfile_fd) + { + need_unlink = 0; +# ifdef O_CLOEXEC + need_cloexec = 0; +# endif + } + } +# endif + +# ifdef HAVE_MKOSTEMP + if (0 > tmpfile_fd) + { + int flags; + flags = 0; + +# ifdef O_CLOEXEC + flags |= O_CLOEXEC; +# endif +# if defined(O_CLOFORK) && !defined(__linux__) + flags |= O_CLOFORK; +# endif + tmpfile_fd = mkostemp (tmp_filename, + flags); +# ifdef O_CLOEXEC + if (0 <= tmpfile_fd) + need_cloexec = 0; +# endif + } +# elif defined(HAVE_MKSTEMP) + if (0 > tmpfile_fd) + tmpfile_fd = mkstemp (tmp_filename); +# endif + + /* Last resort */ + if (0 > tmpfile_fd) + { + static volatile unsigned int cnt = 0u; + int flags; + int t; + uint_least32_t suff_num; + +# if defined(HAVE_MKOSTEMP) || defined(HAVE_MKSTEMP) + /* Restore the filename template in case it was broken */ + memcpy (tmp_filename, tmp_dir, tmp_dir_len); + memcpy (tmp_filename + tmp_dir_len, + basename_prfx, + mhdt_ARR_CNT (basename_prfx) - 1u); +# endif /* HAVE_MKOSTEMP || HAVE_MKSTEMP */ + + flags = O_CREAT | O_RDWR; + /* Last resort: do not try O_CLOFORK as it may be unsupported */ +# ifdef O_EXCL + flags |= O_EXCL; +# else + /* The missing O_EXCL support cannot be replaced with anything. + Open file without O_EXCL as this is only the testing code. */ +# endif +# ifdef O_CLOEXEC + flags |= O_CLOEXEC; +# endif + suff_num = mhdtl_mix32 ((uint_least32_t)getpid (), + (uint_least32_t)mhd_PTR_TO_INT (tmp_filename)); + for (t = 0; mhdt_MAX_TRIES > t; ++t) + { + char *suff_ptr; + suff_num = mhdtl_mix32 (suff_num, (uint_least32_t)cnt++); + suff_ptr = + tmp_filename + tmp_dir_len + mhdt_ARR_CNT (basename_prfx) - 1u; + + mhdt_num_to_hex (MHDT_TMP_FILE_UNIQUE_SUFFIX_LEN + 1u, + suff_ptr, + suff_num); + + tmpfile_fd = open (tmp_filename, + flags, + 0600); + if (0 <= tmpfile_fd) + { +# ifdef O_CLOEXEC + need_cloexec = 0; +# endif + break; + } + else if ((EEXIST != errno) && (EINTR != errno)) + break; /* Avoid useless retries */ + } + } + + if (0 > tmpfile_fd) + continue; /* Try next directory */ + + /* Best effort to set CLOEXEC */ +# ifdef FD_CLOEXEC + if (need_cloexec) + { + int flags; + + flags = fcntl (tmpfile_fd, + F_GETFD); + if (0 <= flags) + { + flags |= FD_CLOEXEC; +# if defined(FD_CLOFORK) + if (0 == fcntl (tmpfile_fd, + F_SETFD, + flags | FD_CLOFORK)) + (void)0; /* Success. Do nothing. */ + else +# endif + if (!0) + { /* FD_CLOFORK may be unsupported */ + int res; + res = fcntl (tmpfile_fd, + F_SETFD, + flags); + (void)res; /* Ignore result */ + } + } + } +# else + (void)need_cloexec; /* Use without CLOEXEC */ +# endif + + if (need_unlink) + { + if (0 != unlink (tmp_filename)) + { + int res; + /* The opened file cannot be unlinked. */ + res = close (tmpfile_fd); + (void)res; /* Ignore result */ + res = unlink (tmp_filename); /* Retry the removal with closed FD */ + (void)res; /* Ignore result */ + continue; /* Try next directory */ + } + } + + return tmpfile_fd; + } + + return -1; +} + + +#else /* W32 */ + +/** + * Add a temporary-directory candidate to the list. + * + * The candidate is normalised in place to end with a backslash. It is ignored + * if NULL, empty, too long, or an exact duplicate of an existing entry. + * @param tmp_dir_name_len length of @p tmp_dir_name, excluding the NUL + * @param[in,out] tmp_dir_name writable NUL-terminated buffer of + * #MHDT_FILENAME_MAX + 1 characters + * @param[in,out] tmp_dirs destination array with a free entry at + * @p *dirs_have_ptr + * @param[in,out] dirs_have_ptr number of used entries, incremented on addition + */ +static void +tmpdirs_try_add_w32 (unsigned long tmp_dir_name_len, + wchar_t tmp_dir_name[MHDT_FILENAME_MAX + 1], + wchar_t (*tmp_dirs)[MHDT_FILENAME_MAX + 1], + unsigned int *dirs_have_ptr) +{ + unsigned int i; + size_t tmp_dir_name_len_sz; + + if (NULL == tmp_dir_name) + return; + if (0u == tmp_dir_name_len) + return; + if (((unsigned long)MHDT_FILENAME_MAX) < tmp_dir_name_len) + return; /* String 'tmp_dir_name' cannot fit the buffer */ + tmp_dir_name_len_sz = (size_t)tmp_dir_name_len; + + + if (L'/' == tmp_dir_name[tmp_dir_name_len_sz - 1u]) + tmp_dir_name[tmp_dir_name_len_sz - 1u] = L'\\'; + + if (L'\\' != tmp_dir_name[tmp_dir_name_len_sz - 1u]) + { + if (((size_t)MHDT_FILENAME_MAX) == tmp_dir_name_len_sz) + return; /* String 'tmp_dir_name' is too long to use */ + /* This converts a drive-relative path "C:" to the root dir "C:\" which is + the desired effect (a drive-relative path is a bad choice). */ + tmp_dir_name[tmp_dir_name_len_sz++] = L'\\'; + tmp_dir_name[tmp_dir_name_len_sz] = L'\0'; + } + + /* Simplified naive deduplication: not all slashes are normalised and + characters are not case-folded. + The filesystems on W32 can be case-sensitive (even with a per-directory + attribute). */ + for (i = 0; i < *dirs_have_ptr; ++i) + if (0 == wcscmp (tmp_dir_name, tmp_dirs[i])) + return; /* Found duplicate */ + + wmemcpy (tmp_dirs[*dirs_have_ptr], + tmp_dir_name, + tmp_dir_name_len_sz + 1u); + ++(*dirs_have_ptr); +} + + +/** + * Get a directory for temporary files + * @param fallback_num the number of the fallback location, + * zero for the main location + * @return the temporary directory string (always ends with a backslash, + * the length of the string is not greater than #MHDT_FILENAME_MAX), + * NULL if @p fallback_num is too high + */ +static const wchar_t * +mhdt_get_tmpfile_dir_w32 (unsigned int fallback_num) +{ + /* 'volatile' does not provide thread-safety, but the stored values are + identical for every initialisation. + This simplification is considered acceptable for the testing code. */ + static volatile unsigned int dirs_have = 0u; + static wchar_t tmp_dirs[6][MHDT_FILENAME_MAX + 1] = + { L"", L"", L"", L"", L"", L"" }; + wchar_t var_buf[MHDT_FILENAME_MAX + 1]; + const unsigned int var_buf_size = (sizeof(var_buf) / sizeof(var_buf[0])); + + if (0u == dirs_have) + { + unsigned int dirs_found = 0u; + + tmpdirs_try_add_w32 (GetEnvironmentVariableW (L"TMP", + var_buf, + var_buf_size), + var_buf, + tmp_dirs, + &dirs_found); + + tmpdirs_try_add_w32 (GetEnvironmentVariableW (L"TEMP", + var_buf, + var_buf_size), + var_buf, + tmp_dirs, + &dirs_found); + + wmemcpy (var_buf, L".\\", 2u + 1u); + tmpdirs_try_add_w32 (2u, + var_buf, + tmp_dirs, + &dirs_found); + + tmpdirs_try_add_w32 (GetEnvironmentVariableW (L"USERPROFILE", + var_buf, + var_buf_size), + var_buf, + tmp_dirs, + &dirs_found); + + /* This is mostly a duplication of one of the previous values */ + tmpdirs_try_add_w32 (GetTempPathW (var_buf_size, + var_buf), + var_buf, + tmp_dirs, + &dirs_found); + + if (sizeof(tmp_dirs) / sizeof(tmp_dirs[0]) < dirs_found) + abort (); + + if (0u == dirs_found) + abort (); + + dirs_have = dirs_found; + } + + if (dirs_have <= fallback_num) + return NULL; + + return tmp_dirs[fallback_num]; +} + + +/** + * Fill the buffer with the hexadecimal representation of the number. + * + * Exactly @p buf_size - 1 lower hexadecimal digits of the @p number are + * written (zero-padded), followed by the terminating NUL. + * @param buf_size the size of the @p buf buffer in characters, + * must not be zero + * @param[out] buf the buffer to fill + * @param number the number to convert + */ +static void +mhdt_num_to_hex_w (size_t buf_size, + wchar_t *buf, + uint_least32_t number) +{ + static const wchar_t hex_map[] = L"0123456789abcdef"; + size_t p = buf_size - 1u; + if (p > buf_size) + abort (); + buf[p] = L'\0'; + while (--p < buf_size) + { + buf[p] = hex_map[number & 0xFu]; + number >>= 4u; + } +} + + +# define mhdt_WSTR_(str) L ## str +# define mhdt_WSTR(str) mhdt_WSTR_(str) + +# define MHDT_TMP_FILE_BASENAME_PREFIX_L\ + mhdt_WSTR(MHDT_TMP_FILE_BASENAME_PREFIX) + +/** + * Create a temporary file that is deleted automatically when the FD is + * closed. + * + * The file is opened for both reading and writing in binary mode. + * The FD is not inherited by child processes. + * + * The locations for temporary files are tried in the following order: + * + the directories set by the "TMP" and the "TEMP" environment variables, + * + the current directory, + * + the directory set by "USERPROFILE", + * + and the system default directory for temporary files. + * The locations that are unset, too long or duplicated are skipped. + * + * @return the FD of the created file on success, + * -1 if the file cannot be created in any of the locations + */ +static int +mhdt_create_tmpfile_w32 (void) +{ + static const wchar_t basename_prfx[] = MHDT_TMP_FILE_BASENAME_PREFIX_L; + const wchar_t *tmp_dir; + unsigned int i; + + for (i = 0u; NULL != (tmp_dir = mhdt_get_tmpfile_dir_w32 (i)); ++i) + { + wchar_t tmp_filename[MHDT_FILENAME_MAX + + mhdt_ARR_CNT (basename_prfx) + + MHDT_TMP_FILE_UNIQUE_SUFFIX_LEN]; + int tmpfile_fd; + size_t tmp_dir_len; + static volatile unsigned int cnt = 0u; + int t; + int dir_access_checked; + uint_least32_t suff_num; + + tmp_dir_len = wcslen (tmp_dir); + wmemcpy (tmp_filename, + tmp_dir, + tmp_dir_len); + wmemcpy (tmp_filename + tmp_dir_len, + basename_prfx, + mhdt_ARR_CNT (basename_prfx)); + + tmpfile_fd = -1; + dir_access_checked = 0; + + suff_num = mhdtl_mix32 ((uint_least32_t)GetCurrentProcessId (), + (uint_least32_t)mhd_PTR_TO_INT (tmp_filename)); + for (t = 0; mhdt_MAX_TRIES > t; ++t) + { + wchar_t *suff_ptr; + suff_num = mhdtl_mix32 (suff_num, + (uint_least32_t)cnt++); + suff_ptr = tmp_filename + tmp_dir_len + mhdt_ARR_CNT (basename_prfx) - 1u; + + mhdt_num_to_hex_w (MHDT_TMP_FILE_UNIQUE_SUFFIX_LEN + 1u, + suff_ptr, + suff_num); + + tmpfile_fd = _wopen (tmp_filename, + _O_CREAT | _O_EXCL | _O_RDWR | _O_BINARY + | _O_TEMPORARY | _O_SHORT_LIVED | _O_NOINHERIT, + _S_IREAD | _S_IWRITE); + if (0 <= tmpfile_fd) + break; + if (EEXIST == errno) + continue; /* Filename collision, try another filename */ + if (EACCES == errno) + { + HANDLE h; + if (dir_access_checked) + continue; + + h = CreateFileW (tmp_dir, + FILE_ADD_FILE, + FILE_SHARE_READ | FILE_SHARE_WRITE + | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, /* Required to open a directory */ + NULL); + if (INVALID_HANDLE_VALUE == h) + break; /* Directory not accessible, try next directory */ + (void)CloseHandle (h); + dir_access_checked = !0; + continue; /* Conflict with a file being deleted, try another filename */ + } + break; /* Avoid useless retries */ + } + + if (0 > tmpfile_fd) + continue; /* Try next directory */ + + return tmpfile_fd; + } + + return -1; +} + + +#endif /* W32 */ + +int +MHDT_create_tmpfile (void) +{ +#ifndef mhdt_USE_W32 + return mhdt_create_tmpfile_posix (); +#else + return mhdt_create_tmpfile_w32 (); +#endif +} diff --git a/src/tests/client_server/mhdt_tmpfile.h b/src/tests/client_server/mhdt_tmpfile.h @@ -0,0 +1,70 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2026 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd 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 2.1 of the License, or (at your option) any later version. + + GNU libmicrohttpd 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. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file src/tests/client_server/mhdt_tmpfile.h + * @brief MHD test framework temporary file helpers + * @author Karlson2k (Evgeny Grin) + */ +#ifndef MHDT_TMPFILE_H +#define MHDT_TMPFILE_H 1 + +#include "mhd_sys_options.h" + +/** + * Create a temporary file that is deleted automatically when the FD is + * closed. + * + * The locations for temporary files are tried in the following order: + * + the directory set by the "TMPDIR" environment variable (POSIX only), + * + the directories set by the "TMP" and the "TEMP" environment variables, + * + the current directory, + * + the directory specified by the P_tmpdir macro, if defined (POSIX only), + * + the "/tmp" directory (POSIX only), + * + the directory set by the "USERPROFILE" environment variable (W32 only), + * + and the system default directory for temporary files (W32 only). + * The locations that are unset, too long, duplicated or not usable are + * skipped. + * + * @return the FD of the created file on success, + * -1 if the file cannot be created in any of the locations + */ +int +MHDT_create_tmpfile (void); + +#endif /* ! MHDT_TMPFILE_H */