jscript: Added support for RegExp.$* properties.
[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 struct _format_message_data
265 {
266     LPWSTR formatted;
267     DWORD size;
268     LPWSTR t;
269 };
270
271 static void format_add_char(struct _format_message_data *fmd, WCHAR c)
272 {
273     *fmd->t++ = c;
274     if ((DWORD)(fmd->t - fmd->formatted) == fmd->size) {
275         fmd->formatted = HeapReAlloc(GetProcessHeap(), 0, fmd->formatted, (fmd->size * 2) * sizeof(WCHAR));
276         fmd->t = fmd->formatted + fmd->size;
277         fmd->size *= 2;
278     }
279 }
280
281 /**********************************************************************
282  *      format_message    (internal)
283  */
284 static LPWSTR format_message( BOOL unicode_caller, DWORD dwFlags, LPCWSTR fmtstr,
285                               struct format_args *format_args )
286 {
287     struct _format_message_data fmd;
288     LPCWSTR f;
289     DWORD width = dwFlags & FORMAT_MESSAGE_MAX_WIDTH_MASK;
290     BOOL eos = FALSE;
291
292     fmd.size = 100;
293     fmd.formatted = fmd.t = HeapAlloc( GetProcessHeap(), 0, fmd.size * sizeof(WCHAR) );
294
295     f = fmtstr;
296     while (*f && !eos) {
297         if (*f=='%') {
298             int insertnr;
299             WCHAR *str,*x;
300
301             f++;
302             switch (*f) {
303             case '1':case '2':case '3':case '4':case '5':
304             case '6':case '7':case '8':case '9':
305                 if (dwFlags & FORMAT_MESSAGE_IGNORE_INSERTS)
306                     goto ignore_inserts;
307                 else if (((dwFlags & FORMAT_MESSAGE_ARGUMENT_ARRAY) && !format_args->args) ||
308                         (!(dwFlags & FORMAT_MESSAGE_ARGUMENT_ARRAY) && !format_args->list))
309                 {
310                     SetLastError(ERROR_INVALID_PARAMETER);
311                     HeapFree(GetProcessHeap(), 0, fmd.formatted);
312                     return NULL;
313                 }
314                 insertnr = *f-'0';
315                 switch (f[1]) {
316                 case '0':case '1':case '2':case '3':
317                 case '4':case '5':case '6':case '7':
318                 case '8':case '9':
319                     f++;
320                     insertnr = insertnr*10 + *f-'0';
321                     f++;
322                     break;
323                 default:
324                     f++;
325                     break;
326                 }
327                 f = format_insert( unicode_caller, insertnr, f, dwFlags, format_args, &str );
328                 for (x = str; *x; x++) format_add_char(&fmd, *x);
329                 HeapFree( GetProcessHeap(), 0, str );
330                 break;
331             case 'n':
332                 format_add_char(&fmd, '\r');
333                 format_add_char(&fmd, '\n');
334                 f++;
335                 break;
336             case 'r':
337                 format_add_char(&fmd, '\r');
338                 f++;
339                 break;
340             case 't':
341                 format_add_char(&fmd, '\t');
342                 f++;
343                 break;
344             case '0':
345                 eos = TRUE;
346                 f++;
347                 break;
348             case '\0':
349                 SetLastError(ERROR_INVALID_PARAMETER);
350                 HeapFree(GetProcessHeap(), 0, fmd.formatted);
351                 return NULL;
352             ignore_inserts:
353             default:
354                 if (dwFlags & FORMAT_MESSAGE_IGNORE_INSERTS)
355                     format_add_char(&fmd, '%');
356                 format_add_char(&fmd, *f++);
357                 break;
358             }
359         } else {
360             WCHAR ch = *f;
361             f++;
362             if (ch == '\r') {
363                 if (*f == '\n')
364                     f++;
365                 if(width)
366                     format_add_char(&fmd, ' ');
367                 else
368                 {
369                     format_add_char(&fmd, '\r');
370                     format_add_char(&fmd, '\n');
371                 }
372             } else {
373                 if (ch == '\n')
374                 {
375                     if(width)
376                         format_add_char(&fmd, ' ');
377                     else
378                     {
379                         format_add_char(&fmd, '\r');
380                         format_add_char(&fmd, '\n');
381                     }
382                 }
383                 else
384                     format_add_char(&fmd, ch);
385             }
386         }
387     }
388     *fmd.t = '\0';
389
390     return fmd.formatted;
391 }
392
393 /***********************************************************************
394  *           FormatMessageA   (KERNEL32.@)
395  * FIXME: missing wrap,
396  */
397 DWORD WINAPI FormatMessageA(
398         DWORD   dwFlags,
399         LPCVOID lpSource,
400         DWORD   dwMessageId,
401         DWORD   dwLanguageId,
402         LPSTR   lpBuffer,
403         DWORD   nSize,
404         __ms_va_list* args )
405 {
406     struct format_args format_args;
407     DWORD ret = 0;
408     LPWSTR      target;
409     DWORD       destlength;
410     LPWSTR      from;
411     DWORD       width = dwFlags & FORMAT_MESSAGE_MAX_WIDTH_MASK;
412
413     TRACE("(0x%x,%p,%d,0x%x,%p,%d,%p)\n",
414           dwFlags,lpSource,dwMessageId,dwLanguageId,lpBuffer,nSize,args);
415
416     if (dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER)
417     {
418         if (!lpBuffer)
419         {
420             SetLastError(ERROR_NOT_ENOUGH_MEMORY);
421             return 0;
422         }
423         else
424             *(LPSTR *)lpBuffer = NULL;
425     }
426
427     if (dwFlags & FORMAT_MESSAGE_ARGUMENT_ARRAY)
428     {
429         format_args.args = (ULONG_PTR *)args;
430         format_args.list = NULL;
431         format_args.last = 0;
432     }
433     else
434     {
435         format_args.args = NULL;
436         format_args.list = args;
437         format_args.last = 0;
438     }
439
440     if (width && width != FORMAT_MESSAGE_MAX_WIDTH_MASK)
441         FIXME("line wrapping (%u) not supported.\n", width);
442     from = NULL;
443     if (dwFlags & FORMAT_MESSAGE_FROM_STRING)
444     {
445         DWORD length = MultiByteToWideChar(CP_ACP, 0, lpSource, -1, NULL, 0);
446         from = HeapAlloc( GetProcessHeap(), 0, length * sizeof(WCHAR) );
447         MultiByteToWideChar(CP_ACP, 0, lpSource, -1, from, length);
448     }
449     else if (dwFlags & (FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_FROM_SYSTEM))
450     {
451         if (dwFlags & FORMAT_MESSAGE_FROM_HMODULE)
452             from = load_message( (HMODULE)lpSource, dwMessageId, dwLanguageId );
453         if (!from && (dwFlags & FORMAT_MESSAGE_FROM_SYSTEM))
454             from = load_message( kernel32_handle, dwMessageId, dwLanguageId );
455         if (!from) return 0;
456     }
457     else
458     {
459         SetLastError(ERROR_INVALID_PARAMETER);
460         return 0;
461     }
462
463     target = format_message( FALSE, dwFlags, from, &format_args );
464     if (!target)
465         goto failure;
466
467     TRACE("-- %s\n", debugstr_w(target));
468
469     /* Only try writing to an output buffer if there are processed characters
470      * in the temporary output buffer. */
471     if (*target)
472     {
473         destlength = WideCharToMultiByte(CP_ACP, 0, target, -1, NULL, 0, NULL, NULL);
474         if (dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER)
475         {
476             LPSTR buf = LocalAlloc(LMEM_ZEROINIT, max(nSize, destlength));
477             WideCharToMultiByte(CP_ACP, 0, target, -1, buf, destlength, NULL, NULL);
478             *((LPSTR*)lpBuffer) = buf;
479         }
480         else
481         {
482             if (nSize < destlength)
483             {
484                 SetLastError(ERROR_INSUFFICIENT_BUFFER);
485                 goto failure;
486             }
487             WideCharToMultiByte(CP_ACP, 0, target, -1, lpBuffer, destlength, NULL, NULL);
488         }
489         ret = destlength - 1; /* null terminator */
490     }
491
492 failure:
493     HeapFree(GetProcessHeap(),0,target);
494     HeapFree(GetProcessHeap(),0,from);
495     if (!(dwFlags & FORMAT_MESSAGE_ARGUMENT_ARRAY)) HeapFree( GetProcessHeap(), 0, format_args.args );
496     TRACE("-- returning %u\n", ret);
497     return ret;
498 }
499
500 /***********************************************************************
501  *           FormatMessageW   (KERNEL32.@)
502  */
503 DWORD WINAPI FormatMessageW(
504         DWORD   dwFlags,
505         LPCVOID lpSource,
506         DWORD   dwMessageId,
507         DWORD   dwLanguageId,
508         LPWSTR  lpBuffer,
509         DWORD   nSize,
510         __ms_va_list* args )
511 {
512     struct format_args format_args;
513     DWORD ret = 0;
514     LPWSTR target;
515     DWORD talloced;
516     LPWSTR from;
517     DWORD width = dwFlags & FORMAT_MESSAGE_MAX_WIDTH_MASK;
518
519     TRACE("(0x%x,%p,%d,0x%x,%p,%d,%p)\n",
520           dwFlags,lpSource,dwMessageId,dwLanguageId,lpBuffer,nSize,args);
521
522     if (!lpBuffer)
523     {
524         SetLastError(ERROR_INVALID_PARAMETER);
525         return 0;
526     }
527
528     if (dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER)
529         *(LPWSTR *)lpBuffer = NULL;
530
531     if (dwFlags & FORMAT_MESSAGE_ARGUMENT_ARRAY)
532     {
533         format_args.args = (ULONG_PTR *)args;
534         format_args.list = NULL;
535         format_args.last = 0;
536     }
537     else
538     {
539         format_args.args = NULL;
540         format_args.list = args;
541         format_args.last = 0;
542     }
543
544     if (width && width != FORMAT_MESSAGE_MAX_WIDTH_MASK)
545         FIXME("line wrapping not supported.\n");
546     from = NULL;
547     if (dwFlags & FORMAT_MESSAGE_FROM_STRING) {
548         from = HeapAlloc( GetProcessHeap(), 0, (strlenW(lpSource) + 1) *
549             sizeof(WCHAR) );
550         strcpyW( from, lpSource );
551     }
552     else if (dwFlags & (FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_FROM_SYSTEM))
553     {
554         if (dwFlags & FORMAT_MESSAGE_FROM_HMODULE)
555             from = load_message( (HMODULE)lpSource, dwMessageId, dwLanguageId );
556         if (!from && (dwFlags & FORMAT_MESSAGE_FROM_SYSTEM))
557             from = load_message( kernel32_handle, dwMessageId, dwLanguageId );
558         if (!from) return 0;
559     }
560     else
561     {
562         SetLastError(ERROR_INVALID_PARAMETER);
563         return 0;
564     }
565
566     target = format_message( TRUE, dwFlags, from, &format_args );
567     if (!target)
568         goto failure;
569
570     talloced = strlenW(target)+1;
571     TRACE("-- %s\n",debugstr_w(target));
572
573     /* Only allocate a buffer if there are processed characters in the
574      * temporary output buffer. If a caller supplies the buffer, then
575      * a null terminator will be written to it. */
576     if (dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER)
577     {
578         if (*target)
579         {
580             /* nSize is the MINIMUM size */
581             *((LPVOID*)lpBuffer) = LocalAlloc(LMEM_ZEROINIT, max(nSize, talloced)*sizeof(WCHAR));
582             strcpyW(*(LPWSTR*)lpBuffer, target);
583         }
584     }
585     else
586     {
587         if (nSize < talloced)
588         {
589             SetLastError(ERROR_INSUFFICIENT_BUFFER);
590             goto failure;
591         }
592         strcpyW(lpBuffer, target);
593     }
594
595     ret = talloced - 1; /* null terminator */
596 failure:
597     HeapFree(GetProcessHeap(),0,target);
598     HeapFree(GetProcessHeap(),0,from);
599     if (!(dwFlags & FORMAT_MESSAGE_ARGUMENT_ARRAY)) HeapFree( GetProcessHeap(), 0, format_args.args );
600     TRACE("-- returning %u\n", ret);
601     return ret;
602 }