[KERNEL32]
[reactos.git] / reactos / dll / win32 / kernel32 / winnls / string / 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 #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 static const WCHAR FMTWSTR[] = { '%','s',0 };
60
61 /**********************************************************************
62 * load_message (internal)
63 */
64 static LPWSTR load_message( HMODULE module, UINT id, WORD lang )
65 {
66 MESSAGE_RESOURCE_ENTRY *mre;
67 WCHAR *buffer;
68 NTSTATUS status;
69
70 TRACE("module = %p, id = %08x\n", module, id );
71
72 if (!module) module = GetModuleHandleW( NULL );
73 if ((status = RtlFindMessage( module, (ULONG)RT_MESSAGETABLE, lang, id, &mre )) != STATUS_SUCCESS)
74 {
75 SetLastError( RtlNtStatusToDosError(status) );
76 return NULL;
77 }
78
79 if (mre->Flags & MESSAGE_RESOURCE_UNICODE)
80 {
81 int len = (strlenW( (const WCHAR *)mre->Text ) + 1) * sizeof(WCHAR);
82 if (!(buffer = HeapAlloc( GetProcessHeap(), 0, len ))) return NULL;
83 memcpy( buffer, mre->Text, len );
84 }
85 else
86 {
87 int len = MultiByteToWideChar( CP_ACP, 0, (const char *)mre->Text, -1, NULL, 0 );
88 if (!(buffer = HeapAlloc( GetProcessHeap(), 0, len * sizeof(WCHAR) ))) return NULL;
89 MultiByteToWideChar( CP_ACP, 0, (const char*)mre->Text, -1, buffer, len );
90 }
91 TRACE("returning %s\n", wine_dbgstr_w(buffer));
92 return buffer;
93 }
94
95 /**********************************************************************
96 * get_arg (internal)
97 */
98 static ULONG_PTR get_arg( int nr, DWORD flags, struct format_args *args )
99 {
100 if (nr == -1) nr = args->last + 1;
101 if (args->list)
102 {
103 if (!args->args) args->args = HeapAlloc( GetProcessHeap(), 0, 99 * sizeof(ULONG_PTR) );
104 while (nr > args->last)
105 args->args[args->last++] = va_arg( *args->list, ULONG_PTR );
106 }
107 if (nr > args->last) args->last = nr;
108 return args->args[nr - 1];
109 }
110
111 /**********************************************************************
112 * format_insert (internal)
113 */
114 static LPCWSTR format_insert( BOOL unicode_caller, int insert, LPCWSTR format,
115 DWORD flags, struct format_args *args,
116 LPWSTR *result )
117 {
118 static const WCHAR fmt_lu[] = {'%','l','u',0};
119 WCHAR *wstring = NULL, *p, fmt[256];
120 ULONG_PTR arg;
121 int size;
122
123 if (*format != '!') /* simple string */
124 {
125 arg = get_arg( insert, flags, args );
126 if (unicode_caller || !arg)
127 {
128 static const WCHAR nullW[] = {'(','n','u','l','l',')',0};
129 const WCHAR *str = (const WCHAR *)arg;
130
131 if (!str) str = nullW;
132 *result = HeapAlloc( GetProcessHeap(), 0, (strlenW(str) + 1) * sizeof(WCHAR) );
133 strcpyW( *result, str );
134 }
135 else
136 {
137 const char *str = (const 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 = (char)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
396 TRACE("(0x%x,%p,%d,0x%x,%p,%d,%p)\n",
397 dwFlags,lpSource,dwMessageId,dwLanguageId,lpBuffer,nSize,args);
398
399 if (dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER)
400 {
401 if (!lpBuffer)
402 {
403 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
404 return 0;
405 }
406 else
407 *(LPSTR *)lpBuffer = NULL;
408 }
409
410 if (dwFlags & FORMAT_MESSAGE_ARGUMENT_ARRAY)
411 {
412 format_args.args = (ULONG_PTR *)args;
413 format_args.list = NULL;
414 format_args.last = 0;
415 }
416 else
417 {
418 format_args.args = NULL;
419 format_args.list = args;
420 format_args.last = 0;
421 }
422
423 if (width && width != FORMAT_MESSAGE_MAX_WIDTH_MASK)
424 FIXME("line wrapping (%u) not supported.\n", width);
425 from = NULL;
426 if (dwFlags & FORMAT_MESSAGE_FROM_STRING)
427 {
428 DWORD length = MultiByteToWideChar(CP_ACP, 0, lpSource, -1, NULL, 0);
429 from = HeapAlloc( GetProcessHeap(), 0, length * sizeof(WCHAR) );
430 MultiByteToWideChar(CP_ACP, 0, lpSource, -1, from, length);
431 }
432 else if (dwFlags & (FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_FROM_SYSTEM))
433 {
434 if (dwFlags & FORMAT_MESSAGE_FROM_HMODULE)
435 from = load_message( (HMODULE)lpSource, dwMessageId, (WORD)dwLanguageId );
436 if (!from && (dwFlags & FORMAT_MESSAGE_FROM_SYSTEM))
437 from = load_message( kernel32_handle, dwMessageId, (WORD)dwLanguageId );
438 if (!from) return 0;
439 }
440 else
441 {
442 SetLastError(ERROR_INVALID_PARAMETER);
443 return 0;
444 }
445
446 target = format_message( FALSE, dwFlags, from, &format_args );
447 if (!target)
448 goto failure;
449
450 TRACE("-- %s\n", debugstr_w(target));
451
452 /* Only try writing to an output buffer if there are processed characters
453 * in the temporary output buffer. */
454 if (*target)
455 {
456 destlength = WideCharToMultiByte(CP_ACP, 0, target, -1, NULL, 0, NULL, NULL);
457 if (dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER)
458 {
459 LPSTR buf = LocalAlloc(LMEM_ZEROINIT, max(nSize, destlength));
460 WideCharToMultiByte(CP_ACP, 0, target, -1, buf, destlength, NULL, NULL);
461 *((LPSTR*)lpBuffer) = buf;
462 }
463 else
464 {
465 if (nSize < destlength)
466 {
467 SetLastError(ERROR_INSUFFICIENT_BUFFER);
468 goto failure;
469 }
470 WideCharToMultiByte(CP_ACP, 0, target, -1, lpBuffer, destlength, NULL, NULL);
471 }
472 ret = destlength - 1; /* null terminator */
473 }
474
475 failure:
476 HeapFree(GetProcessHeap(),0,target);
477 HeapFree(GetProcessHeap(),0,from);
478 if (!(dwFlags & FORMAT_MESSAGE_ARGUMENT_ARRAY)) HeapFree( GetProcessHeap(), 0, format_args.args );
479 TRACE("-- returning %u\n", ret);
480 return ret;
481 }
482
483 /***********************************************************************
484 * FormatMessageW (KERNEL32.@)
485 */
486 DWORD WINAPI FormatMessageW(
487 DWORD dwFlags,
488 LPCVOID lpSource,
489 DWORD dwMessageId,
490 DWORD dwLanguageId,
491 LPWSTR lpBuffer,
492 DWORD nSize,
493 __ms_va_list* args )
494 {
495 struct format_args format_args;
496 DWORD ret = 0;
497 LPWSTR target;
498 DWORD talloced;
499 LPWSTR from;
500 DWORD width = dwFlags & FORMAT_MESSAGE_MAX_WIDTH_MASK;
501
502 TRACE("(0x%x,%p,%d,0x%x,%p,%d,%p)\n",
503 dwFlags,lpSource,dwMessageId,dwLanguageId,lpBuffer,nSize,args);
504
505 if (!lpBuffer)
506 {
507 SetLastError(ERROR_INVALID_PARAMETER);
508 return 0;
509 }
510
511 if (dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER)
512 *(LPWSTR *)lpBuffer = NULL;
513
514 if (dwFlags & FORMAT_MESSAGE_ARGUMENT_ARRAY)
515 {
516 format_args.args = (ULONG_PTR *)args;
517 format_args.list = NULL;
518 format_args.last = 0;
519 }
520 else
521 {
522 format_args.args = NULL;
523 format_args.list = args;
524 format_args.last = 0;
525 }
526
527 if (width && width != FORMAT_MESSAGE_MAX_WIDTH_MASK)
528 FIXME("line wrapping not supported.\n");
529 from = NULL;
530 if (dwFlags & FORMAT_MESSAGE_FROM_STRING) {
531 from = HeapAlloc( GetProcessHeap(), 0, (strlenW(lpSource) + 1) *
532 sizeof(WCHAR) );
533 strcpyW( from, lpSource );
534 }
535 else if (dwFlags & (FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_FROM_SYSTEM))
536 {
537 if (dwFlags & FORMAT_MESSAGE_FROM_HMODULE)
538 from = load_message( (HMODULE)lpSource, dwMessageId, (WORD)dwLanguageId );
539 if (!from && (dwFlags & FORMAT_MESSAGE_FROM_SYSTEM))
540 from = load_message( kernel32_handle, dwMessageId, (WORD)dwLanguageId );
541 if (!from) return 0;
542 }
543 else
544 {
545 SetLastError(ERROR_INVALID_PARAMETER);
546 return 0;
547 }
548
549 target = format_message( TRUE, dwFlags, from, &format_args );
550 if (!target)
551 goto failure;
552
553 talloced = strlenW(target)+1;
554 TRACE("-- %s\n",debugstr_w(target));
555
556 /* Only allocate a buffer if there are processed characters in the
557 * temporary output buffer. If a caller supplies the buffer, then
558 * a null terminator will be written to it. */
559 if (dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER)
560 {
561 if (*target)
562 {
563 /* nSize is the MINIMUM size */
564 *((LPVOID*)lpBuffer) = LocalAlloc(LMEM_ZEROINIT, max(nSize, talloced)*sizeof(WCHAR));
565 strcpyW(*(LPWSTR*)lpBuffer, target);
566 }
567 }
568 else
569 {
570 if (nSize < talloced)
571 {
572 SetLastError(ERROR_INSUFFICIENT_BUFFER);
573 goto failure;
574 }
575 strcpyW(lpBuffer, target);
576 }
577
578 ret = talloced - 1; /* null terminator */
579 failure:
580 HeapFree(GetProcessHeap(),0,target);
581 HeapFree(GetProcessHeap(),0,from);
582 if (!(dwFlags & FORMAT_MESSAGE_ARGUMENT_ARRAY)) HeapFree( GetProcessHeap(), 0, format_args.args );
583 TRACE("-- returning %u\n", ret);
584 return ret;
585 }