winmm: Update Dutch translation.
[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 PCNTFMTWSTR[] = { '%','%','%','s',0 };
72 static const WCHAR FMTWSTR[] = { '%','s',0 };
73
74 /**********************************************************************
75  *      load_message    (internal)
76  */
77 static LPWSTR load_message( HMODULE module, UINT id, WORD lang )
78 {
79     const MESSAGE_RESOURCE_ENTRY *mre;
80     WCHAR *buffer;
81     NTSTATUS status;
82
83     TRACE("module = %p, id = %08x\n", module, id );
84
85     if (!module) module = GetModuleHandleW( NULL );
86     if ((status = RtlFindMessage( module, RT_MESSAGETABLE, lang, id, &mre )) != STATUS_SUCCESS)
87     {
88         SetLastError( RtlNtStatusToDosError(status) );
89         return NULL;
90     }
91
92     if (mre->Flags & MESSAGE_RESOURCE_UNICODE)
93     {
94         int len = (strlenW( (const WCHAR *)mre->Text ) + 1) * sizeof(WCHAR);
95         if (!(buffer = HeapAlloc( GetProcessHeap(), 0, len ))) return NULL;
96         memcpy( buffer, mre->Text, len );
97     }
98     else
99     {
100         int len = MultiByteToWideChar( CP_ACP, 0, (const char *)mre->Text, -1, NULL, 0 );
101         if (!(buffer = HeapAlloc( GetProcessHeap(), 0, len * sizeof(WCHAR) ))) return NULL;
102         MultiByteToWideChar( CP_ACP, 0, (const char*)mre->Text, -1, buffer, len );
103     }
104     TRACE("returning %s\n", wine_dbgstr_w(buffer));
105     return buffer;
106 }
107
108 /**********************************************************************
109  *      get_arg    (internal)
110  */
111 static ULONG_PTR get_arg( int nr, DWORD flags, struct format_args *args )
112 {
113     if (nr == -1) nr = args->last + 1;
114     if (args->list)
115     {
116         if (!args->args) args->args = HeapAlloc( GetProcessHeap(), 0, 99 * sizeof(ULONG_PTR) );
117         while (nr > args->last)
118             args->args[args->last++] = va_arg( *args->list, ULONG_PTR );
119     }
120     if (nr > args->last) args->last = nr;
121     return args->args[nr - 1];
122 }
123
124 /**********************************************************************
125  *      format_insert    (internal)
126  */
127 static LPCWSTR format_insert( BOOL unicode_caller, int insert, LPCWSTR format,
128                               DWORD flags, struct format_args *args,
129                               LPWSTR *result )
130 {
131     static const WCHAR fmt_lu[] = {'%','l','u',0};
132     WCHAR *wstring = NULL, *p, fmt[256];
133     ULONG_PTR arg;
134     int size;
135
136     if (*format != '!')  /* simple string */
137     {
138         arg = get_arg( insert, flags, args );
139         if (unicode_caller)
140         {
141             WCHAR *str = (WCHAR *)arg;
142             *result = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) );
143             strcpyW( *result, str );
144         }
145         else
146         {
147             char *str = (char *)arg;
148             DWORD length = MultiByteToWideChar( CP_ACP, 0, str, -1, NULL, 0 );
149             *result = HeapAlloc( GetProcessHeap(), 0, length * sizeof(WCHAR) );
150             MultiByteToWideChar( CP_ACP, 0, str, -1, *result, length );
151         }
152         return format;
153     }
154
155     format++;
156     p = fmt;
157     *p++ = '%';
158
159     while (*format == '0' ||
160            *format == '+' ||
161            *format == '-' ||
162            *format == ' ' ||
163            *format == '*' ||
164            *format == '#')
165     {
166         if (*format == '*')
167         {
168             p += sprintfW( p, fmt_lu, get_arg( insert, flags, args ));
169             insert = -1;
170             format++;
171         }
172         else *p++ = *format++;
173     }
174     while (isdigitW(*format)) *p++ = *format++;
175
176     if (*format == '.')
177     {
178         *p++ = *format++;
179         if (*format == '*')
180         {
181             p += sprintfW( p, fmt_lu, get_arg( insert, flags, args ));
182             insert = -1;
183             format++;
184         }
185         else
186             while (isdigitW(*format)) *p++ = *format++;
187     }
188
189     /* replicate MS bug: drop an argument when using va_list with width/precision */
190     if (insert == -1 && args->list) args->last--;
191     arg = get_arg( insert, flags, args );
192
193     /* check for ascii string format */
194     if ((format[0] == 'h' && format[1] == 's') ||
195         (format[0] == 'h' && format[1] == 'S') ||
196         (unicode_caller && format[0] == 'S') ||
197         (!unicode_caller && format[0] == 's'))
198     {
199         DWORD len = MultiByteToWideChar( CP_ACP, 0, (char *)arg, -1, NULL, 0 );
200         wstring = HeapAlloc( GetProcessHeap(), 0, len * sizeof(WCHAR) );
201         MultiByteToWideChar( CP_ACP, 0, (char *)arg, -1, wstring, len );
202         arg = (ULONG_PTR)wstring;
203         *p++ = 's';
204     }
205     /* check for ascii character format */
206     else if ((format[0] == 'h' && format[1] == 'c') ||
207              (format[0] == 'h' && format[1] == 'C') ||
208              (unicode_caller && format[0] == 'C') ||
209              (!unicode_caller && format[0] == 'c'))
210     {
211         char ch = arg;
212         wstring = HeapAlloc( GetProcessHeap(), 0, 2 * sizeof(WCHAR) );
213         MultiByteToWideChar( CP_ACP, 0, &ch, 1, wstring, 1 );
214         wstring[1] = 0;
215         arg = (ULONG_PTR)wstring;
216         *p++ = 's';
217     }
218     /* check for wide string format */
219     else if ((format[0] == 'l' && format[1] == 's') ||
220              (format[0] == 'l' && format[1] == 'S') ||
221              (format[0] == 'w' && format[1] == 's') ||
222              (!unicode_caller && format[0] == 'S'))
223     {
224         *p++ = 's';
225     }
226     /* check for wide character format */
227     else if ((format[0] == 'l' && format[1] == 'c') ||
228              (format[0] == 'l' && format[1] == 'C') ||
229              (format[0] == 'w' && format[1] == 'c') ||
230              (!unicode_caller && format[0] == 'C'))
231     {
232         *p++ = 'c';
233     }
234     /* FIXME: handle I64 etc. */
235     else while (*format && *format != '!') *p++ = *format++;
236
237     *p = 0;
238     size = 256;
239     for (;;)
240     {
241         WCHAR *ret = HeapAlloc( GetProcessHeap(), 0, size * sizeof(WCHAR) );
242         int needed = snprintfW( ret, size, fmt, arg );
243         if (needed == -1 || needed >= size)
244         {
245             HeapFree( GetProcessHeap(), 0, ret );
246             size = max( needed + 1, size * 2 );
247         }
248         else
249         {
250             *result = ret;
251             break;
252         }
253     }
254
255     while (*format && *format != '!') format++;
256     if (*format == '!') format++;
257
258     HeapFree( GetProcessHeap(), 0, wstring );
259     return format;
260 }
261
262 /**********************************************************************
263  *      format_message    (internal)
264  */
265 static LPWSTR format_message( BOOL unicode_caller, DWORD dwFlags, LPCWSTR fmtstr,
266                               struct format_args *format_args )
267 {
268     LPWSTR target,t;
269     DWORD talloced;
270     LPCWSTR f;
271     DWORD width = dwFlags & FORMAT_MESSAGE_MAX_WIDTH_MASK;
272     BOOL eos = FALSE;
273     WCHAR ch;
274
275     target = t = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, 100 * sizeof(WCHAR) );
276     talloced = 100;
277
278 #define ADD_TO_T(c)  do {\
279     *t++=c;\
280     if ((DWORD)(t-target) == talloced) {\
281         target = HeapReAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,target,talloced*2*sizeof(WCHAR));\
282         t = target+talloced;\
283         talloced*=2;\
284     } \
285 } while (0)
286
287     f = fmtstr;
288     while (*f && !eos) {
289         if (*f=='%') {
290             int insertnr;
291             WCHAR *str,*x;
292
293             f++;
294             switch (*f) {
295             case '1':case '2':case '3':case '4':case '5':
296             case '6':case '7':case '8':case '9':
297                 if (dwFlags & FORMAT_MESSAGE_IGNORE_INSERTS)
298                     goto ignore_inserts;
299                 insertnr = *f-'0';
300                 switch (f[1]) {
301                 case '0':case '1':case '2':case '3':
302                 case '4':case '5':case '6':case '7':
303                 case '8':case '9':
304                     f++;
305                     insertnr = insertnr*10 + *f-'0';
306                     f++;
307                     break;
308                 default:
309                     f++;
310                     break;
311                 }
312                 f = format_insert( unicode_caller, insertnr, f, dwFlags, format_args, &str );
313                 for (x = str; *x; x++) ADD_TO_T(*x);
314                 HeapFree( GetProcessHeap(), 0, str );
315                 break;
316             case 'n':
317                 ADD_TO_T('\r');
318                 ADD_TO_T('\n');
319                 f++;
320                 break;
321             case 'r':
322                 ADD_TO_T('\r');
323                 f++;
324                 break;
325             case 't':
326                 ADD_TO_T('\t');
327                 f++;
328                 break;
329             case '0':
330                 eos = TRUE;
331                 f++;
332                 break;
333             case '\0':
334                 SetLastError(ERROR_INVALID_PARAMETER);
335                 HeapFree(GetProcessHeap(), 0, target);
336                 return NULL;
337             ignore_inserts:
338             default:
339                 if (dwFlags & FORMAT_MESSAGE_IGNORE_INSERTS)
340                     ADD_TO_T('%');
341                 ADD_TO_T(*f++);
342                 break;
343             }
344         } else {
345             ch = *f;
346             f++;
347             if (ch == '\r') {
348                 if (*f == '\n')
349                     f++;
350                 if(width)
351                     ADD_TO_T(' ');
352                 else
353                 {
354                     ADD_TO_T('\r');
355                     ADD_TO_T('\n');
356                 }
357             } else {
358                 if (ch == '\n')
359                 {
360                     if(width)
361                         ADD_TO_T(' ');
362                     else
363                     {
364                         ADD_TO_T('\r');
365                         ADD_TO_T('\n');
366                     }
367                 }
368                 else
369                     ADD_TO_T(ch);
370             }
371         }
372     }
373     *t = '\0';
374
375     return target;
376 }
377 #undef ADD_TO_T
378
379 /***********************************************************************
380  *           FormatMessageA   (KERNEL32.@)
381  * FIXME: missing wrap,
382  */
383 DWORD WINAPI FormatMessageA(
384         DWORD   dwFlags,
385         LPCVOID lpSource,
386         DWORD   dwMessageId,
387         DWORD   dwLanguageId,
388         LPSTR   lpBuffer,
389         DWORD   nSize,
390         __ms_va_list* args )
391 {
392     struct format_args format_args;
393     DWORD ret = 0;
394     LPWSTR      target;
395     DWORD       destlength;
396     LPWSTR      from;
397     DWORD       width = dwFlags & FORMAT_MESSAGE_MAX_WIDTH_MASK;
398
399     TRACE("(0x%x,%p,%d,0x%x,%p,%d,%p)\n",
400           dwFlags,lpSource,dwMessageId,dwLanguageId,lpBuffer,nSize,args);
401
402     if (dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER)
403     {
404         if (!lpBuffer)
405         {
406             SetLastError(ERROR_NOT_ENOUGH_MEMORY);
407             return 0;
408         }
409         else
410             *(LPSTR *)lpBuffer = NULL;
411     }
412
413     if (dwFlags & FORMAT_MESSAGE_ARGUMENT_ARRAY)
414     {
415         format_args.args = (ULONG_PTR *)args;
416         format_args.list = NULL;
417         format_args.last = 0;
418     }
419     else
420     {
421         format_args.args = NULL;
422         format_args.list = args;
423         format_args.last = 0;
424     }
425
426     if (width && width != FORMAT_MESSAGE_MAX_WIDTH_MASK)
427         FIXME("line wrapping (%u) not supported.\n", width);
428     from = NULL;
429     if (dwFlags & FORMAT_MESSAGE_FROM_STRING)
430     {
431         DWORD length = MultiByteToWideChar(CP_ACP, 0, lpSource, -1, NULL, 0);
432         from = HeapAlloc( GetProcessHeap(), 0, length * sizeof(WCHAR) );
433         MultiByteToWideChar(CP_ACP, 0, lpSource, -1, from, length);
434     }
435     else if (dwFlags & (FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_FROM_SYSTEM))
436     {
437         if (dwFlags & FORMAT_MESSAGE_FROM_HMODULE)
438             from = load_message( (HMODULE)lpSource, dwMessageId, dwLanguageId );
439         if (!from && (dwFlags & FORMAT_MESSAGE_FROM_SYSTEM))
440             from = load_message( kernel32_handle, dwMessageId, dwLanguageId );
441         if (!from) return 0;
442     }
443     else
444     {
445         SetLastError(ERROR_INVALID_PARAMETER);
446         return 0;
447     }
448
449     target = format_message( FALSE, dwFlags, from, &format_args );
450     if (!target)
451         goto failure;
452
453     TRACE("-- %s\n", debugstr_w(target));
454
455     /* Only try writing to an output buffer if there are processed characters
456      * in the temporary output buffer. */
457     if (*target)
458     {
459         destlength = WideCharToMultiByte(CP_ACP, 0, target, -1, NULL, 0, NULL, NULL);
460         if (dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER)
461         {
462             LPSTR buf = LocalAlloc(LMEM_ZEROINIT, max(nSize, destlength));
463             WideCharToMultiByte(CP_ACP, 0, target, -1, buf, destlength, NULL, NULL);
464             *((LPSTR*)lpBuffer) = buf;
465         }
466         else
467         {
468             if (nSize < destlength)
469             {
470                 SetLastError(ERROR_INSUFFICIENT_BUFFER);
471                 goto failure;
472             }
473             WideCharToMultiByte(CP_ACP, 0, target, -1, lpBuffer, destlength, NULL, NULL);
474         }
475         ret = destlength - 1; /* null terminator */
476     }
477
478 failure:
479     HeapFree(GetProcessHeap(),0,target);
480     HeapFree(GetProcessHeap(),0,from);
481     if (!(dwFlags & FORMAT_MESSAGE_ARGUMENT_ARRAY)) HeapFree( GetProcessHeap(), 0, format_args.args );
482     TRACE("-- returning %u\n", ret);
483     return ret;
484 }
485
486 /***********************************************************************
487  *           FormatMessageW   (KERNEL32.@)
488  */
489 DWORD WINAPI FormatMessageW(
490         DWORD   dwFlags,
491         LPCVOID lpSource,
492         DWORD   dwMessageId,
493         DWORD   dwLanguageId,
494         LPWSTR  lpBuffer,
495         DWORD   nSize,
496         __ms_va_list* args )
497 {
498     struct format_args format_args;
499     DWORD ret = 0;
500     LPWSTR target;
501     DWORD talloced;
502     LPWSTR from;
503     DWORD width = dwFlags & FORMAT_MESSAGE_MAX_WIDTH_MASK;
504
505     TRACE("(0x%x,%p,%d,0x%x,%p,%d,%p)\n",
506           dwFlags,lpSource,dwMessageId,dwLanguageId,lpBuffer,nSize,args);
507
508     if (!lpBuffer)
509     {
510         SetLastError(ERROR_INVALID_PARAMETER);
511         return 0;
512     }
513
514     if (dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER)
515         *(LPWSTR *)lpBuffer = NULL;
516
517     if (dwFlags & FORMAT_MESSAGE_ARGUMENT_ARRAY)
518     {
519         format_args.args = (ULONG_PTR *)args;
520         format_args.list = NULL;
521         format_args.last = 0;
522     }
523     else
524     {
525         format_args.args = NULL;
526         format_args.list = args;
527         format_args.last = 0;
528     }
529
530     if (width && width != FORMAT_MESSAGE_MAX_WIDTH_MASK)
531         FIXME("line wrapping not supported.\n");
532     from = NULL;
533     if (dwFlags & FORMAT_MESSAGE_FROM_STRING) {
534         from = HeapAlloc( GetProcessHeap(), 0, (strlenW(lpSource) + 1) *
535             sizeof(WCHAR) );
536         strcpyW( from, lpSource );
537     }
538     else if (dwFlags & (FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_FROM_SYSTEM))
539     {
540         if (dwFlags & FORMAT_MESSAGE_FROM_HMODULE)
541             from = load_message( (HMODULE)lpSource, dwMessageId, dwLanguageId );
542         if (!from && (dwFlags & FORMAT_MESSAGE_FROM_SYSTEM))
543             from = load_message( kernel32_handle, dwMessageId, dwLanguageId );
544         if (!from) return 0;
545     }
546     else
547     {
548         SetLastError(ERROR_INVALID_PARAMETER);
549         return 0;
550     }
551
552     target = format_message( TRUE, dwFlags, from, &format_args );
553     if (!target)
554         goto failure;
555
556     talloced = strlenW(target)+1;
557     TRACE("-- %s\n",debugstr_w(target));
558
559     /* Only allocate a buffer if there are processed characters in the
560      * temporary output buffer. If a caller supplies the buffer, then
561      * a null terminator will be written to it. */
562     if (dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER)
563     {
564         if (*target)
565         {
566             /* nSize is the MINIMUM size */
567             *((LPVOID*)lpBuffer) = LocalAlloc(LMEM_ZEROINIT, max(nSize, talloced)*sizeof(WCHAR));
568             strcpyW(*(LPWSTR*)lpBuffer, target);
569         }
570     }
571     else
572     {
573         if (nSize < talloced)
574         {
575             SetLastError(ERROR_INSUFFICIENT_BUFFER);
576             goto failure;
577         }
578         strcpyW(lpBuffer, target);
579     }
580
581     ret = talloced - 1; /* null terminator */
582 failure:
583     HeapFree(GetProcessHeap(),0,target);
584     HeapFree(GetProcessHeap(),0,from);
585     if (!(dwFlags & FORMAT_MESSAGE_ARGUMENT_ARRAY)) HeapFree( GetProcessHeap(), 0, format_args.args );
586     TRACE("-- returning %u\n", ret);
587     return ret;
588 }