kernel32: Update the Dutch (Suriname) NLS file.
[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 *)get_arg( insert, flags, args );
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     if (dwFlags & FORMAT_MESSAGE_IGNORE_INSERTS) {
289         while (*f && !eos)
290             ADD_TO_T(*f++);
291     }
292     else {
293         while (*f && !eos) {
294             if (*f=='%') {
295                 int insertnr;
296                 WCHAR *str,*x;
297
298                 f++;
299                 if (!*f) {
300                     ADD_TO_T('%');
301                     continue;
302                 }
303
304                 switch (*f) {
305                 case '1':case '2':case '3':case '4':case '5':
306                 case '6':case '7':case '8':case '9':
307                     insertnr = *f-'0';
308                     switch (f[1]) {
309                     case '0':case '1':case '2':case '3':
310                     case '4':case '5':case '6':case '7':
311                     case '8':case '9':
312                         f++;
313                         insertnr = insertnr*10 + *f-'0';
314                         f++;
315                         break;
316                     default:
317                         f++;
318                         break;
319                     }
320                     f = format_insert( unicode_caller, insertnr, f, dwFlags, format_args, &str );
321                     for (x = str; *x; x++) ADD_TO_T(*x);
322                     HeapFree( GetProcessHeap(), 0, str );
323                     break;
324                 case 'n':
325                     ADD_TO_T('\r');
326                     ADD_TO_T('\n');
327                     f++;
328                     break;
329                 case '0':
330                     eos = TRUE;
331                     f++;
332                     break;
333                 default:
334                     ADD_TO_T(*f++);
335                     break;
336                 }
337             } else {
338                 ch = *f;
339                 f++;
340                 if (ch == '\r') {
341                     if (*f == '\n')
342                         f++;
343                     if(width)
344                         ADD_TO_T(' ');
345                     else
346                     {
347                         ADD_TO_T('\r');
348                         ADD_TO_T('\n');
349                     }
350                 } else {
351                     if (ch == '\n')
352                     {
353                         if(width)
354                             ADD_TO_T(' ');
355                         else
356                         {
357                             ADD_TO_T('\r');
358                             ADD_TO_T('\n');
359                         }
360                     }
361                     else
362                         ADD_TO_T(ch);
363                 }
364             }
365         }
366     }
367     *t = '\0';
368
369     return target;
370 }
371 #undef ADD_TO_T
372
373 /***********************************************************************
374  *           FormatMessageA   (KERNEL32.@)
375  * FIXME: missing wrap,
376  */
377 DWORD WINAPI FormatMessageA(
378         DWORD   dwFlags,
379         LPCVOID lpSource,
380         DWORD   dwMessageId,
381         DWORD   dwLanguageId,
382         LPSTR   lpBuffer,
383         DWORD   nSize,
384         __ms_va_list* args )
385 {
386     struct format_args format_args;
387     DWORD ret = 0;
388     LPWSTR      target;
389     DWORD       destlength;
390     LPWSTR      from;
391     DWORD       width = dwFlags & FORMAT_MESSAGE_MAX_WIDTH_MASK;
392
393     TRACE("(0x%x,%p,%d,0x%x,%p,%d,%p)\n",
394           dwFlags,lpSource,dwMessageId,dwLanguageId,lpBuffer,nSize,args);
395     if ((dwFlags & FORMAT_MESSAGE_FROM_STRING)
396         &&((dwFlags & FORMAT_MESSAGE_FROM_SYSTEM)
397            || (dwFlags & FORMAT_MESSAGE_FROM_HMODULE))) return 0;
398
399     if ((dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER) && !lpBuffer)
400     {
401         SetLastError(ERROR_NOT_ENOUGH_MEMORY);
402         return 0;
403     }
404
405     if (dwFlags & FORMAT_MESSAGE_ARGUMENT_ARRAY)
406     {
407         format_args.args = (ULONG_PTR *)args;
408         format_args.list = NULL;
409         format_args.last = 0;
410     }
411     else
412     {
413         format_args.args = NULL;
414         format_args.list = args;
415         format_args.last = 0;
416     }
417
418     if (width && width != FORMAT_MESSAGE_MAX_WIDTH_MASK)
419         FIXME("line wrapping (%u) not supported.\n", width);
420     from = NULL;
421     if (dwFlags & FORMAT_MESSAGE_FROM_STRING)
422     {
423         DWORD length = MultiByteToWideChar(CP_ACP, 0, lpSource, -1, NULL, 0);
424         from = HeapAlloc( GetProcessHeap(), 0, length * sizeof(WCHAR) );
425         MultiByteToWideChar(CP_ACP, 0, lpSource, -1, from, length);
426     }
427     else {
428         if (dwFlags & FORMAT_MESSAGE_FROM_HMODULE)
429             from = load_message( (HMODULE)lpSource, dwMessageId, dwLanguageId );
430         if (!from && (dwFlags & FORMAT_MESSAGE_FROM_SYSTEM))
431             from = load_message( kernel32_handle, dwMessageId, dwLanguageId );
432         if (!from) return 0;
433     }
434
435     target = format_message( FALSE, dwFlags, from, &format_args );
436
437     TRACE("-- %s\n", debugstr_w(target));
438     destlength = WideCharToMultiByte(CP_ACP, 0, target, -1, NULL, 0, NULL, NULL);
439     if (dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER) {
440         LPSTR buf = LocalAlloc(LMEM_ZEROINIT, max(nSize, destlength));
441         WideCharToMultiByte(CP_ACP, 0, target, -1, buf, destlength, NULL, NULL);
442         *((LPSTR*)lpBuffer) = buf;
443     } else {
444         if (nSize < destlength)
445         {
446             SetLastError(ERROR_INSUFFICIENT_BUFFER);
447             goto failure;
448         }
449
450         WideCharToMultiByte(CP_ACP, 0, target, -1, lpBuffer, destlength, NULL, NULL);
451     }
452
453     ret = destlength - 1; /* null terminator */
454 failure:
455     HeapFree(GetProcessHeap(),0,target);
456     HeapFree(GetProcessHeap(),0,from);
457     if (!(dwFlags & FORMAT_MESSAGE_ARGUMENT_ARRAY)) HeapFree( GetProcessHeap(), 0, format_args.args );
458     TRACE("-- returning %u\n", ret);
459     return ret;
460 }
461 #undef ADD_TO_T
462
463
464 /***********************************************************************
465  *           FormatMessageW   (KERNEL32.@)
466  */
467 DWORD WINAPI FormatMessageW(
468         DWORD   dwFlags,
469         LPCVOID lpSource,
470         DWORD   dwMessageId,
471         DWORD   dwLanguageId,
472         LPWSTR  lpBuffer,
473         DWORD   nSize,
474         __ms_va_list* args )
475 {
476     struct format_args format_args;
477     DWORD ret = 0;
478     LPWSTR target;
479     DWORD talloced;
480     LPWSTR from;
481     DWORD width = dwFlags & FORMAT_MESSAGE_MAX_WIDTH_MASK;
482
483     TRACE("(0x%x,%p,%d,0x%x,%p,%d,%p)\n",
484           dwFlags,lpSource,dwMessageId,dwLanguageId,lpBuffer,nSize,args);
485     if ((dwFlags & FORMAT_MESSAGE_FROM_STRING)
486         &&((dwFlags & FORMAT_MESSAGE_FROM_SYSTEM)
487            || (dwFlags & FORMAT_MESSAGE_FROM_HMODULE))) return 0;
488
489     if (!lpBuffer)
490     {
491         SetLastError(ERROR_INVALID_PARAMETER);
492         return 0;
493     }
494
495     if (dwFlags & FORMAT_MESSAGE_ARGUMENT_ARRAY)
496     {
497         format_args.args = (ULONG_PTR *)args;
498         format_args.list = NULL;
499         format_args.last = 0;
500     }
501     else
502     {
503         format_args.args = NULL;
504         format_args.list = args;
505         format_args.last = 0;
506     }
507
508     if (width && width != FORMAT_MESSAGE_MAX_WIDTH_MASK)
509         FIXME("line wrapping not supported.\n");
510     from = NULL;
511     if (dwFlags & FORMAT_MESSAGE_FROM_STRING) {
512         from = HeapAlloc( GetProcessHeap(), 0, (strlenW(lpSource) + 1) *
513             sizeof(WCHAR) );
514         strcpyW( from, lpSource );
515     }
516     else {
517         if (dwFlags & FORMAT_MESSAGE_FROM_HMODULE)
518             from = load_message( (HMODULE)lpSource, dwMessageId, dwLanguageId );
519         if (!from && (dwFlags & FORMAT_MESSAGE_FROM_SYSTEM))
520             from = load_message( kernel32_handle, dwMessageId, dwLanguageId );
521         if (!from) return 0;
522     }
523
524     target = format_message( TRUE, dwFlags, from, &format_args );
525
526     talloced = strlenW(target)+1;
527     TRACE("-- %s\n",debugstr_w(target));
528     if (dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER) {
529         /* nSize is the MINIMUM size */
530         *((LPVOID*)lpBuffer) = LocalAlloc(LMEM_ZEROINIT, max(nSize, talloced)*sizeof(WCHAR));
531         strcpyW(*(LPWSTR*)lpBuffer, target);
532     }
533     else
534     {
535         if (nSize < talloced)
536         {
537             SetLastError(ERROR_INSUFFICIENT_BUFFER);
538             goto failure;
539         }
540         strcpyW(lpBuffer, target);
541     }
542
543     ret = talloced - 1; /* null terminator */
544 failure:
545     HeapFree(GetProcessHeap(),0,target);
546     HeapFree(GetProcessHeap(),0,from);
547     if (!(dwFlags & FORMAT_MESSAGE_ARGUMENT_ARRAY)) HeapFree( GetProcessHeap(), 0, format_args.args );
548     TRACE("-- returning %u\n", ret);
549     return ret;
550 }
551 #undef ADD_TO_T