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