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