| /* | |
| * CHM Utility API | |
| * | |
| * Copyright 2005 James Hawkins | |
| * Copyright 2007 Jacek Caban | |
| * | |
| * This library is free software; you can redistribute it and/or | |
| * modify it under the terms of the GNU Lesser General Public | |
| * License as published by the Free Software Foundation; either | |
| * version 2.1 of the License, or (at your option) any later version. | |
| * | |
| * This library is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| * Lesser General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU Lesser General Public | |
| * License along with this library; if not, write to the Free Software | |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA | |
| */ | |
| #include "hhctrl.h" | |
| #include "winreg.h" | |
| #include "shlwapi.h" | |
| #include "wine/debug.h" | |
| WINE_DEFAULT_DEBUG_CHANNEL(htmlhelp); | |
| #define BLOCK_BITS 12 | |
| #define BLOCK_SIZE (1 << BLOCK_BITS) | |
| #define BLOCK_MASK (BLOCK_SIZE-1) | |
| /* Reads a string from the #STRINGS section in the CHM file */ | |
| static LPCSTR GetChmString(CHMInfo *chm, DWORD offset) | |
| { | |
| if(!chm->strings_stream) | |
| return NULL; | |
| if(chm->strings_size <= (offset >> BLOCK_BITS)) { | |
| chm->strings_size = (offset >> BLOCK_BITS)+1; | |
| if(chm->strings) | |
| chm->strings = heap_realloc_zero(chm->strings, | |
| chm->strings_size*sizeof(char*)); | |
| else | |
| chm->strings = heap_alloc_zero( | |
| chm->strings_size*sizeof(char*)); | |
| } | |
| if(!chm->strings[offset >> BLOCK_BITS]) { | |
| LARGE_INTEGER pos; | |
| DWORD read; | |
| HRESULT hres; | |
| pos.QuadPart = offset & ~BLOCK_MASK; | |
| hres = IStream_Seek(chm->strings_stream, pos, STREAM_SEEK_SET, NULL); | |
| if(FAILED(hres)) { | |
| WARN("Seek failed: %08x\n", hres); | |
| return NULL; | |
| } | |
| chm->strings[offset >> BLOCK_BITS] = heap_alloc(BLOCK_SIZE); | |
| hres = IStream_Read(chm->strings_stream, chm->strings[offset >> BLOCK_BITS], | |
| BLOCK_SIZE, &read); | |
| if(FAILED(hres)) { | |
| WARN("Read failed: %08x\n", hres); | |
| heap_free(chm->strings[offset >> BLOCK_BITS]); | |
| chm->strings[offset >> BLOCK_BITS] = NULL; | |
| return NULL; | |
| } | |
| } | |
| return chm->strings[offset >> BLOCK_BITS] + (offset & BLOCK_MASK); | |
| } | |
| static BOOL ReadChmSystem(CHMInfo *chm) | |
| { | |
| IStream *stream; | |
| DWORD ver=0xdeadbeef, read, buf_size; | |
| char *buf; | |
| HRESULT hres; | |
| struct { | |
| WORD code; | |
| WORD len; | |
| } entry; | |
| static const WCHAR wszSYSTEM[] = {'#','S','Y','S','T','E','M',0}; | |
| hres = IStorage_OpenStream(chm->pStorage, wszSYSTEM, NULL, STGM_READ, 0, &stream); | |
| if(FAILED(hres)) { | |
| WARN("Could not open #SYSTEM stream: %08x\n", hres); | |
| return FALSE; | |
| } | |
| IStream_Read(stream, &ver, sizeof(ver), &read); | |
| TRACE("version is %x\n", ver); | |
| buf = heap_alloc(8*sizeof(DWORD)); | |
| buf_size = 8*sizeof(DWORD); | |
| while(1) { | |
| hres = IStream_Read(stream, &entry, sizeof(entry), &read); | |
| if(hres != S_OK) | |
| break; | |
| if(entry.len > buf_size) | |
| buf = heap_realloc(buf, buf_size=entry.len); | |
| hres = IStream_Read(stream, buf, entry.len, &read); | |
| if(hres != S_OK) | |
| break; | |
| switch(entry.code) { | |
| case 0x0: | |
| TRACE("TOC is %s\n", debugstr_an(buf, entry.len)); | |
| heap_free(chm->defToc); | |
| chm->defToc = strdupnAtoW(buf, entry.len); | |
| break; | |
| case 0x2: | |
| TRACE("Default topic is %s\n", debugstr_an(buf, entry.len)); | |
| heap_free(chm->defTopic); | |
| chm->defTopic = strdupnAtoW(buf, entry.len); | |
| break; | |
| case 0x3: | |
| TRACE("Title is %s\n", debugstr_an(buf, entry.len)); | |
| heap_free(chm->defTitle); | |
| chm->defTitle = strdupnAtoW(buf, entry.len); | |
| break; | |
| case 0x5: | |
| TRACE("Default window is %s\n", debugstr_an(buf, entry.len)); | |
| break; | |
| case 0x6: | |
| TRACE("Compiled file is %s\n", debugstr_an(buf, entry.len)); | |
| break; | |
| case 0x9: | |
| TRACE("Version is %s\n", debugstr_an(buf, entry.len)); | |
| break; | |
| case 0xa: | |
| TRACE("Time is %08x\n", *(DWORD*)buf); | |
| break; | |
| case 0xc: | |
| TRACE("Number of info types: %d\n", *(DWORD*)buf); | |
| break; | |
| case 0xf: | |
| TRACE("Check sum: %x\n", *(DWORD*)buf); | |
| break; | |
| default: | |
| TRACE("unhandled code %x, size %x\n", entry.code, entry.len); | |
| } | |
| } | |
| heap_free(buf); | |
| IStream_Release(stream); | |
| return SUCCEEDED(hres); | |
| } | |
| LPWSTR FindContextAlias(CHMInfo *chm, DWORD index) | |
| { | |
| IStream *ivb_stream; | |
| DWORD size, read, i; | |
| DWORD *buf; | |
| LPCSTR ret = NULL; | |
| HRESULT hres; | |
| static const WCHAR wszIVB[] = {'#','I','V','B',0}; | |
| hres = IStorage_OpenStream(chm->pStorage, wszIVB, NULL, STGM_READ, 0, &ivb_stream); | |
| if(FAILED(hres)) { | |
| WARN("Could not open #IVB stream: %08x\n", hres); | |
| return NULL; | |
| } | |
| hres = IStream_Read(ivb_stream, &size, sizeof(size), &read); | |
| if(FAILED(hres)) { | |
| WARN("Read failed: %08x\n", hres); | |
| IStream_Release(ivb_stream); | |
| return NULL; | |
| } | |
| buf = heap_alloc(size); | |
| hres = IStream_Read(ivb_stream, buf, size, &read); | |
| IStream_Release(ivb_stream); | |
| if(FAILED(hres)) { | |
| WARN("Read failed: %08x\n", hres); | |
| heap_free(buf); | |
| return NULL; | |
| } | |
| size /= 2*sizeof(DWORD); | |
| for(i=0; i<size; i++) { | |
| if(buf[2*i] == index) { | |
| ret = GetChmString(chm, buf[2*i+1]); | |
| break; | |
| } | |
| } | |
| heap_free(buf); | |
| TRACE("returning %s\n", debugstr_a(ret)); | |
| return strdupAtoW(ret); | |
| } | |
| /* Loads the HH_WINTYPE data from the CHM file | |
| * | |
| * FIXME: There may be more than one window type in the file, so | |
| * add the ability to choose a certain window type | |
| */ | |
| BOOL LoadWinTypeFromCHM(HHInfo *info) | |
| { | |
| LARGE_INTEGER liOffset; | |
| IStorage *pStorage = info->pCHMInfo->pStorage; | |
| IStream *pStream; | |
| HRESULT hr; | |
| DWORD cbRead; | |
| static const WCHAR windowsW[] = {'#','W','I','N','D','O','W','S',0}; | |
| hr = IStorage_OpenStream(pStorage, windowsW, NULL, STGM_READ, 0, &pStream); | |
| if (FAILED(hr)) | |
| { | |
| /* no defined window types so use (hopefully) sane defaults */ | |
| static const WCHAR defaultwinW[] = {'d','e','f','a','u','l','t','w','i','n','\0'}; | |
| static const WCHAR null[] = {0}; | |
| memset((void*)&(info->WinType), 0, sizeof(info->WinType)); | |
| info->WinType.cbStruct=sizeof(info->WinType); | |
| info->WinType.fUniCodeStrings=TRUE; | |
| info->WinType.pszType=strdupW(defaultwinW); | |
| info->WinType.pszToc = strdupW(info->pCHMInfo->defToc ? info->pCHMInfo->defToc : null); | |
| info->WinType.pszIndex = strdupW(null); | |
| info->WinType.fsValidMembers=0; | |
| info->WinType.fsWinProperties=HHWIN_PROP_TRI_PANE; | |
| info->WinType.pszCaption=strdupW(info->pCHMInfo->defTitle); | |
| info->WinType.dwStyles=WS_POPUP; | |
| info->WinType.dwExStyles=0; | |
| info->WinType.nShowState=SW_SHOW; | |
| info->WinType.pszFile=strdupW(info->pCHMInfo->defTopic); | |
| info->WinType.curNavType=HHWIN_NAVTYPE_TOC; | |
| return TRUE; | |
| } | |
| /* jump past the #WINDOWS header */ | |
| liOffset.QuadPart = sizeof(DWORD) * 2; | |
| hr = IStream_Seek(pStream, liOffset, STREAM_SEEK_SET, NULL); | |
| if (FAILED(hr)) goto done; | |
| /* read the HH_WINTYPE struct data */ | |
| hr = IStream_Read(pStream, &info->WinType, sizeof(info->WinType), &cbRead); | |
| if (FAILED(hr)) goto done; | |
| /* convert the #STRINGS offsets to actual strings */ | |
| info->WinType.pszType = info->pszType = strdupAtoW(GetChmString(info->pCHMInfo, (DWORD_PTR)info->WinType.pszType)); | |
| info->WinType.pszCaption = info->pszCaption = strdupAtoW(GetChmString(info->pCHMInfo, (DWORD_PTR)info->WinType.pszCaption)); | |
| info->WinType.pszToc = info->pszToc = strdupAtoW(GetChmString(info->pCHMInfo, (DWORD_PTR)info->WinType.pszToc)); | |
| info->WinType.pszIndex = info->pszIndex = strdupAtoW(GetChmString(info->pCHMInfo, (DWORD_PTR)info->WinType.pszIndex)); | |
| info->WinType.pszFile = info->pszFile = strdupAtoW(GetChmString(info->pCHMInfo, (DWORD_PTR)info->WinType.pszFile)); | |
| info->WinType.pszHome = info->pszHome = strdupAtoW(GetChmString(info->pCHMInfo, (DWORD_PTR)info->WinType.pszHome)); | |
| info->WinType.pszJump1 = info->pszJump1 = strdupAtoW(GetChmString(info->pCHMInfo, (DWORD_PTR)info->WinType.pszJump1)); | |
| info->WinType.pszJump2 = info->pszJump2 = strdupAtoW(GetChmString(info->pCHMInfo, (DWORD_PTR)info->WinType.pszJump2)); | |
| info->WinType.pszUrlJump1 = info->pszUrlJump1 = strdupAtoW(GetChmString(info->pCHMInfo, (DWORD_PTR)info->WinType.pszUrlJump1)); | |
| info->WinType.pszUrlJump2 = info->pszUrlJump2 = strdupAtoW(GetChmString(info->pCHMInfo, (DWORD_PTR)info->WinType.pszUrlJump2)); | |
| /* FIXME: pszCustomTabs is a list of multiple zero-terminated strings so ReadString won't | |
| * work in this case | |
| */ | |
| #if 0 | |
| info->WinType.pszCustomTabs = info->pszCustomTabs = CHM_ReadString(pChmInfo, (DWORD_PTR)info->WinType.pszCustomTabs); | |
| #endif | |
| done: | |
| IStream_Release(pStream); | |
| return SUCCEEDED(hr); | |
| } | |
| static LPCWSTR skip_schema(LPCWSTR url) | |
| { | |
| static const WCHAR its_schema[] = {'i','t','s',':'}; | |
| static const WCHAR msits_schema[] = {'m','s','-','i','t','s',':'}; | |
| static const WCHAR mk_schema[] = {'m','k',':','@','M','S','I','T','S','t','o','r','e',':'}; | |
| if(!strncmpiW(its_schema, url, sizeof(its_schema)/sizeof(WCHAR))) | |
| return url+sizeof(its_schema)/sizeof(WCHAR); | |
| if(!strncmpiW(msits_schema, url, sizeof(msits_schema)/sizeof(WCHAR))) | |
| return url+sizeof(msits_schema)/sizeof(WCHAR); | |
| if(!strncmpiW(mk_schema, url, sizeof(mk_schema)/sizeof(WCHAR))) | |
| return url+sizeof(mk_schema)/sizeof(WCHAR); | |
| return url; | |
| } | |
| void SetChmPath(ChmPath *file, LPCWSTR base_file, LPCWSTR path) | |
| { | |
| LPCWSTR ptr; | |
| static const WCHAR separatorW[] = {':',':',0}; | |
| path = skip_schema(path); | |
| ptr = strstrW(path, separatorW); | |
| if(ptr) { | |
| WCHAR chm_file[MAX_PATH]; | |
| WCHAR rel_path[MAX_PATH]; | |
| WCHAR base_path[MAX_PATH]; | |
| LPWSTR p; | |
| strcpyW(base_path, base_file); | |
| p = strrchrW(base_path, '\\'); | |
| if(p) | |
| *p = 0; | |
| memcpy(rel_path, path, (ptr-path)*sizeof(WCHAR)); | |
| rel_path[ptr-path] = 0; | |
| PathCombineW(chm_file, base_path, rel_path); | |
| file->chm_file = strdupW(chm_file); | |
| ptr += 2; | |
| }else { | |
| file->chm_file = strdupW(base_file); | |
| ptr = path; | |
| } | |
| file->chm_index = strdupW(ptr); | |
| TRACE("ChmFile = {%s %s}\n", debugstr_w(file->chm_file), debugstr_w(file->chm_index)); | |
| } | |
| IStream *GetChmStream(CHMInfo *info, LPCWSTR parent_chm, ChmPath *chm_file) | |
| { | |
| IStorage *storage; | |
| IStream *stream = NULL; | |
| HRESULT hres; | |
| TRACE("%s (%s :: %s)\n", debugstr_w(parent_chm), debugstr_w(chm_file->chm_file), | |
| debugstr_w(chm_file->chm_index)); | |
| if(parent_chm || chm_file->chm_file) { | |
| hres = IITStorage_StgOpenStorage(info->pITStorage, | |
| chm_file->chm_file ? chm_file->chm_file : parent_chm, NULL, | |
| STGM_READ | STGM_SHARE_DENY_WRITE, NULL, 0, &storage); | |
| if(FAILED(hres)) { | |
| WARN("Could not open storage: %08x\n", hres); | |
| return NULL; | |
| } | |
| }else { | |
| storage = info->pStorage; | |
| IStorage_AddRef(info->pStorage); | |
| } | |
| hres = IStorage_OpenStream(storage, chm_file->chm_index, NULL, STGM_READ, 0, &stream); | |
| IStorage_Release(storage); | |
| if(FAILED(hres)) | |
| WARN("Could not open stream: %08x\n", hres); | |
| return stream; | |
| } | |
| /* Opens the CHM file for reading */ | |
| CHMInfo *OpenCHM(LPCWSTR szFile) | |
| { | |
| HRESULT hres; | |
| CHMInfo *ret; | |
| static const WCHAR wszSTRINGS[] = {'#','S','T','R','I','N','G','S',0}; | |
| if (!(ret = heap_alloc_zero(sizeof(CHMInfo)))) | |
| return NULL; | |
| if (!(ret->szFile = strdupW(szFile))) { | |
| heap_free(ret); | |
| return NULL; | |
| } | |
| hres = CoCreateInstance(&CLSID_ITStorage, NULL, CLSCTX_INPROC_SERVER, | |
| &IID_IITStorage, (void **) &ret->pITStorage) ; | |
| if(FAILED(hres)) { | |
| WARN("Could not create ITStorage: %08x\n", hres); | |
| return CloseCHM(ret); | |
| } | |
| hres = IITStorage_StgOpenStorage(ret->pITStorage, szFile, NULL, | |
| STGM_READ | STGM_SHARE_DENY_WRITE, NULL, 0, &ret->pStorage); | |
| if(FAILED(hres)) { | |
| WARN("Could not open storage: %08x\n", hres); | |
| return CloseCHM(ret); | |
| } | |
| hres = IStorage_OpenStream(ret->pStorage, wszSTRINGS, NULL, STGM_READ, 0, | |
| &ret->strings_stream); | |
| if(FAILED(hres)) { | |
| WARN("Could not open #STRINGS stream: %08x\n", hres); | |
| /* It's not critical, so we pass */ | |
| } | |
| if(!ReadChmSystem(ret)) { | |
| WARN("Could not read #SYSTEM\n"); | |
| return CloseCHM(ret); | |
| } | |
| return ret; | |
| } | |
| CHMInfo *CloseCHM(CHMInfo *chm) | |
| { | |
| if(chm->pITStorage) | |
| IITStorage_Release(chm->pITStorage); | |
| if(chm->pStorage) | |
| IStorage_Release(chm->pStorage); | |
| if(chm->strings_stream) | |
| IStream_Release(chm->strings_stream); | |
| if(chm->strings_size) { | |
| DWORD i; | |
| for(i=0; i<chm->strings_size; i++) | |
| heap_free(chm->strings[i]); | |
| } | |
| heap_free(chm->strings); | |
| heap_free(chm->defTitle); | |
| heap_free(chm->defTopic); | |
| heap_free(chm->defToc); | |
| heap_free(chm->szFile); | |
| heap_free(chm); | |
| return NULL; | |
| } |