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