/***************************************************************************1* _ _ ____ _2* Project ___| | | | _ \| |3* / __| | | | |_) | |4* | (__| |_| | _ <| |___5* \___|\___/|_| \_\_____|6*7* Copyright (C) Daniel Stenberg, <[email protected]>, et al.8*9* This software is licensed as described in the file COPYING, which10* you should have received as part of this distribution. The terms11* are also available at https://curl.se/docs/copyright.html.12*13* You may opt to use, copy, modify, merge, publish, distribute and/or sell14* copies of the Software, and permit persons to whom the Software is15* furnished to do so, under the terms of the COPYING file.16*17* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY18* KIND, either express or implied.19*20* SPDX-License-Identifier: curl21*22***************************************************************************/23#include "tool_setup.h"2425#if defined(_WIN32) || defined(MSDOS)2627#if defined(HAVE_LIBGEN_H) && defined(HAVE_BASENAME)28# include <libgen.h>29#endif3031#ifdef _WIN3232# include <stdlib.h>33# include <tlhelp32.h>34# include "tool_cfgable.h"35# include "tool_libinfo.h"36#endif3738#include "tool_bname.h"39#include "tool_doswin.h"4041#include <curlx.h>42#include <memdebug.h> /* keep this as LAST include */4344#ifdef _WIN3245# undef PATH_MAX46# define PATH_MAX MAX_PATH47#elif !defined(__DJGPP__) || (__DJGPP__ < 2) /* DJGPP 2.0 has _use_lfn() */48# define _use_lfn(f) (0) /* long filenames never available */49#elif defined(__DJGPP__)50# include <fcntl.h> /* _use_lfn(f) prototype */51#endif5253#ifdef MSDOS5455#ifndef S_ISCHR56# ifdef S_IFCHR57# define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR)58# else59# define S_ISCHR(m) (0) /* cannot tell if file is a device */60# endif61#endif6263/* only used by msdosify() */64static SANITIZEcode truncate_dryrun(const char *path,65const size_t truncate_pos);66static SANITIZEcode msdosify(char **const sanitized, const char *file_name,67int flags);68#endif69static SANITIZEcode rename_if_reserved_dos(char **const sanitized,70const char *file_name,71int flags);727374/*75Sanitize a file or path name.7677All banned characters are replaced by underscores, for example:78f?*foo => f__foo79f:foo::$DATA => f_foo__$DATA80f:\foo:bar => f__foo_bar81f:\foo:bar => f:\foo:bar (flag SANITIZE_ALLOW_PATH)8283This function was implemented according to the guidelines in 'Naming Files,84Paths, and Namespaces' section 'Naming Conventions'.85https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247.aspx8687Flags88-----89SANITIZE_ALLOW_PATH: Allow path separators and colons.90Without this flag path separators and colons are sanitized.9192SANITIZE_ALLOW_RESERVED: Allow reserved device names.93Without this flag a reserved device name is renamed (COM1 => _COM1) unless it94is in a UNC prefixed path.9596Success: (SANITIZE_ERR_OK) *sanitized points to a sanitized copy of file_name.97Failure: (!= SANITIZE_ERR_OK) *sanitized is NULL.98*/99SANITIZEcode sanitize_file_name(char **const sanitized, const char *file_name,100int flags)101{102char *p, *target;103size_t len;104SANITIZEcode sc;105size_t max_sanitized_len;106107if(!sanitized)108return SANITIZE_ERR_BAD_ARGUMENT;109110*sanitized = NULL;111112if(!file_name)113return SANITIZE_ERR_BAD_ARGUMENT;114115if(flags & SANITIZE_ALLOW_PATH) {116#ifndef MSDOS117if(file_name[0] == '\\' && file_name[1] == '\\')118/* UNC prefixed path \\ (eg \\?\C:\foo) */119max_sanitized_len = 32767-1;120else121#endif122max_sanitized_len = PATH_MAX-1;123}124else125/* The maximum length of a filename. FILENAME_MAX is often the same as126PATH_MAX, in other words it is 260 and does not discount the path127information therefore we should not use it. */128max_sanitized_len = (PATH_MAX-1 > 255) ? 255 : PATH_MAX-1;129130len = strlen(file_name);131if(len > max_sanitized_len)132return SANITIZE_ERR_INVALID_PATH;133134target = strdup(file_name);135if(!target)136return SANITIZE_ERR_OUT_OF_MEMORY;137138#ifndef MSDOS139if((flags & SANITIZE_ALLOW_PATH) && !strncmp(target, "\\\\?\\", 4))140/* Skip the literal path prefix \\?\ */141p = target + 4;142else143#endif144p = target;145146/* replace control characters and other banned characters */147for(; *p; ++p) {148const char *banned;149150if((1 <= *p && *p <= 31) ||151(!(flags & SANITIZE_ALLOW_PATH) && *p == ':') ||152(!(flags & SANITIZE_ALLOW_PATH) && (*p == '/' || *p == '\\'))) {153*p = '_';154continue;155}156157for(banned = "|<>\"?*"; *banned; ++banned) {158if(*p == *banned) {159*p = '_';160break;161}162}163}164165/* remove trailing spaces and periods if not allowing paths */166if(!(flags & SANITIZE_ALLOW_PATH) && len) {167char *clip = NULL;168169p = &target[len];170do {171--p;172if(*p != ' ' && *p != '.')173break;174clip = p;175} while(p != target);176177if(clip) {178*clip = '\0';179}180}181182#ifdef MSDOS183sc = msdosify(&p, target, flags);184free(target);185if(sc)186return sc;187target = p;188len = strlen(target);189190if(len > max_sanitized_len) {191free(target);192return SANITIZE_ERR_INVALID_PATH;193}194#endif195196if(!(flags & SANITIZE_ALLOW_RESERVED)) {197sc = rename_if_reserved_dos(&p, target, flags);198free(target);199if(sc)200return sc;201target = p;202len = strlen(target);203204if(len > max_sanitized_len) {205free(target);206return SANITIZE_ERR_INVALID_PATH;207}208}209210*sanitized = target;211return SANITIZE_ERR_OK;212}213214#ifdef MSDOS215/*216Test if truncating a path to a file will leave at least a single character in217the filename. Filenames suffixed by an alternate data stream cannot be218truncated. This performs a dry run, nothing is modified.219220Good truncate_pos 9: C:\foo\bar => C:\foo\ba221Good truncate_pos 6: C:\foo => C:\foo222Good truncate_pos 5: C:\foo => C:\fo223Bad* truncate_pos 5: C:foo => C:foo224Bad truncate_pos 5: C:\foo:ads => C:\fo225Bad truncate_pos 9: C:\foo:ads => C:\foo:ad226Bad truncate_pos 5: C:\foo\bar => C:\fo227Bad truncate_pos 5: C:\foo\ => C:\fo228Bad truncate_pos 7: C:\foo\ => C:\foo\229Error truncate_pos 7: C:\foo => (pos out of range)230Bad truncate_pos 1: C:\foo\ => C231232* C:foo is ambiguous, C could end up being a drive or file therefore something233like C:superlongfilename cannot be truncated.234235Returns236SANITIZE_ERR_OK: Good -- 'path' can be truncated237SANITIZE_ERR_INVALID_PATH: Bad -- 'path' cannot be truncated238!= SANITIZE_ERR_OK && != SANITIZE_ERR_INVALID_PATH: Error239*/240static SANITIZEcode truncate_dryrun(const char *path,241const size_t truncate_pos)242{243size_t len;244245if(!path)246return SANITIZE_ERR_BAD_ARGUMENT;247248len = strlen(path);249250if(truncate_pos > len)251return SANITIZE_ERR_BAD_ARGUMENT;252253if(!len || !truncate_pos)254return SANITIZE_ERR_INVALID_PATH;255256if(strpbrk(&path[truncate_pos - 1], "\\/:"))257return SANITIZE_ERR_INVALID_PATH;258259/* C:\foo can be truncated but C:\foo:ads cannot */260if(truncate_pos > 1) {261const char *p = &path[truncate_pos - 1];262do {263--p;264if(*p == ':')265return SANITIZE_ERR_INVALID_PATH;266} while(p != path && *p != '\\' && *p != '/');267}268269return SANITIZE_ERR_OK;270}271272/* The functions msdosify, rename_if_dos_device_name and __crt0_glob_function273* were taken with modification from the DJGPP port of tar 1.12. They use274* algorithms originally from DJTAR.275*/276277/*278Extra sanitization MS-DOS for file_name.279280This is a supporting function for sanitize_file_name.281282Warning: This is an MS-DOS legacy function and was purposely written in a way283that some path information may pass through. For example drive letter names284(C:, D:, etc) are allowed to pass through. For sanitizing a filename use285sanitize_file_name.286287Success: (SANITIZE_ERR_OK) *sanitized points to a sanitized copy of file_name.288Failure: (!= SANITIZE_ERR_OK) *sanitized is NULL.289*/290static SANITIZEcode msdosify(char **const sanitized, const char *file_name,291int flags)292{293char dos_name[PATH_MAX];294static const char illegal_chars_dos[] = ".+, ;=[]" /* illegal in DOS */295"|<>/\\\":?*"; /* illegal in DOS & W95 */296static const char *illegal_chars_w95 = &illegal_chars_dos[8];297int idx, dot_idx;298const char *s = file_name;299char *d = dos_name;300const char *const dlimit = dos_name + sizeof(dos_name) - 1;301const char *illegal_aliens = illegal_chars_dos;302size_t len = sizeof(illegal_chars_dos) - 1;303304if(!sanitized)305return SANITIZE_ERR_BAD_ARGUMENT;306307*sanitized = NULL;308309if(!file_name)310return SANITIZE_ERR_BAD_ARGUMENT;311312if(strlen(file_name) > PATH_MAX-1)313return SANITIZE_ERR_INVALID_PATH;314315/* Support for Windows 9X VFAT systems, when available. */316if(_use_lfn(file_name)) {317illegal_aliens = illegal_chars_w95;318len -= (illegal_chars_w95 - illegal_chars_dos);319}320321/* Get past the drive letter, if any. */322if(s[0] >= 'A' && s[0] <= 'z' && s[1] == ':') {323*d++ = *s++;324*d = ((flags & SANITIZE_ALLOW_PATH)) ? ':' : '_';325++d; ++s;326}327328for(idx = 0, dot_idx = -1; *s && d < dlimit; s++, d++) {329if(memchr(illegal_aliens, *s, len)) {330331if((flags & SANITIZE_ALLOW_PATH) && *s == ':')332*d = ':';333else if((flags & SANITIZE_ALLOW_PATH) && (*s == '/' || *s == '\\'))334*d = *s;335/* Dots are special: DOS does not allow them as the leading character,336and a filename cannot have more than a single dot. We leave the337first non-leading dot alone, unless it comes too close to the338beginning of the name: we want sh.lex.c to become sh_lex.c, not339sh.lex-c. */340else if(*s == '.') {341if((flags & SANITIZE_ALLOW_PATH) && idx == 0 &&342(s[1] == '/' || s[1] == '\\' ||343(s[1] == '.' && (s[2] == '/' || s[2] == '\\')))) {344/* Copy "./" and "../" verbatim. */345*d++ = *s++;346if(d == dlimit)347break;348if(*s == '.') {349*d++ = *s++;350if(d == dlimit)351break;352}353*d = *s;354}355else if(idx == 0)356*d = '_';357else if(dot_idx >= 0) {358if(dot_idx < 5) { /* 5 is a heuristic ad-hoc'ery */359d[dot_idx - idx] = '_'; /* replace previous dot */360*d = '.';361}362else363*d = '-';364}365else366*d = '.';367368if(*s == '.')369dot_idx = idx;370}371else if(*s == '+' && s[1] == '+') {372if(idx - 2 == dot_idx) { /* .c++, .h++ etc. */373*d++ = 'x';374if(d == dlimit)375break;376*d = 'x';377}378else {379/* libg++ etc. */380if(dlimit - d < 4) {381*d++ = 'x';382if(d == dlimit)383break;384*d = 'x';385}386else {387memcpy(d, "plus", 4);388d += 3;389}390}391s++;392idx++;393}394else395*d = '_';396}397else398*d = *s;399if(*s == '/' || *s == '\\') {400idx = 0;401dot_idx = -1;402}403else404idx++;405}406*d = '\0';407408if(*s) {409/* dos_name is truncated, check that truncation requirements are met,410specifically truncating a filename suffixed by an alternate data stream411or truncating the entire filename is not allowed. */412if(strpbrk(s, "\\/:") || truncate_dryrun(dos_name, d - dos_name))413return SANITIZE_ERR_INVALID_PATH;414}415416*sanitized = strdup(dos_name);417return *sanitized ? SANITIZE_ERR_OK : SANITIZE_ERR_OUT_OF_MEMORY;418}419#endif /* MSDOS */420421/*422Rename file_name if it is a reserved dos device name.423424This is a supporting function for sanitize_file_name.425426Warning: This is an MS-DOS legacy function and was purposely written in a way427that some path information may pass through. For example drive letter names428(C:, D:, etc) are allowed to pass through. For sanitizing a filename use429sanitize_file_name.430431Success: (SANITIZE_ERR_OK) *sanitized points to a sanitized copy of file_name.432Failure: (!= SANITIZE_ERR_OK) *sanitized is NULL.433*/434static SANITIZEcode rename_if_reserved_dos(char **const sanitized,435const char *file_name,436int flags)437{438/* We could have a file whose name is a device on MS-DOS. Trying to439* retrieve such a file would fail at best and wedge us at worst. We need440* to rename such files. */441char *p, *base;442char fname[PATH_MAX];443#ifdef MSDOS444struct_stat st_buf;445#endif446size_t len;447448if(!sanitized || !file_name)449return SANITIZE_ERR_BAD_ARGUMENT;450451*sanitized = NULL;452len = strlen(file_name);453454/* Ignore UNC prefixed paths, they are allowed to contain a reserved name. */455#ifndef MSDOS456if((flags & SANITIZE_ALLOW_PATH) &&457file_name[0] == '\\' && file_name[1] == '\\') {458*sanitized = strdup(file_name);459if(!*sanitized)460return SANITIZE_ERR_OUT_OF_MEMORY;461return SANITIZE_ERR_OK;462}463#endif464465if(len > PATH_MAX-1)466return SANITIZE_ERR_INVALID_PATH;467468memcpy(fname, file_name, len);469fname[len] = '\0';470base = basename(fname);471472/* Rename reserved device names that are known to be accessible without \\.\473Examples: CON => _CON, CON.EXT => CON_EXT, CON:ADS => CON_ADS474https://support.microsoft.com/en-us/kb/74496475https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247.aspx476*/477for(p = fname; p; p = (p == fname && fname != base ? base : NULL)) {478size_t p_len;479int x = (curl_strnequal(p, "CON", 3) ||480curl_strnequal(p, "PRN", 3) ||481curl_strnequal(p, "AUX", 3) ||482curl_strnequal(p, "NUL", 3)) ? 3 :483(curl_strnequal(p, "CLOCK$", 6)) ? 6 :484(curl_strnequal(p, "COM", 3) || curl_strnequal(p, "LPT", 3)) ?485(('1' <= p[3] && p[3] <= '9') ? 4 : 3) : 0;486487if(!x)488continue;489490/* the devices may be accessible with an extension or ADS, for491example CON.AIR and 'CON . AIR' and CON:AIR access console */492493for(; p[x] == ' '; ++x)494;495496if(p[x] == '.') {497p[x] = '_';498continue;499}500else if(p[x] == ':') {501if(!(flags & SANITIZE_ALLOW_PATH)) {502p[x] = '_';503continue;504}505++x;506}507else if(p[x]) /* no match */508continue;509510/* p points to 'CON' or 'CON ' or 'CON:', etc */511p_len = strlen(p);512513/* Prepend a '_' */514if(strlen(fname) == PATH_MAX-1)515return SANITIZE_ERR_INVALID_PATH;516memmove(p + 1, p, p_len + 1);517p[0] = '_';518++p_len;519520/* if fname was just modified then the basename pointer must be updated */521if(p == fname)522base = basename(fname);523}524525/* This is the legacy portion from rename_if_dos_device_name that checks for526reserved device names. It only works on MS-DOS. On Windows XP the stat527check errors with EINVAL if the device name is reserved. On Windows528Vista/7/8 it sets mode S_IFREG (regular file or device). According to529MSDN stat doc the latter behavior is correct, but that does not help us530identify whether it is a reserved device name and not a regular531filename. */532#ifdef MSDOS533if(base && ((stat(base, &st_buf)) == 0) && (S_ISCHR(st_buf.st_mode))) {534/* Prepend a '_' */535size_t blen = strlen(base);536if(blen) {537if(strlen(fname) >= PATH_MAX-1)538return SANITIZE_ERR_INVALID_PATH;539memmove(base + 1, base, blen + 1);540base[0] = '_';541}542}543#endif544545*sanitized = strdup(fname);546return *sanitized ? SANITIZE_ERR_OK : SANITIZE_ERR_OUT_OF_MEMORY;547}548549#ifdef __DJGPP__550/*551* Disable program default argument globbing. We do it on our own.552*/553char **__crt0_glob_function(char *arg)554{555(void)arg;556return (char **)0;557}558#endif559560#ifdef _WIN32561562#if !defined(CURL_WINDOWS_UWP) && !defined(UNDER_CE) && \563!defined(CURL_DISABLE_CA_SEARCH) && !defined(CURL_CA_SEARCH_SAFE)564/* Search and set the CA cert file for Windows.565*566* Do not call this function if Schannel is the selected SSL backend. We allow567* setting CA location for Schannel only when explicitly specified by the user568* via CURLOPT_CAINFO / --cacert.569*570* Function to find CACert bundle on a Win32 platform using SearchPath.571* (SearchPath is already declared via inclusions done in setup header file)572* (Use the ASCII version instead of the Unicode one!)573* The order of the directories it searches is:574* 1. application's directory575* 2. current working directory576* 3. Windows System directory (e.g. C:\Windows\System32)577* 4. Windows Directory (e.g. C:\Windows)578* 5. all directories along %PATH%579*580* For WinXP and later search order actually depends on registry value:581* HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\SafeProcessSearchMode582*/583CURLcode FindWin32CACert(struct OperationConfig *config,584const TCHAR *bundle_file)585{586CURLcode result = CURLE_OK;587DWORD res_len;588TCHAR buf[PATH_MAX];589TCHAR *ptr = NULL;590591buf[0] = TEXT('\0');592593res_len = SearchPath(NULL, bundle_file, NULL, PATH_MAX, buf, &ptr);594if(res_len > 0) {595char *mstr = curlx_convert_tchar_to_UTF8(buf);596tool_safefree(config->cacert);597if(mstr)598config->cacert = strdup(mstr);599curlx_unicodefree(mstr);600if(!config->cacert)601result = CURLE_OUT_OF_MEMORY;602}603604return result;605}606#endif607608/* Get a list of all loaded modules with full paths.609* Returns slist on success or NULL on error.610*/611struct curl_slist *GetLoadedModulePaths(void)612{613struct curl_slist *slist = NULL;614#if !defined(CURL_WINDOWS_UWP) && !defined(UNDER_CE)615HANDLE hnd = INVALID_HANDLE_VALUE;616MODULEENTRY32 mod = {0};617618mod.dwSize = sizeof(MODULEENTRY32);619620do {621hnd = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 0);622} while(hnd == INVALID_HANDLE_VALUE && GetLastError() == ERROR_BAD_LENGTH);623624if(hnd == INVALID_HANDLE_VALUE)625goto error;626627if(!Module32First(hnd, &mod))628goto error;629630do {631char *path; /* points to stack allocated buffer */632struct curl_slist *temp;633634#ifdef UNICODE635/* sizeof(mod.szExePath) is the max total bytes of wchars. the max total636bytes of multibyte chars will not be more than twice that. */637char buffer[sizeof(mod.szExePath) * 2];638if(!WideCharToMultiByte(CP_ACP, 0, mod.szExePath, -1,639buffer, sizeof(buffer), NULL, NULL))640goto error;641path = buffer;642#else643path = mod.szExePath;644#endif645temp = curl_slist_append(slist, path);646if(!temp)647goto error;648slist = temp;649} while(Module32Next(hnd, &mod));650651goto cleanup;652653error:654curl_slist_free_all(slist);655slist = NULL;656cleanup:657if(hnd != INVALID_HANDLE_VALUE)658CloseHandle(hnd);659#endif660return slist;661}662663bool tool_term_has_bold;664665#if !defined(CURL_WINDOWS_UWP) && !defined(UNDER_CE)666/* The terminal settings to restore on exit */667static struct TerminalSettings {668HANDLE hStdOut;669DWORD dwOutputMode;670LONG valid;671} TerminalSettings;672673#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING674#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004675#endif676677static void restore_terminal(void)678{679if(InterlockedExchange(&TerminalSettings.valid, (LONG)FALSE))680SetConsoleMode(TerminalSettings.hStdOut, TerminalSettings.dwOutputMode);681}682683/* This is the console signal handler.684* The system calls it in a separate thread.685*/686static BOOL WINAPI signal_handler(DWORD type)687{688if(type == CTRL_C_EVENT || type == CTRL_BREAK_EVENT)689restore_terminal();690return FALSE;691}692693static void init_terminal(void)694{695TerminalSettings.hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);696697/*698* Enable VT (Virtual Terminal) output.699* Note: VT mode flag can be set on any version of Windows, but VT700* processing only performed on Win10 >= version 1709 (OS build 16299)701* Creator's Update. Also, ANSI bold on/off supported since then.702*/703if(TerminalSettings.hStdOut == INVALID_HANDLE_VALUE ||704!GetConsoleMode(TerminalSettings.hStdOut,705&TerminalSettings.dwOutputMode) ||706!curlx_verify_windows_version(10, 0, 16299, PLATFORM_WINNT,707VERSION_GREATER_THAN_EQUAL))708return;709710if((TerminalSettings.dwOutputMode & ENABLE_VIRTUAL_TERMINAL_PROCESSING))711tool_term_has_bold = true;712else {713/* The signal handler is set before attempting to change the console mode714because otherwise a signal would not be caught after the change but715before the handler was installed. */716(void)InterlockedExchange(&TerminalSettings.valid, (LONG)TRUE);717if(SetConsoleCtrlHandler(signal_handler, TRUE)) {718if(SetConsoleMode(TerminalSettings.hStdOut,719(TerminalSettings.dwOutputMode |720ENABLE_VIRTUAL_TERMINAL_PROCESSING))) {721tool_term_has_bold = true;722atexit(restore_terminal);723}724else {725SetConsoleCtrlHandler(signal_handler, FALSE);726(void)InterlockedExchange(&TerminalSettings.valid, (LONG)FALSE);727}728}729}730}731#endif732733CURLcode win32_init(void)734{735curlx_now_init();736#if !defined(CURL_WINDOWS_UWP) && !defined(UNDER_CE)737init_terminal();738#endif739740return CURLE_OK;741}742743#endif /* _WIN32 */744745#endif /* _WIN32 || MSDOS */746747748