1 /* 2 * CHM Utility API 3 * 4 * Copyright 2005 James Hawkins 5 * Copyright 2007 Jacek Caban 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 "hhctrl.h" 23 #include "stream.h" 24 25 #include "winreg.h" 26 #include "shlwapi.h" 27 #include "wine/debug.h" 28 29 WINE_DEFAULT_DEBUG_CHANNEL(htmlhelp); 30 31 /* Reads a string from the #STRINGS section in the CHM file */ 32 static LPCSTR GetChmString(CHMInfo *chm, DWORD offset) 33 { 34 LPCSTR str; 35 36 if(!chm->strings_stream) 37 return NULL; 38 39 if(chm->strings_size <= (offset >> BLOCK_BITS)) { 40 chm->strings_size = (offset >> BLOCK_BITS)+1; 41 if(chm->strings) 42 chm->strings = heap_realloc_zero(chm->strings, 43 chm->strings_size*sizeof(char*)); 44 else 45 chm->strings = heap_alloc_zero( 46 chm->strings_size*sizeof(char*)); 47 48 } 49 50 if(!chm->strings[offset >> BLOCK_BITS]) { 51 LARGE_INTEGER pos; 52 DWORD read; 53 HRESULT hres; 54 55 pos.QuadPart = offset & ~BLOCK_MASK; 56 hres = IStream_Seek(chm->strings_stream, pos, STREAM_SEEK_SET, NULL); 57 if(FAILED(hres)) { 58 WARN("Seek failed: %08x\n", hres); 59 return NULL; 60 } 61 62 chm->strings[offset >> BLOCK_BITS] = heap_alloc(BLOCK_SIZE); 63 64 hres = IStream_Read(chm->strings_stream, chm->strings[offset >> BLOCK_BITS], 65 BLOCK_SIZE, &read); 66 if(FAILED(hres)) { 67 WARN("Read failed: %08x\n", hres); 68 heap_free(chm->strings[offset >> BLOCK_BITS]); 69 chm->strings[offset >> BLOCK_BITS] = NULL; 70 return NULL; 71 } 72 } 73 74 str = chm->strings[offset >> BLOCK_BITS] + (offset & BLOCK_MASK); 75 TRACE("offset %#x => %s\n", offset, debugstr_a(str)); 76 return str; 77 } 78 79 static BOOL ReadChmSystem(CHMInfo *chm) 80 { 81 IStream *stream; 82 DWORD ver=0xdeadbeef, read, buf_size; 83 char *buf; 84 HRESULT hres; 85 86 struct { 87 WORD code; 88 WORD len; 89 } entry; 90 91 static const WCHAR wszSYSTEM[] = {'#','S','Y','S','T','E','M',0}; 92 93 hres = IStorage_OpenStream(chm->pStorage, wszSYSTEM, NULL, STGM_READ, 0, &stream); 94 if(FAILED(hres)) { 95 WARN("Could not open #SYSTEM stream: %08x\n", hres); 96 return FALSE; 97 } 98 99 IStream_Read(stream, &ver, sizeof(ver), &read); 100 TRACE("version is %x\n", ver); 101 102 buf = heap_alloc(8*sizeof(DWORD)); 103 buf_size = 8*sizeof(DWORD); 104 105 while(1) { 106 hres = IStream_Read(stream, &entry, sizeof(entry), &read); 107 if(hres != S_OK) 108 break; 109 110 if(entry.len > buf_size) 111 buf = heap_realloc(buf, buf_size=entry.len); 112 113 hres = IStream_Read(stream, buf, entry.len, &read); 114 if(hres != S_OK) 115 break; 116 117 switch(entry.code) { 118 case 0x0: 119 TRACE("TOC is %s\n", debugstr_an(buf, entry.len)); 120 heap_free(chm->defToc); 121 chm->defToc = strdupnAtoW(buf, entry.len); 122 break; 123 case 0x2: 124 TRACE("Default topic is %s\n", debugstr_an(buf, entry.len)); 125 heap_free(chm->defTopic); 126 chm->defTopic = strdupnAtoW(buf, entry.len); 127 break; 128 case 0x3: 129 TRACE("Title is %s\n", debugstr_an(buf, entry.len)); 130 heap_free(chm->defTitle); 131 chm->defTitle = strdupnAtoW(buf, entry.len); 132 break; 133 case 0x4: 134 /* TODO: Currently only the Locale ID is loaded from this field */ 135 TRACE("Locale is: %d\n", *(LCID*)&buf[0]); 136 if(!GetLocaleInfoW(*(LCID*)&buf[0], LOCALE_IDEFAULTANSICODEPAGE|LOCALE_RETURN_NUMBER, 137 (WCHAR *)&chm->codePage, sizeof(chm->codePage)/sizeof(WCHAR))) 138 chm->codePage = CP_ACP; 139 break; 140 case 0x5: 141 TRACE("Window name is %s\n", debugstr_an(buf, entry.len)); 142 chm->defWindow = strdupnAtoW(buf, entry.len); 143 break; 144 case 0x6: 145 TRACE("Compiled file is %s\n", debugstr_an(buf, entry.len)); 146 heap_free(chm->compiledFile); 147 chm->compiledFile = strdupnAtoW(buf, entry.len); 148 break; 149 case 0x9: 150 TRACE("Version is %s\n", debugstr_an(buf, entry.len)); 151 break; 152 case 0xa: 153 TRACE("Time is %08x\n", *(DWORD*)buf); 154 break; 155 case 0xc: 156 TRACE("Number of info types: %d\n", *(DWORD*)buf); 157 break; 158 case 0xf: 159 TRACE("Check sum: %x\n", *(DWORD*)buf); 160 break; 161 default: 162 TRACE("unhandled code %x, size %x\n", entry.code, entry.len); 163 } 164 } 165 166 heap_free(buf); 167 IStream_Release(stream); 168 169 return SUCCEEDED(hres); 170 } 171 172 LPWSTR FindContextAlias(CHMInfo *chm, DWORD index) 173 { 174 IStream *ivb_stream; 175 DWORD size, read, i; 176 DWORD *buf; 177 LPCSTR ret = NULL; 178 HRESULT hres; 179 180 static const WCHAR wszIVB[] = {'#','I','V','B',0}; 181 182 hres = IStorage_OpenStream(chm->pStorage, wszIVB, NULL, STGM_READ, 0, &ivb_stream); 183 if(FAILED(hres)) { 184 WARN("Could not open #IVB stream: %08x\n", hres); 185 return NULL; 186 } 187 188 hres = IStream_Read(ivb_stream, &size, sizeof(size), &read); 189 if(FAILED(hres)) { 190 WARN("Read failed: %08x\n", hres); 191 IStream_Release(ivb_stream); 192 return NULL; 193 } 194 195 buf = heap_alloc(size); 196 hres = IStream_Read(ivb_stream, buf, size, &read); 197 IStream_Release(ivb_stream); 198 if(FAILED(hres)) { 199 WARN("Read failed: %08x\n", hres); 200 heap_free(buf); 201 return NULL; 202 } 203 204 size /= 2*sizeof(DWORD); 205 206 for(i=0; i<size; i++) { 207 if(buf[2*i] == index) { 208 ret = GetChmString(chm, buf[2*i+1]); 209 break; 210 } 211 } 212 213 heap_free(buf); 214 215 TRACE("returning %s\n", debugstr_a(ret)); 216 return strdupAtoW(ret); 217 } 218 219 /* 220 * Tests if the file <chmfile>.<ext> exists, used for loading Indices, Table of Contents, etc. 221 * when these files are not available from the HH_WINTYPE structure. 222 */ 223 static WCHAR *FindHTMLHelpSetting(HHInfo *info, const WCHAR *extW) 224 { 225 static const WCHAR periodW[] = {'.',0}; 226 IStorage *pStorage = info->pCHMInfo->pStorage; 227 IStream *pStream; 228 WCHAR *filename; 229 HRESULT hr; 230 231 filename = heap_alloc( (lstrlenW(info->pCHMInfo->compiledFile) 232 + lstrlenW(periodW) + lstrlenW(extW) + 1) * sizeof(WCHAR) ); 233 lstrcpyW(filename, info->pCHMInfo->compiledFile); 234 lstrcatW(filename, periodW); 235 lstrcatW(filename, extW); 236 hr = IStorage_OpenStream(pStorage, filename, NULL, STGM_READ, 0, &pStream); 237 if (FAILED(hr)) 238 { 239 heap_free(filename); 240 return strdupAtoW(""); 241 } 242 IStream_Release(pStream); 243 return filename; 244 } 245 246 static inline WCHAR *MergeChmString(LPCWSTR src, WCHAR **dst) 247 { 248 if(*dst == NULL) 249 *dst = strdupW(src); 250 return *dst; 251 } 252 253 void MergeChmProperties(HH_WINTYPEW *src, HHInfo *info, BOOL override) 254 { 255 DWORD unhandled_params = src->fsValidMembers & ~(HHWIN_PARAM_PROPERTIES|HHWIN_PARAM_STYLES 256 |HHWIN_PARAM_EXSTYLES|HHWIN_PARAM_RECT|HHWIN_PARAM_NAV_WIDTH 257 |HHWIN_PARAM_SHOWSTATE|HHWIN_PARAM_INFOTYPES|HHWIN_PARAM_TB_FLAGS 258 |HHWIN_PARAM_EXPANSION|HHWIN_PARAM_TABPOS|HHWIN_PARAM_TABORDER 259 |HHWIN_PARAM_HISTORY_COUNT|HHWIN_PARAM_CUR_TAB); 260 HH_WINTYPEW *dst = &info->WinType; 261 DWORD merge = override ? src->fsValidMembers : src->fsValidMembers & ~dst->fsValidMembers; 262 263 if (unhandled_params) 264 FIXME("Unsupported fsValidMembers fields: 0x%x\n", unhandled_params); 265 266 dst->fsValidMembers |= merge; 267 if (dst->cbStruct == 0) 268 { 269 /* If the structure has not been filled in yet then use all of the values */ 270 dst->cbStruct = sizeof(HH_WINTYPEW); 271 merge = ~0; 272 } 273 if (merge & HHWIN_PARAM_PROPERTIES) dst->fsWinProperties = src->fsWinProperties; 274 if (merge & HHWIN_PARAM_STYLES) dst->dwStyles = src->dwStyles; 275 if (merge & HHWIN_PARAM_EXSTYLES) dst->dwExStyles = src->dwExStyles; 276 if (merge & HHWIN_PARAM_RECT) dst->rcWindowPos = src->rcWindowPos; 277 if (merge & HHWIN_PARAM_NAV_WIDTH) dst->iNavWidth = src->iNavWidth; 278 if (merge & HHWIN_PARAM_SHOWSTATE) dst->nShowState = src->nShowState; 279 if (merge & HHWIN_PARAM_INFOTYPES) dst->paInfoTypes = src->paInfoTypes; 280 if (merge & HHWIN_PARAM_TB_FLAGS) dst->fsToolBarFlags = src->fsToolBarFlags; 281 if (merge & HHWIN_PARAM_EXPANSION) dst->fNotExpanded = src->fNotExpanded; 282 if (merge & HHWIN_PARAM_TABPOS) dst->tabpos = src->tabpos; 283 if (merge & HHWIN_PARAM_TABORDER) memcpy(dst->tabOrder, src->tabOrder, sizeof(src->tabOrder)); 284 if (merge & HHWIN_PARAM_HISTORY_COUNT) dst->cHistory = src->cHistory; 285 if (merge & HHWIN_PARAM_CUR_TAB) dst->curNavType = src->curNavType; 286 287 /* 288 * Note: We assume that hwndHelp, hwndCaller, hwndToolBar, hwndNavigation, and hwndHTML cannot be 289 * modified by the user. rcHTML and rcMinSize are not currently supported, so don't bother to copy them. 290 */ 291 292 dst->pszType = MergeChmString(src->pszType, &info->stringsW.pszType); 293 dst->pszFile = MergeChmString(src->pszFile, &info->stringsW.pszFile); 294 dst->pszToc = MergeChmString(src->pszToc, &info->stringsW.pszToc); 295 dst->pszIndex = MergeChmString(src->pszIndex, &info->stringsW.pszIndex); 296 dst->pszCaption = MergeChmString(src->pszCaption, &info->stringsW.pszCaption); 297 dst->pszHome = MergeChmString(src->pszHome, &info->stringsW.pszHome); 298 dst->pszJump1 = MergeChmString(src->pszJump1, &info->stringsW.pszJump1); 299 dst->pszJump2 = MergeChmString(src->pszJump2, &info->stringsW.pszJump2); 300 dst->pszUrlJump1 = MergeChmString(src->pszUrlJump1, &info->stringsW.pszUrlJump1); 301 dst->pszUrlJump2 = MergeChmString(src->pszUrlJump2, &info->stringsW.pszUrlJump2); 302 303 /* FIXME: pszCustomTabs is a list of multiple zero-terminated strings so ReadString won't 304 * work in this case 305 */ 306 #if 0 307 dst->pszCustomTabs = MergeChmString(src->pszCustomTabs, &info->pszCustomTabs); 308 #endif 309 } 310 311 static inline WCHAR *ConvertChmString(HHInfo *info, DWORD id) 312 { 313 WCHAR *ret = NULL; 314 315 if(id) 316 ret = strdupAtoW(GetChmString(info->pCHMInfo, id)); 317 return ret; 318 } 319 320 static inline void wintype_free(HH_WINTYPEW *wintype) 321 { 322 heap_free((void *)wintype->pszType); 323 heap_free((void *)wintype->pszCaption); 324 heap_free(wintype->paInfoTypes); 325 heap_free((void *)wintype->pszToc); 326 heap_free((void *)wintype->pszIndex); 327 heap_free((void *)wintype->pszFile); 328 heap_free((void *)wintype->pszHome); 329 heap_free((void *)wintype->pszJump1); 330 heap_free((void *)wintype->pszJump2); 331 heap_free((void *)wintype->pszUrlJump1); 332 heap_free((void *)wintype->pszUrlJump2); 333 heap_free((void *)wintype->pszCustomTabs); 334 } 335 336 /* Loads the HH_WINTYPE data from the CHM file 337 * 338 * FIXME: There may be more than one window type in the file, so 339 * add the ability to choose a certain window type 340 */ 341 BOOL LoadWinTypeFromCHM(HHInfo *info) 342 { 343 LARGE_INTEGER liOffset; 344 IStorage *pStorage = info->pCHMInfo->pStorage; 345 IStream *pStream = NULL; 346 HH_WINTYPEW wintype; 347 HRESULT hr; 348 DWORD cbRead; 349 BOOL ret = FALSE; 350 351 static const WCHAR empty[] = {0}; 352 static const WCHAR toc_extW[] = {'h','h','c',0}; 353 static const WCHAR index_extW[] = {'h','h','k',0}; 354 static const WCHAR windowsW[] = {'#','W','I','N','D','O','W','S',0}; 355 356 /* HH_WINTYPE as stored on disk. It's identical to HH_WINTYPE except that the pointer fields 357 have been changed to DWORDs, so that the layout on 64-bit remains unchanged. */ 358 struct file_wintype 359 { 360 int cbStruct; 361 BOOL fUniCodeStrings; 362 DWORD pszType; 363 DWORD fsValidMembers; 364 DWORD fsWinProperties; 365 DWORD pszCaption; 366 DWORD dwStyles; 367 DWORD dwExStyles; 368 RECT rcWindowPos; 369 int nShowState; 370 DWORD hwndHelp; 371 DWORD hwndCaller; 372 DWORD paInfoTypes; 373 DWORD hwndToolBar; 374 DWORD hwndNavigation; 375 DWORD hwndHTML; 376 int iNavWidth; 377 RECT rcHTML; 378 DWORD pszToc; 379 DWORD pszIndex; 380 DWORD pszFile; 381 DWORD pszHome; 382 DWORD fsToolBarFlags; 383 BOOL fNotExpanded; 384 int curNavType; 385 int tabpos; 386 int idNotify; 387 BYTE tabOrder[HH_MAX_TABS+1]; 388 int cHistory; 389 DWORD pszJump1; 390 DWORD pszJump2; 391 DWORD pszUrlJump1; 392 DWORD pszUrlJump2; 393 RECT rcMinSize; 394 int cbInfoTypes; 395 DWORD pszCustomTabs; 396 } file_wintype; 397 398 memset(&wintype, 0, sizeof(wintype)); 399 wintype.cbStruct = sizeof(wintype); 400 wintype.fUniCodeStrings = TRUE; 401 402 hr = IStorage_OpenStream(pStorage, windowsW, NULL, STGM_READ, 0, &pStream); 403 if (SUCCEEDED(hr)) 404 { 405 /* jump past the #WINDOWS header */ 406 liOffset.QuadPart = sizeof(DWORD) * 2; 407 408 hr = IStream_Seek(pStream, liOffset, STREAM_SEEK_SET, NULL); 409 if (FAILED(hr)) goto done; 410 411 /* read the HH_WINTYPE struct data */ 412 hr = IStream_Read(pStream, &file_wintype, sizeof(file_wintype), &cbRead); 413 if (FAILED(hr)) goto done; 414 415 /* convert the #STRINGS offsets to actual strings */ 416 wintype.pszType = ConvertChmString(info, file_wintype.pszType); 417 wintype.fsValidMembers = file_wintype.fsValidMembers; 418 wintype.fsWinProperties = file_wintype.fsWinProperties; 419 wintype.pszCaption = ConvertChmString(info, file_wintype.pszCaption); 420 wintype.dwStyles = file_wintype.dwStyles; 421 wintype.dwExStyles = file_wintype.dwExStyles; 422 wintype.rcWindowPos = file_wintype.rcWindowPos; 423 wintype.nShowState = file_wintype.nShowState; 424 wintype.iNavWidth = file_wintype.iNavWidth; 425 wintype.rcHTML = file_wintype.rcHTML; 426 wintype.pszToc = ConvertChmString(info, file_wintype.pszToc); 427 wintype.pszIndex = ConvertChmString(info, file_wintype.pszIndex); 428 wintype.pszFile = ConvertChmString(info, file_wintype.pszFile); 429 wintype.pszHome = ConvertChmString(info, file_wintype.pszHome); 430 wintype.fsToolBarFlags = file_wintype.fsToolBarFlags; 431 wintype.fNotExpanded = file_wintype.fNotExpanded; 432 wintype.curNavType = file_wintype.curNavType; 433 wintype.tabpos = file_wintype.tabpos; 434 wintype.idNotify = file_wintype.idNotify; 435 memcpy(&wintype.tabOrder, file_wintype.tabOrder, sizeof(wintype.tabOrder)); 436 wintype.cHistory = file_wintype.cHistory; 437 wintype.pszJump1 = ConvertChmString(info, file_wintype.pszJump1); 438 wintype.pszJump2 = ConvertChmString(info, file_wintype.pszJump2); 439 wintype.pszUrlJump1 = ConvertChmString(info, file_wintype.pszUrlJump1); 440 wintype.pszUrlJump2 = ConvertChmString(info, file_wintype.pszUrlJump2); 441 wintype.rcMinSize = file_wintype.rcMinSize; 442 wintype.cbInfoTypes = file_wintype.cbInfoTypes; 443 } 444 else 445 { 446 /* no defined window types so use (hopefully) sane defaults */ 447 static const WCHAR defaultwinW[] = {'d','e','f','a','u','l','t','w','i','n','\0'}; 448 wintype.pszType = strdupW(info->pCHMInfo->defWindow ? info->pCHMInfo->defWindow : defaultwinW); 449 wintype.pszToc = strdupW(info->pCHMInfo->defToc ? info->pCHMInfo->defToc : empty); 450 wintype.pszIndex = strdupW(empty); 451 wintype.fsValidMembers = 0; 452 wintype.fsWinProperties = HHWIN_PROP_TRI_PANE; 453 wintype.dwStyles = WS_POPUP; 454 wintype.dwExStyles = 0; 455 wintype.nShowState = SW_SHOW; 456 wintype.curNavType = HHWIN_NAVTYPE_TOC; 457 } 458 459 /* merge the new data with any pre-existing HH_WINTYPE structure */ 460 MergeChmProperties(&wintype, info, FALSE); 461 if (!info->WinType.pszCaption) 462 info->WinType.pszCaption = info->stringsW.pszCaption = strdupW(info->pCHMInfo->defTitle ? info->pCHMInfo->defTitle : empty); 463 if (!info->WinType.pszFile) 464 info->WinType.pszFile = info->stringsW.pszFile = strdupW(info->pCHMInfo->defTopic ? info->pCHMInfo->defTopic : empty); 465 if (!info->WinType.pszToc) 466 info->WinType.pszToc = info->stringsW.pszToc = FindHTMLHelpSetting(info, toc_extW); 467 if (!info->WinType.pszIndex) 468 info->WinType.pszIndex = info->stringsW.pszIndex = FindHTMLHelpSetting(info, index_extW); 469 470 wintype_free(&wintype); 471 ret = TRUE; 472 473 done: 474 if (pStream) 475 IStream_Release(pStream); 476 477 return ret; 478 } 479 480 LPCWSTR skip_schema(LPCWSTR url) 481 { 482 static const WCHAR its_schema[] = {'i','t','s',':'}; 483 static const WCHAR msits_schema[] = {'m','s','-','i','t','s',':'}; 484 static const WCHAR mk_schema[] = {'m','k',':','@','M','S','I','T','S','t','o','r','e',':'}; 485 486 if(!_wcsnicmp(its_schema, url, ARRAY_SIZE(its_schema))) 487 return url + ARRAY_SIZE(its_schema); 488 if(!_wcsnicmp(msits_schema, url, ARRAY_SIZE(msits_schema))) 489 return url + ARRAY_SIZE(msits_schema); 490 if(!_wcsnicmp(mk_schema, url, ARRAY_SIZE(mk_schema))) 491 return url + ARRAY_SIZE(mk_schema); 492 493 return url; 494 } 495 496 void SetChmPath(ChmPath *file, LPCWSTR base_file, LPCWSTR path) 497 { 498 LPCWSTR ptr; 499 static const WCHAR separatorW[] = {':',':',0}; 500 501 path = skip_schema(path); 502 503 ptr = wcsstr(path, separatorW); 504 if(ptr) { 505 WCHAR chm_file[MAX_PATH]; 506 WCHAR rel_path[MAX_PATH]; 507 WCHAR base_path[MAX_PATH]; 508 LPWSTR p; 509 510 lstrcpyW(base_path, base_file); 511 p = wcsrchr(base_path, '\\'); 512 if(p) 513 *p = 0; 514 515 memcpy(rel_path, path, (ptr-path)*sizeof(WCHAR)); 516 rel_path[ptr-path] = 0; 517 518 PathCombineW(chm_file, base_path, rel_path); 519 520 file->chm_file = strdupW(chm_file); 521 ptr += 2; 522 }else { 523 file->chm_file = strdupW(base_file); 524 ptr = path; 525 } 526 527 file->chm_index = strdupW(ptr); 528 529 TRACE("ChmFile = {%s %s}\n", debugstr_w(file->chm_file), debugstr_w(file->chm_index)); 530 } 531 532 IStream *GetChmStream(CHMInfo *info, LPCWSTR parent_chm, ChmPath *chm_file) 533 { 534 IStorage *storage; 535 IStream *stream = NULL; 536 HRESULT hres; 537 538 TRACE("%s (%s :: %s)\n", debugstr_w(parent_chm), debugstr_w(chm_file->chm_file), 539 debugstr_w(chm_file->chm_index)); 540 541 if(parent_chm || chm_file->chm_file) { 542 hres = IITStorage_StgOpenStorage(info->pITStorage, 543 chm_file->chm_file ? chm_file->chm_file : parent_chm, NULL, 544 STGM_READ | STGM_SHARE_DENY_WRITE, NULL, 0, &storage); 545 if(FAILED(hres)) { 546 WARN("Could not open storage: %08x\n", hres); 547 return NULL; 548 } 549 }else { 550 storage = info->pStorage; 551 IStorage_AddRef(info->pStorage); 552 } 553 554 hres = IStorage_OpenStream(storage, chm_file->chm_index, NULL, STGM_READ, 0, &stream); 555 IStorage_Release(storage); 556 if(FAILED(hres)) 557 WARN("Could not open stream: %08x\n", hres); 558 559 return stream; 560 } 561 562 /* 563 * Retrieve a CHM document and parse the data from the <title> element to get the document's title. 564 */ 565 WCHAR *GetDocumentTitle(CHMInfo *info, LPCWSTR document) 566 { 567 strbuf_t node, node_name, content; 568 WCHAR *document_title = NULL; 569 IStream *str = NULL; 570 IStorage *storage; 571 stream_t stream; 572 HRESULT hres; 573 574 TRACE("%s\n", debugstr_w(document)); 575 576 storage = info->pStorage; 577 if(!storage) { 578 WARN("Could not open storage to obtain the title for a document.\n"); 579 return NULL; 580 } 581 IStorage_AddRef(storage); 582 583 hres = IStorage_OpenStream(storage, document, NULL, STGM_READ, 0, &str); 584 IStorage_Release(storage); 585 if(FAILED(hres)) 586 WARN("Could not open stream: %08x\n", hres); 587 588 stream_init(&stream, str); 589 strbuf_init(&node); 590 strbuf_init(&content); 591 strbuf_init(&node_name); 592 593 while(next_node(&stream, &node)) { 594 get_node_name(&node, &node_name); 595 596 TRACE("%s\n", node.buf); 597 598 if(!_strnicmp(node_name.buf, "title", -1)) { 599 if(next_content(&stream, &content) && content.len > 1) 600 { 601 document_title = strdupnAtoW(&content.buf[1], content.len-1); 602 FIXME("magic: %s\n", debugstr_w(document_title)); 603 break; 604 } 605 } 606 607 strbuf_zero(&node); 608 } 609 610 strbuf_free(&node); 611 strbuf_free(&content); 612 strbuf_free(&node_name); 613 IStream_Release(str); 614 615 return document_title; 616 } 617 618 /* Opens the CHM file for reading */ 619 CHMInfo *OpenCHM(LPCWSTR szFile) 620 { 621 HRESULT hres; 622 CHMInfo *ret; 623 624 static const WCHAR wszSTRINGS[] = {'#','S','T','R','I','N','G','S',0}; 625 626 if (!(ret = heap_alloc_zero(sizeof(CHMInfo)))) 627 return NULL; 628 ret->codePage = CP_ACP; 629 630 if (!(ret->szFile = strdupW(szFile))) { 631 heap_free(ret); 632 return NULL; 633 } 634 635 hres = CoCreateInstance(&CLSID_ITStorage, NULL, CLSCTX_INPROC_SERVER, 636 &IID_IITStorage, (void **) &ret->pITStorage) ; 637 if(FAILED(hres)) { 638 WARN("Could not create ITStorage: %08x\n", hres); 639 return CloseCHM(ret); 640 } 641 642 hres = IITStorage_StgOpenStorage(ret->pITStorage, szFile, NULL, 643 STGM_READ | STGM_SHARE_DENY_WRITE, NULL, 0, &ret->pStorage); 644 if(FAILED(hres)) { 645 WARN("Could not open storage: %08x\n", hres); 646 return CloseCHM(ret); 647 } 648 hres = IStorage_OpenStream(ret->pStorage, wszSTRINGS, NULL, STGM_READ, 0, 649 &ret->strings_stream); 650 if(FAILED(hres)) { 651 WARN("Could not open #STRINGS stream: %08x\n", hres); 652 /* It's not critical, so we pass */ 653 } 654 655 if(!ReadChmSystem(ret)) { 656 WARN("Could not read #SYSTEM\n"); 657 return CloseCHM(ret); 658 } 659 660 return ret; 661 } 662 663 CHMInfo *CloseCHM(CHMInfo *chm) 664 { 665 if(chm->pITStorage) 666 IITStorage_Release(chm->pITStorage); 667 668 if(chm->pStorage) 669 IStorage_Release(chm->pStorage); 670 671 if(chm->strings_stream) 672 IStream_Release(chm->strings_stream); 673 674 if(chm->strings_size) { 675 DWORD i; 676 677 for(i=0; i<chm->strings_size; i++) 678 heap_free(chm->strings[i]); 679 } 680 681 heap_free(chm->strings); 682 heap_free(chm->defWindow); 683 heap_free(chm->defTitle); 684 heap_free(chm->defTopic); 685 heap_free(chm->defToc); 686 heap_free(chm->szFile); 687 heap_free(chm->compiledFile); 688 heap_free(chm); 689 690 return NULL; 691 } 692