msxml3: Support VT_I2 variants tracing.
[wine] / dlls / kernel32 / format_msg.c
1 /*
2  * FormatMessage implementation
3  *
4  * Copyright 1996 Marcus Meissner
5  * Copyright 2009 Alexandre Julliard
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20  */
21
22 #include "config.h"
23
24 #include <stdarg.h>
25 #include <stdio.h>
26 #include <string.h>
27
28 #include "ntstatus.h"
29 #define WIN32_NO_STATUS
30 #include "windef.h"
31 #include "winbase.h"
32 #include "winerror.h"
33 #include "winternl.h"
34 #include "winuser.h"
35 #include "winnls.h"
36 #include "wine/unicode.h"
37 #include "kernel_private.h"
38 #include "wine/debug.h"
39
40 WINE_DEFAULT_DEBUG_CHANNEL(resource);
41
42 struct format_args
43 {
44     ULONG_PTR    *args;
45     __ms_va_list *list;
46     int           last;
47 };
48
49 /* Messages used by FormatMessage
50  *
51  * They can be specified either directly or using a message ID and
52  * loading them from the resource.
53  *
54  * The resourcedata has following format:
55  * start:
56  * 0: DWORD nrofentries
57  * nrofentries * subentry:
58  *      0: DWORD firstentry
59  *      4: DWORD lastentry
60  *      8: DWORD offset from start to the stringentries
61  *
62  * (lastentry-firstentry) * stringentry:
63  * 0: WORD len (0 marks end)    [ includes the 4 byte header length ]
64  * 2: WORD flags
65  * 4: CHAR[len-4]
66  *      (stringentry i of a subentry refers to the ID 'firstentry+i')
67  *
68  * Yes, ANSI strings in win32 resources. Go figure.
69  */
70
71 static const WCHAR FMTWSTR[] = { '%','s',0 };
72
73 /**********************************************************************
74  *      load_message    (internal)
75  */
76 static LPWSTR load_message( HMODULE module, UINT id, WORD lang )
77 {
78     const MESSAGE_RESOURCE_ENTRY *mre;
79     WCHAR *buffer;
80     NTSTATUS status;
81
82     TRACE("module = %p, id = %08x\n", module, id );
83
84     if (!module) module = GetModuleHandleW( NULL );
85     if ((status = RtlFindMessage( module, RT_MESSAGETABLE, lang, id, &mre )) != STATUS_SUCCESS)
86     {
87         SetLastError( RtlNtStatusToDosError(status) );
88         return NULL;
89     }
90
91     if (mre->Flags & MESSAGE_RESOURCE_UNICODE)
92     {
93         int len = (strlenW( (const WCHAR *)mre->Text ) + 1) * sizeof(WCHAR);
94         if (!(buffer = HeapAlloc( GetProcessHeap(), 0, len ))) return NULL;
95         memcpy( buffer, mre->Text, len );
96     }
97     else
98     {
99         int len = MultiByteToWideChar( CP_ACP, 0, (const char *)mre->Text, -1, NULL, 0 );
100         if (!(buffer = HeapAlloc( GetProcessHeap(), 0, len * sizeof(WCHAR) ))) return NULL;
101         MultiByteToWideChar( CP_ACP, 0, (const char*)mre->Text, -1, buffer, len );
102     }
103     TRACE("returning %s\n", wine_dbgstr_w(buffer));
104     return buffer;
105 }
106
107 /**********************************************************************
108  *      get_arg    (internal)
109  */
110 static ULONG_PTR get_arg( int nr, DWORD flags, struct format_args *args )
111 {
112     if (nr == -1) nr = args->last + 1;
113     if (args->list)
114     {
115         if (!args->args) args->args = HeapAlloc( GetProcessHeap(), 0, 99 * sizeof(ULONG_PTR) );
116         while (nr > args->last)
117             args->args[args->last++] = va_arg( *args->list, ULONG_PTR );
118     }
119     if (nr > args->last) args->last = nr;
120     return args->args[nr - 1];
121 }
122
123 /**********************************************************************
124  *      format_insert    (internal)
125  */
126 static LPCWSTR format_insert( BOOL unicode_caller, int insert, LPCWSTR format,
127                               DWORD flags, struct format_args *args,
128                               LPWSTR *result )
129 {
130     static const WCHAR fmt_lu[] = {'%','l','u',0};
131     WCHAR *wstring = NULL, *p, fmt[256];
132     ULONG_PTR arg;
133     int size;
134
135     if (*format != '!')  /* simple string */
136     {
137         arg = get_arg( insert, flags, args );
138         if (unicode_caller || !arg)
139         {
140             static const WCHAR nullW[] = {'(','n','u','l','l',')',0};
141             const WCHAR *str = (const WCHAR *)arg;
142
143             if (!str) str = nullW;
144             *result = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) );
145             strcpyW( *result, str );
146         }
147         else
148         {
149             const char *str = (const char *)arg;
150             DWORD length = MultiByteToWideChar( CP_ACP, 0, str, -1, NULL, 0 );
151             *result = HeapAlloc( GetProcessHeap(), 0, length * sizeof(WCHAR) );
152             MultiByteToWideChar( CP_ACP, 0, str, -1, *result, length );
153         }
154         return format;
155     }
156
157     format++;
158     p = fmt;
159     *p++ = '%';
160
161     while (*format == '0' ||
162            *format == '+' ||
163            *format == '-' ||
164            *format == ' ' ||
165            *format == '*' ||
166            *format == '#')
167     {
168         if (*format == '*')
169         {
170             p += sprintfW( p, fmt_lu, get_arg( insert, flags, args ));
171             insert = -1;
172             format++;
173         }
174         else *p++ = *format++;
175     }
176     while (isdigitW(*format)) *p++ = *format++;
177
178     if (*format == '.')
179     {
180         *p++ = *format++;
181         if (*format == '*')
182         {
183             p += sprintfW( p, fmt_lu, get_arg( insert, flags, args ));
184             insert = -1;
185             format++;
186         }
187         else
188             while (isdigitW(*format)) *p++ = *format++;
189     }
190
191     /* replicate MS bug: drop an argument when using va_list with width/precision */
192     if (insert == -1 && args->list) args->last--;
193     arg = get_arg( insert, flags, args );
194
195     /* check for ascii string format */
196     if ((format[0] == 'h' && format[1] == 's') ||
197         (format[0] == 'h' && format[1] == 'S') ||
198         (unicode_caller && format[0] == 'S') ||
199         (!unicode_caller && format[0] == 's'))
200     {
201         DWORD len = MultiByteToWideChar( CP_ACP, 0, (char *)arg, -1, NULL, 0 );
202         wstring = HeapAlloc( GetProcessHeap(), 0, len * sizeof(WCHAR) );
203         MultiByteToWideChar( CP_ACP, 0, (char *)arg, -1, wstring, len );
204         arg = (ULONG_PTR)wstring;
205         *p++ = 's';
206     }
207     /* check for ascii character format */
208     else if ((format[0] == 'h' && format[1] == 'c') ||
209              (format[0] == 'h' && format[1] == 'C') ||
210              (unicode_caller && format[0] == 'C') ||
211              (!unicode_caller && format[0] == 'c'))
212     {
213         char ch = arg;
214         wstring = HeapAlloc( GetProcessHeap(), 0, 2 * sizeof(WCHAR) );
215         MultiByteToWideChar( CP_ACP, 0, &ch, 1, wstring, 1 );
216         wstring[1] = 0;
217         arg = (ULONG_PTR)wstring;
218         *p++ = 's';
219     }
220     /* check for wide string format */
221     else if ((format[0] == 'l' && format[1] == 's') ||
222              (format[0] == 'l' && format[1] == 'S') ||
223              (format[0] == 'w' && format[1] == 's') ||
224              (!unicode_caller && format[0] == 'S'))
225     {
226         *p++ = 's';
227     }
228     /* check for wide character format */
229     else if ((format[0] == 'l' && format[1] == 'c') ||
230              (format[0] == 'l' && format[1] == 'C') ||
231              (format[0] == 'w' && format[1] == 'c') ||
232              (!unicode_caller && format[0] == 'C'))
233     {
234         *p++ = 'c';
235     }
236     /* FIXME: handle I64 etc. */
237     else while (*format && *format != '!') *p++ = *format++;
238
239     *p = 0;
240     size = 256;
241     for (;;)
242     {
243         WCHAR *ret = HeapAlloc( GetProcessHeap(), 0, size * sizeof(WCHAR) );
244         int needed = snprintfW( ret, size, fmt, arg );
245         if (needed == -1 || needed >= size)
246         {
247             HeapFree( GetProcessHeap(), 0, ret );
248             size = max( needed + 1, size * 2 );
249         }
250         else
251         {
252             *result = ret;
253             break;
254         }
255     }
256
257     while (*format && *format != '!') format++;
258     if (*format == '!') format++;
259
260     HeapFree( GetProcessHeap(), 0, wstring );
261     return format;
262 }
263
264 /**********************************************************************
265  *      format_message    (internal)
266  */
267 static LPWSTR format_message( BOOL unicode_caller, DWORD dwFlags, LPCWSTR fmtstr,
268                               struct format_args *format_args )
269 {
270     LPWSTR target,t;
271     DWORD talloced;
272     LPCWSTR f;
273     DWORD width = dwFlags & FORMAT_MESSAGE_MAX_WIDTH_MASK;
274     BOOL eos = FALSE;
275     WCHAR ch;
276
277     target = t = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, 100 * sizeof(WCHAR) );
278     talloced = 100;
279
280 #define ADD_TO_T(c)  do {\
281     *t++=c;\
282     if ((DWORD)(t-target) == talloced) {\
283         target = HeapReAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,target,talloced*2*sizeof(WCHAR));\
284         t = target+talloced;\
285         talloced*=2;\
286     } \
287 } while (0)
288
289     f = fmtstr;
290     while (*f && !eos) {
291         if (*f=='%') {
292             int insertnr;
293             WCHAR *str,*x;
294
295             f++;
296             switch (*f) {
297             case '1':case '2':case '3':case '4':case '5':
298             case '6':case '7':case '8':case '9':
299                 if (dwFlags & FORMAT_MESSAGE_IGNORE_INSERTS)
300                     goto ignore_inserts;
301                 else if (((dwFlags & FORMAT_MESSAGE_ARGUMENT_ARRAY) && !format_args->args) ||
302                         (!(dwFlags & FORMAT_MESSAGE_ARGUMENT_ARRAY) && !format_args->list))
303                 {
304                     SetLastError(ERROR_INVALID_PARAMETER);
305                     HeapFree(GetProcessHeap(), 0, target);
306                     return NULL;
307                 }
308                 insertnr = *f-'0';
309                 switch (f[1]) {
310                 case '0':case '1':case '2':case '3':
311                 case '4':case '5':case '6':case '7':
312                 case '8':case '9':
313                     f++;
314                     insertnr = insertnr*10 + *f-'0';
315                     f++;
316                     break;
317                 default:
318                     f++;
319                     break;
320                 }
321                 f = format_insert( unicode_caller, insertnr, f, dwFlags, format_args, &str );
322                 for (x = str; *x; x++) ADD_TO_T(*x);
323                 HeapFree( GetProcessHeap(), 0, str );
324                 break;
325             case 'n':
326                 ADD_TO_T('\r');
327                 ADD_TO_T('\n');
328                 f++;
329                 break;
330             case 'r':
331                 ADD_TO_T('\r');
332                 f++;
333                 break;
334             case 't':
335                 ADD_TO_T('\t');
336                 f++;
337                 break;
338             case '0':
339                 eos = TRUE;
340                 f++;
341                 break;
342             case '\0':
343                 SetLastError(ERROR_INVALID_PARAMETER);
344                 HeapFree(GetProcessHeap(), 0, target);
345                 return NULL;
346             ignore_inserts:
347             default:
348                 if (dwFlags & FORMAT_MESSAGE_IGNORE_INSERTS)
349                     ADD_TO_T('%');
350                 ADD_TO_T(*f++);
351                 break;
352             }
353         } else {
354             ch = *f;
355             f++;
356             if (ch == '\r') {
357                 if (*f == '\n')
358                     f++;
359                 if(width)
360                     ADD_TO_T(' ');
361                 else
362                 {
363                     ADD_TO_T('\r');
364                     ADD_TO_T('\n');
365                 }
366             } else {
367                 if (ch == '\n')
368                 {
369                     if(width)
370                         ADD_TO_T(' ');
371                     else
372                     {
373                         ADD_TO_T('\r');
374                         ADD_TO_T('\n');
375                     }
376                 }
377                 else
378                     ADD_TO_T(ch);
379             }
380         }
381     }
382     *t = '\0';
383
384     return target;
385 }
386 #undef ADD_TO_T
387
388 /***********************************************************************
389  *           FormatMessageA   (KERNEL32.@)
390  * FIXME: missing wrap,
391  */
392 DWORD WINAPI FormatMessageA(
393         DWORD   dwFlags,
394         LPCVOID lpSource,
395         DWORD   dwMessageId,
396         DWORD   dwLanguageId,
397         LPSTR   lpBuffer,
398         DWORD   nSize,
399         __ms_va_list* args )
400 {
401     struct format_args format_args;
402     DWORD ret = 0;
403     LPWSTR      target;
404     DWORD       destlength;
405     LPWSTR      from;
406     DWORD       width = dwFlags & FORMAT_MESSAGE_MAX_WIDTH_MASK;
407
408     TRACE("(0x%x,%p,%d,0x%x,%p,%d,%p)\n",
409           dwFlags,lpSource,dwMessageId,dwLanguageId,lpBuffer,nSize,args);
410
411     if (dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER)
412     {
413         if (!lpBuffer)
414         {
415             SetLastError(ERROR_NOT_ENOUGH_MEMORY);
416             return 0;
417         }
418         else
419             *(LPSTR *)lpBuffer = NULL;
420     }
421
422     if (dwFlags & FORMAT_MESSAGE_ARGUMENT_ARRAY)
423     {
424         format_args.args = (ULONG_PTR *)args;
425         format_args.list = NULL;
426         format_args.last = 0;
427     }
428     else
429     {
430         format_args.args = NULL;
431         format_args.list = args;
432         format_args.last = 0;
433     }
434
435     if (width && width != FORMAT_MESSAGE_MAX_WIDTH_MASK)
436         FIXME("line wrapping (%u) not supported.\n", width);
437     from = NULL;
438     if (dwFlags & FORMAT_MESSAGE_FROM_STRING)
439     {
440         DWORD length = MultiByteToWideChar(CP_ACP, 0, lpSource, -1, NULL, 0);
441         from = HeapAlloc( GetProcessHeap(), 0, length * sizeof(WCHAR) );
442         MultiByteToWideChar(CP_ACP, 0, lpSource, -1, from, length);
443     }
444     else if (dwFlags & (FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_FROM_SYSTEM))
445     {
446         if (dwFlags & FORMAT_MESSAGE_FROM_HMODULE)
447             from = load_message( (HMODULE)lpSource, dwMessageId, dwLanguageId );
448         if (!from && (dwFlags & FORMAT_MESSAGE_FROM_SYSTEM))
449             from = load_message( kernel32_handle, dwMessageId, dwLanguageId );
450         if (!from) return 0;
451     }
452     else
453     {
454         SetLastError(ERROR_INVALID_PARAMETER);
455         return 0;
456     }
457
458     target = format_message( FALSE, dwFlags, from, &format_args );
459     if (!target)
460         goto failure;
461
462     TRACE("-- %s\n", debugstr_w(target));
463
464     /* Only try writing to an output buffer if there are processed characters
465      * in the temporary output buffer. */
466     if (*target)
467     {
468         destlength = WideCharToMultiByte(CP_ACP, 0, target, -1, NULL, 0, NULL, NULL);
469         if (dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER)
470         {
471             LPSTR buf = LocalAlloc(LMEM_ZEROINIT, max(nSize, destlength));
472             WideCharToMultiByte(CP_ACP, 0, target, -1, buf, destlength, NULL, NULL);
473             *((LPSTR*)lpBuffer) = buf;
474         }
475         else
476         {
477             if (nSize < destlength)
478             {
479                 SetLastError(ERROR_INSUFFICIENT_BUFFER);
480                 goto failure;
481             }
482             WideCharToMultiByte(CP_ACP, 0, target, -1, lpBuffer, destlength, NULL, NULL);
483         }
484         ret = destlength - 1; /* null terminator */
485     }
486
487 failure:
488     HeapFree(GetProcessHeap(),0,target);
489     HeapFree(GetProcessHeap(),0,from);
490     if (!(dwFlags & FORMAT_MESSAGE_ARGUMENT_ARRAY)) HeapFree( GetProcessHeap(), 0, format_args.args );
491     TRACE("-- returning %u\n", ret);
492     return ret;
493 }
494
495 /***********************************************************************
496  *           FormatMessageW   (KERNEL32.@)
497  */
498 DWORD WINAPI FormatMessageW(
499         DWORD   dwFlags,
500         LPCVOID lpSource,
501         DWORD   dwMessageId,
502         DWORD   dwLanguageId,
503         LPWSTR  lpBuffer,
504         DWORD   nSize,
505         __ms_va_list* args )
506 {
507     struct format_args format_args;
508     DWORD ret = 0;
509     LPWSTR target;
510     DWORD talloced;
511     LPWSTR from;
512     DWORD width = dwFlags & FORMAT_MESSAGE_MAX_WIDTH_MASK;
513
514     TRACE("(0x%x,%p,%d,0x%x,%p,%d,%p)\n",
515           dwFlags,lpSource,dwMessageId,dwLanguageId,lpBuffer,nSize,args);
516
517     if (!lpBuffer)
518     {
519         SetLastError(ERROR_INVALID_PARAMETER);
520         return 0;
521     }
522
523     if (dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER)
524         *(LPWSTR *)lpBuffer = NULL;
525
526     if (dwFlags & FORMAT_MESSAGE_ARGUMENT_ARRAY)
527     {
528         format_args.args = (ULONG_PTR *)args;
529         format_args.list = NULL;
530         format_args.last = 0;
531     }
532     else
533     {
534         format_args.args = NULL;
535         format_args.list = args;
536         format_args.last = 0;
537     }
538
539     if (width && width != FORMAT_MESSAGE_MAX_WIDTH_MASK)
540         FIXME("line wrapping not supported.\n");
541     from = NULL;
542     if (dwFlags & FORMAT_MESSAGE_FROM_STRING) {
543         from = HeapAlloc( GetProcessHeap(), 0, (strlenW(lpSource) + 1) *
544             sizeof(WCHAR) );
545         strcpyW( from, lpSource );
546     }
547     else if (dwFlags & (FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_FROM_SYSTEM))
548     {
549         if (dwFlags & FORMAT_MESSAGE_FROM_HMODULE)
550             from = load_message( (HMODULE)lpSource, dwMessageId, dwLanguageId );
551         if (!from && (dwFlags & FORMAT_MESSAGE_FROM_SYSTEM))
552             from = load_message( kernel32_handle, dwMessageId, dwLanguageId );
553         if (!from) return 0;
554     }
555     else
556     {
557         SetLastError(ERROR_INVALID_PARAMETER);
558         return 0;
559     }
560
561     target = format_message( TRUE, dwFlags, from, &format_args );
562     if (!target)
563         goto failure;
564
565     talloced = strlenW(target)+1;
566     TRACE("-- %s\n",debugstr_w(target));
567
568     /* Only allocate a buffer if there are processed characters in the
569      * temporary output buffer. If a caller supplies the buffer, then
570      * a null terminator will be written to it. */
571     if (dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER)
572     {
573         if (*target)
574         {
575             /* nSize is the MINIMUM size */
576             *((LPVOID*)lpBuffer) = LocalAlloc(LMEM_ZEROINIT, max(nSize, talloced)*sizeof(WCHAR));
577             strcpyW(*(LPWSTR*)lpBuffer, target);
578         }
579     }
580     else
581     {
582         if (nSize < talloced)
583         {
584             SetLastError(ERROR_INSUFFICIENT_BUFFER);
585             goto failure;
586         }
587         strcpyW(lpBuffer, target);
588     }
589
590     ret = talloced - 1; /* null terminator */
591 failure:
592     HeapFree(GetProcessHeap(),0,target);
593     HeapFree(GetProcessHeap(),0,from);
594     if (!(dwFlags & FORMAT_MESSAGE_ARGUMENT_ARRAY)) HeapFree( GetProcessHeap(), 0, format_args.args );
595     TRACE("-- returning %u\n", ret);
596     return ret;
597 }