1 /* 2 * PROJECT: ReactOS Console Utilities Library 3 * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+) 4 * PURPOSE: Base set of functions for loading string resources 5 * and message strings, and handle type identification. 6 * COPYRIGHT: Copyright 2017-2018 ReactOS Team 7 * Copyright 2017-2018 Hermes Belusca-Maito 8 */ 9 10 /** 11 * @file utils.c 12 * @ingroup ConUtils 13 * 14 * @brief General-purpose utility functions (wrappers around 15 * or reimplementations of Win32 APIs). 16 **/ 17 18 /* FIXME: Temporary HACK before we cleanly support UNICODE functions */ 19 #define UNICODE 20 #define _UNICODE 21 22 #include <windef.h> 23 #include <winbase.h> 24 #include <winnls.h> 25 #include <winuser.h> // MAKEINTRESOURCEW, RT_STRING 26 #include <wincon.h> // Console APIs (only if kernel32 support included) 27 #include <strsafe.h> 28 29 /* PSEH for SEH Support */ 30 #include <pseh/pseh2.h> 31 32 // #include "conutils.h" 33 #include "utils.h" 34 35 #if 0 // The following function may be useful in the future... 36 37 // Performs MultiByteToWideChar then WideCharToMultiByte . 38 // See https://github.com/pcman-bbs/pcman-windows/blob/master/Lite/StrUtils.h#l33 39 // and http://www.openfoundry.org/svn/pcman/branches/OpenPCMan_2009/Lite/StrUtils.cpp 40 // for the idea. 41 int 42 MultiByteToMultiByte( 43 // IN WORD wTranslations, 44 IN DWORD dwFlags, 45 IN UINT SrcCodePage, 46 IN LPCSTR lpSrcString, 47 IN int cbSrcChar, 48 IN UINT DestCodePage, 49 OUT LPSTR wDestString OPTIONAL, 50 IN int cbDestChar 51 ); 52 53 #endif 54 55 /** 56 * @name K32LoadStringExW 57 * Loads a string resource from the executable file associated with a 58 * specified module, copies the string into a buffer, and appends a 59 * terminating null character. 60 * This is basically the LoadString() API ripped from user32.dll to 61 * remove any dependency of ConUtils from user32.dll, and to add support 62 * for loading strings from other languages than the current one. 63 * 64 * @param[in] hInstance 65 * Optional handle to an instance of the module whose executable file 66 * contains the string resource. Can be set to NULL to get the handle 67 * to the application itself. 68 * 69 * @param[in] uID 70 * The identifier of the string to be loaded. 71 * 72 * @param[in] LanguageId 73 * The language identifier of the resource. If this parameter is 74 * <tt>MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL)</tt>, the current language 75 * associated with the calling thread is used. To specify a language other 76 * than the current language, use the @c MAKELANGID macro to create this 77 * parameter. 78 * 79 * @param[out] lpBuffer 80 * The buffer that receives the string. Must be of sufficient length 81 * to hold a pointer (8 bytes). 82 * 83 * @param[in] nBufferMax 84 * The size of the buffer, in characters. The string is truncated and 85 * NULL-terminated if it is longer than the number of characters specified. 86 * If this parameter is 0, then @p lpBuffer receives a read-only pointer 87 * to the resource itself. 88 * 89 * @return 90 * If the function succeeds, the return value is the number of characters 91 * copied into the buffer, not including the terminating null character, 92 * or zero if the string resource does not exist. To get extended error 93 * information, call GetLastError(). 94 * 95 * @see LoadString(), K32LoadStringW() 96 **/ 97 INT 98 WINAPI 99 K32LoadStringExW( 100 IN HINSTANCE hInstance OPTIONAL, 101 IN UINT uID, 102 IN LANGID LanguageId, 103 OUT LPWSTR lpBuffer, 104 IN INT nBufferMax) 105 { 106 HRSRC hrsrc; 107 HGLOBAL hmem; 108 WCHAR *p; 109 UINT i; 110 111 if (!lpBuffer) 112 return 0; 113 114 /* Use LOWORD (incremented by 1) as ResourceID */ 115 /* There are always blocks of 16 strings */ 116 hrsrc = FindResourceExW(hInstance, 117 (LPCWSTR)RT_STRING, 118 MAKEINTRESOURCEW((LOWORD(uID) >> 4) + 1), 119 LanguageId); 120 if (!hrsrc) return 0; 121 122 hmem = LoadResource(hInstance, hrsrc); 123 if (!hmem) return 0; 124 125 p = LockResource(hmem); 126 // FreeResource(hmem); 127 128 /* Find the string we're looking for */ 129 uID &= 0x000F; /* Position in the block, same as % 16 */ 130 for (i = 0; i < uID; i++) 131 p += *p + 1; 132 133 /* 134 * If nBufferMax == 0, then return a read-only pointer 135 * to the resource itself in lpBuffer it is assumed that 136 * lpBuffer is actually a (LPWSTR*). 137 */ 138 if (nBufferMax == 0) 139 { 140 *((LPWSTR*)lpBuffer) = p + 1; 141 return *p; 142 } 143 144 i = min(nBufferMax - 1, *p); 145 if (i > 0) 146 { 147 memcpy(lpBuffer, p + 1, i * sizeof(WCHAR)); 148 lpBuffer[i] = L'\0'; 149 } 150 else 151 { 152 if (nBufferMax > 1) 153 { 154 lpBuffer[0] = L'\0'; 155 return 0; 156 } 157 } 158 159 return i; 160 } 161 162 /** 163 * @name K32LoadStringW 164 * Loads a string resource from the executable file associated with a 165 * specified module, copies the string into a buffer, and appends a 166 * terminating null character. 167 * This is a restricted version of K32LoadStringExW(). 168 * 169 * @see LoadString(), K32LoadStringExW() 170 **/ 171 INT 172 WINAPI 173 K32LoadStringW( 174 IN HINSTANCE hInstance OPTIONAL, 175 IN UINT uID, 176 OUT LPWSTR lpBuffer, 177 IN INT nBufferMax) 178 { 179 // NOTE: Instead of using LANG_NEUTRAL, one might use LANG_USER_DEFAULT... 180 return K32LoadStringExW(hInstance, uID, 181 MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), 182 lpBuffer, nBufferMax); 183 } 184 185 /** 186 * @name FormatMessageSafeW 187 * Loads and formats a message string. The function requires a message 188 * definition as input. The message definition can come from a buffer 189 * passed to the function. It can come from a message table resource in 190 * an already-loaded module, or the caller can ask the function to search 191 * the system's message table resource(s) for the message definition. 192 * Please refer to the Win32 FormatMessage() function for more details. 193 * 194 * @param[in] dwFlags 195 * The formatting options, and how to interpret the @p lpSource parameter. 196 * See FormatMessage() for more details. 197 * 198 * @param[in] lpSource 199 * The location of the message definition. The type of this parameter 200 * depends upon the settings in the @p dwFlags parameter. 201 * 202 * @param[in] dwMessageId 203 * The message identifier for the requested message. This parameter 204 * is ignored if @p dwFlags includes @b FORMAT_MESSAGE_FROM_STRING. 205 * 206 * @param[in] dwLanguageId 207 * The language identifier for the requested message. This parameter 208 * is ignored if @p dwFlags includes @b FORMAT_MESSAGE_FROM_STRING. 209 * 210 * @param[out] lpBuffer 211 * A pointer to a buffer that receives the null-terminated string that 212 * specifies the formatted message. If @p dwFlags includes 213 * @b FORMAT_MESSAGE_ALLOCATE_BUFFER, the function allocates a buffer 214 * using the LocalAlloc() function, and places the pointer to the buffer 215 * at the address specified in @p lpBuffer. 216 * This buffer cannot be larger than 64kB. 217 * 218 * @param[in] nSize 219 * If the @b FORMAT_MESSAGE_ALLOCATE_BUFFER flag is not set, this parameter 220 * specifies the size of the output buffer, in @b TCHARs. 221 * If @b FORMAT_MESSAGE_ALLOCATE_BUFFER is set, this parameter specifies 222 * the minimum number of @b TCHARs to allocate for an output buffer. 223 * The output buffer cannot be larger than 64kB. 224 * 225 * @param[in] Arguments 226 * Optional pointer to an array of values describing a variable number of 227 * arguments, depending on the message string. Each argument is used to 228 * replace an <em>insert sequence</em> in the message string. 229 * By default, the @p Arguments parameter is of type @c va_list*, initialized 230 * with va_start(). The state of the @c va_list argument is undefined upon 231 * return from the function. To use the @c va_list again, destroy the variable 232 * argument list pointer using va_end() and reinitialize it with va_start(). 233 * If you do not have a pointer of type @c va_list*, then specify the 234 * @b FORMAT_MESSAGE_ARGUMENT_ARRAY flag and pass a pointer to an array 235 * of @c DWORD_PTR values; those values are input to the message formatted 236 * as the insert values. Each insert must have a corresponding element in 237 * the array. 238 * 239 * @return 240 * If the function succeeds, the return value is the number of characters 241 * copied into the buffer, not including the terminating null character, 242 * or zero if the string resource does not exist. To get extended error 243 * information, call GetLastError(). 244 * 245 * @remark 246 * This function is a "safe" version of FormatMessage(), that does not 247 * crash if a malformed source string is retrieved and then being used 248 * for formatting. It basically wraps calls to FormatMessage() within SEH. 249 * 250 * @see <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/ms679351(v=vs.85).aspx">FormatMessage() (on MSDN)</a> 251 **/ 252 DWORD 253 WINAPI 254 FormatMessageSafeW( 255 IN DWORD dwFlags, 256 IN LPCVOID lpSource OPTIONAL, 257 IN DWORD dwMessageId, 258 IN DWORD dwLanguageId, 259 OUT LPWSTR lpBuffer, 260 IN DWORD nSize, 261 IN va_list *Arguments OPTIONAL) 262 { 263 DWORD dwLength = 0; 264 265 _SEH2_TRY 266 { 267 /* 268 * Retrieve the message string. Wrap in SEH 269 * to protect from invalid string parameters. 270 */ 271 _SEH2_TRY 272 { 273 dwLength = FormatMessageW(dwFlags, 274 lpSource, 275 dwMessageId, 276 dwLanguageId, 277 lpBuffer, 278 nSize, 279 Arguments); 280 } 281 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) 282 { 283 dwLength = 0; 284 285 /* 286 * An exception occurred while calling FormatMessage, this is usually 287 * the sign that a parameter was invalid, either 'lpBuffer' was NULL 288 * but we did not pass the flag FORMAT_MESSAGE_ALLOCATE_BUFFER, or the 289 * array pointer 'Arguments' was NULL or did not contain enough elements, 290 * and we did not pass the flag FORMAT_MESSAGE_IGNORE_INSERTS, and the 291 * message string expected too many inserts. 292 * In this last case only, we can call again FormatMessage but ignore 293 * explicitely the inserts. The string that we will return to the user 294 * will not be pre-formatted. 295 */ 296 if (((dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER) || lpBuffer) && 297 !(dwFlags & FORMAT_MESSAGE_IGNORE_INSERTS)) 298 { 299 /* Remove any possible harmful flags and always ignore inserts */ 300 dwFlags &= ~FORMAT_MESSAGE_ARGUMENT_ARRAY; 301 dwFlags |= FORMAT_MESSAGE_IGNORE_INSERTS; 302 303 /* If this call also throws an exception, we are really dead */ 304 dwLength = FormatMessageW(dwFlags, 305 lpSource, 306 dwMessageId, 307 dwLanguageId, 308 lpBuffer, 309 nSize, 310 NULL /* Arguments */); 311 } 312 } 313 _SEH2_END; 314 } 315 _SEH2_FINALLY 316 { 317 } 318 _SEH2_END; 319 320 return dwLength; 321 } 322 323 /** 324 * @name IsTTYHandle 325 * Checks whether a handle refers to a valid TTY object. 326 * A TTY object may be a console or a "communications" (e.g. serial) port. 327 * 328 * @param[in] hHandle 329 * Handle to the TTY object to check for. 330 * 331 * @return 332 * @b TRUE when the handle refers to a valid TTY object, 333 * @b FALSE if it does not. 334 * 335 * @remark 336 * This test is more general than IsConsoleHandle() as it is not limited 337 * to Win32 console objects only. 338 * 339 * @see IsConsoleHandle() 340 **/ 341 BOOL 342 IsTTYHandle(IN HANDLE hHandle) 343 { 344 /* 345 * More general test than IsConsoleHandle(). Consoles, as well as serial 346 * (communications) ports, etc... verify this test, but only consoles 347 * verify the IsConsoleHandle() test: indeed the latter checks whether 348 * the handle is really handled by the console subsystem. 349 */ 350 return ((GetFileType(hHandle) & ~FILE_TYPE_REMOTE) == FILE_TYPE_CHAR); 351 } 352 353 /** 354 * @name IsConsoleHandle 355 * Checks whether a handle refers to a valid Win32 console object. 356 * 357 * @param[in] hHandle 358 * Handle to the Win32 console object to check for: 359 * console input buffer, console output buffer. 360 * 361 * @return 362 * @b TRUE when the handle refers to a valid Win32 console object, 363 * @b FALSE if it does not. 364 * 365 * @see IsTTYHandle() 366 **/ 367 BOOL 368 IsConsoleHandle(IN HANDLE hHandle) 369 { 370 DWORD dwMode; 371 372 /* Check whether the handle may be that of a console... */ 373 if ((GetFileType(hHandle) & ~FILE_TYPE_REMOTE) != FILE_TYPE_CHAR) 374 return FALSE; 375 376 /* 377 * It may be. Perform another test. The idea comes from the 378 * MSDN description of the WriteConsole API: 379 * 380 * "WriteConsole fails if it is used with a standard handle 381 * that is redirected to a file. If an application processes 382 * multilingual output that can be redirected, determine whether 383 * the output handle is a console handle (one method is to call 384 * the GetConsoleMode function and check whether it succeeds). 385 * If the handle is a console handle, call WriteConsole. If the 386 * handle is not a console handle, the output is redirected and 387 * you should call WriteFile to perform the I/O." 388 */ 389 return GetConsoleMode(hHandle, &dwMode); 390 } 391 392 /* EOF */ 393