xref: /reactos/sdk/lib/conutils/utils.c (revision c7bba39a)
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