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