hhctrl.ocx: Keep parsing failure from causing a segmentation fault.
[wine] / dlls / hhctrl.ocx / chm.c
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
24 #include "winreg.h"
25 #include "shlwapi.h"
26 #include "wine/debug.h"
27
28 WINE_DEFAULT_DEBUG_CHANNEL(htmlhelp);
29
30 #define BLOCK_BITS 12
31 #define BLOCK_SIZE (1 << BLOCK_BITS)
32 #define BLOCK_MASK (BLOCK_SIZE-1)
33
34 /* Reads a string from the #STRINGS section in the CHM file */
35 static LPCSTR GetChmString(CHMInfo *chm, DWORD offset)
36 {
37     LPCSTR str;
38
39     if(!chm->strings_stream)
40         return NULL;
41
42     if(chm->strings_size <= (offset >> BLOCK_BITS)) {
43         chm->strings_size = (offset >> BLOCK_BITS)+1;
44         if(chm->strings)
45             chm->strings = heap_realloc_zero(chm->strings,
46                     chm->strings_size*sizeof(char*));
47         else
48             chm->strings = heap_alloc_zero(
49                     chm->strings_size*sizeof(char*));
50
51     }
52
53     if(!chm->strings[offset >> BLOCK_BITS]) {
54         LARGE_INTEGER pos;
55         DWORD read;
56         HRESULT hres;
57
58         pos.QuadPart = offset & ~BLOCK_MASK;
59         hres = IStream_Seek(chm->strings_stream, pos, STREAM_SEEK_SET, NULL);
60         if(FAILED(hres)) {
61             WARN("Seek failed: %08x\n", hres);
62             return NULL;
63         }
64
65         chm->strings[offset >> BLOCK_BITS] = heap_alloc(BLOCK_SIZE);
66
67         hres = IStream_Read(chm->strings_stream, chm->strings[offset >> BLOCK_BITS],
68                             BLOCK_SIZE, &read);
69         if(FAILED(hres)) {
70             WARN("Read failed: %08x\n", hres);
71             heap_free(chm->strings[offset >> BLOCK_BITS]);
72             chm->strings[offset >> BLOCK_BITS] = NULL;
73             return NULL;
74         }
75     }
76
77     str = chm->strings[offset >> BLOCK_BITS] + (offset & BLOCK_MASK);
78     TRACE("offset %#x => %s\n", offset, debugstr_a(str));
79     return str;
80 }
81
82 static BOOL ReadChmSystem(CHMInfo *chm)
83 {
84     IStream *stream;
85     DWORD ver=0xdeadbeef, read, buf_size;
86     char *buf;
87     HRESULT hres;
88
89     struct {
90         WORD code;
91         WORD len;
92     } entry;
93
94     static const WCHAR wszSYSTEM[] = {'#','S','Y','S','T','E','M',0};
95
96     hres = IStorage_OpenStream(chm->pStorage, wszSYSTEM, NULL, STGM_READ, 0, &stream);
97     if(FAILED(hres)) {
98         WARN("Could not open #SYSTEM stream: %08x\n", hres);
99         return FALSE;
100     }
101
102     IStream_Read(stream, &ver, sizeof(ver), &read);
103     TRACE("version is %x\n", ver);
104
105     buf = heap_alloc(8*sizeof(DWORD));
106     buf_size = 8*sizeof(DWORD);
107
108     while(1) {
109         hres = IStream_Read(stream, &entry, sizeof(entry), &read);
110         if(hres != S_OK)
111             break;
112
113         if(entry.len > buf_size)
114             buf = heap_realloc(buf, buf_size=entry.len);
115
116         hres = IStream_Read(stream, buf, entry.len, &read);
117         if(hres != S_OK)
118             break;
119
120         switch(entry.code) {
121         case 0x0:
122             TRACE("TOC is %s\n", debugstr_an(buf, entry.len));
123             heap_free(chm->defToc);
124             chm->defToc = strdupnAtoW(buf, entry.len);
125             break;
126         case 0x2:
127             TRACE("Default topic is %s\n", debugstr_an(buf, entry.len));
128             heap_free(chm->defTopic);
129             chm->defTopic = strdupnAtoW(buf, entry.len);
130             break;
131         case 0x3:
132             TRACE("Title is %s\n", debugstr_an(buf, entry.len));
133             heap_free(chm->defTitle);
134             chm->defTitle = strdupnAtoW(buf, entry.len);
135             break;
136         case 0x4:
137             /* TODO: Currently only the Locale ID is loaded from this field */
138             TRACE("Locale is: %d\n", *(LCID*)&buf[0]);
139             if(!GetLocaleInfoW(*(LCID*)&buf[0], LOCALE_IDEFAULTANSICODEPAGE|LOCALE_RETURN_NUMBER,
140                                (WCHAR *)&chm->codePage, sizeof(chm->codePage)/sizeof(WCHAR)))
141                 chm->codePage = CP_ACP;
142             break;
143         case 0x5:
144             TRACE("Default window is %s\n", debugstr_an(buf, entry.len));
145             break;
146         case 0x6:
147             TRACE("Compiled file is %s\n", debugstr_an(buf, entry.len));
148             heap_free(chm->compiledFile);
149             chm->compiledFile = strdupnAtoW(buf, entry.len);
150             break;
151         case 0x9:
152             TRACE("Version is %s\n", debugstr_an(buf, entry.len));
153             break;
154         case 0xa:
155             TRACE("Time is %08x\n", *(DWORD*)buf);
156             break;
157         case 0xc:
158             TRACE("Number of info types: %d\n", *(DWORD*)buf);
159             break;
160         case 0xf:
161             TRACE("Check sum: %x\n", *(DWORD*)buf);
162             break;
163         default:
164             TRACE("unhandled code %x, size %x\n", entry.code, entry.len);
165         }
166     }
167
168     heap_free(buf);
169     IStream_Release(stream);
170
171     return SUCCEEDED(hres);
172 }
173
174 LPWSTR FindContextAlias(CHMInfo *chm, DWORD index)
175 {
176     IStream *ivb_stream;
177     DWORD size, read, i;
178     DWORD *buf;
179     LPCSTR ret = NULL;
180     HRESULT hres;
181
182     static const WCHAR wszIVB[] = {'#','I','V','B',0};
183
184     hres = IStorage_OpenStream(chm->pStorage, wszIVB, NULL, STGM_READ, 0, &ivb_stream);
185     if(FAILED(hres)) {
186         WARN("Could not open #IVB stream: %08x\n", hres);
187         return NULL;
188     }
189
190     hres = IStream_Read(ivb_stream, &size, sizeof(size), &read);
191     if(FAILED(hres)) {
192         WARN("Read failed: %08x\n", hres);
193         IStream_Release(ivb_stream);
194         return NULL;
195     }
196
197     buf = heap_alloc(size);
198     hres = IStream_Read(ivb_stream, buf, size, &read);
199     IStream_Release(ivb_stream);
200     if(FAILED(hres)) {
201         WARN("Read failed: %08x\n", hres);
202         heap_free(buf);
203         return NULL;
204     }
205
206     size /= 2*sizeof(DWORD);
207
208     for(i=0; i<size; i++) {
209         if(buf[2*i] == index) {
210             ret = GetChmString(chm, buf[2*i+1]);
211             break;
212         }
213     }
214
215     heap_free(buf);
216
217     TRACE("returning %s\n", debugstr_a(ret));
218     return strdupAtoW(ret);
219 }
220
221 /*
222  * Tests if the file <chmfile>.<ext> exists, used for loading Indices, Table of Contents, etc.
223  * when these files are not available from the HH_WINTYPE structure.
224  */
225 static WCHAR *FindHTMLHelpSetting(HHInfo *info, const WCHAR *extW)
226 {
227     static const WCHAR periodW[] = {'.',0};
228     IStorage *pStorage = info->pCHMInfo->pStorage;
229     IStream *pStream;
230     WCHAR *filename;
231     HRESULT hr;
232
233     filename = heap_alloc( (strlenW(info->pCHMInfo->compiledFile)
234                             + strlenW(periodW) + strlenW(extW) + 1) * sizeof(WCHAR) );
235     strcpyW(filename, info->pCHMInfo->compiledFile);
236     strcatW(filename, periodW);
237     strcatW(filename, extW);
238     hr = IStorage_OpenStream(pStorage, filename, NULL, STGM_READ, 0, &pStream);
239     if (FAILED(hr))
240     {
241         heap_free(filename);
242         return strdupAtoW("");
243     }
244     IStream_Release(pStream);
245     return filename;
246 }
247
248 /* Loads the HH_WINTYPE data from the CHM file
249  *
250  * FIXME: There may be more than one window type in the file, so
251  *        add the ability to choose a certain window type
252  */
253 BOOL LoadWinTypeFromCHM(HHInfo *info)
254 {
255     LARGE_INTEGER liOffset;
256     IStorage *pStorage = info->pCHMInfo->pStorage;
257     IStream *pStream;
258     HRESULT hr;
259     DWORD cbRead;
260
261     static const WCHAR null[] = {0};
262     static const WCHAR toc_extW[] = {'h','h','c',0};
263     static const WCHAR index_extW[] = {'h','h','k',0};
264     static const WCHAR windowsW[] = {'#','W','I','N','D','O','W','S',0};
265
266     hr = IStorage_OpenStream(pStorage, windowsW, NULL, STGM_READ, 0, &pStream);
267     if (FAILED(hr))
268     {
269         /* no defined window types so use (hopefully) sane defaults */
270         static const WCHAR defaultwinW[] = {'d','e','f','a','u','l','t','w','i','n','\0'};
271         memset((void*)&(info->WinType), 0, sizeof(info->WinType));
272         info->WinType.cbStruct=sizeof(info->WinType);
273         info->WinType.fUniCodeStrings=TRUE;
274         info->WinType.pszType=strdupW(defaultwinW);
275         info->WinType.pszToc = strdupW(info->pCHMInfo->defToc ? info->pCHMInfo->defToc : null);
276         info->WinType.pszIndex = strdupW(null);
277         info->WinType.fsValidMembers=0;
278         info->WinType.fsWinProperties=HHWIN_PROP_TRI_PANE;
279         info->WinType.pszCaption=strdupW(info->pCHMInfo->defTitle ? info->pCHMInfo->defTitle : null);
280         info->WinType.dwStyles=WS_POPUP;
281         info->WinType.dwExStyles=0;
282         info->WinType.nShowState=SW_SHOW;
283         info->WinType.pszFile=strdupW(info->pCHMInfo->defTopic ? info->pCHMInfo->defTopic : null);
284         info->WinType.curNavType=HHWIN_NAVTYPE_TOC;
285         return TRUE;
286     }
287
288     /* jump past the #WINDOWS header */
289     liOffset.QuadPart = sizeof(DWORD) * 2;
290
291     hr = IStream_Seek(pStream, liOffset, STREAM_SEEK_SET, NULL);
292     if (FAILED(hr)) goto done;
293
294     /* read the HH_WINTYPE struct data */
295     hr = IStream_Read(pStream, &info->WinType, sizeof(info->WinType), &cbRead);
296     if (FAILED(hr)) goto done;
297
298     /* convert the #STRINGS offsets to actual strings */
299
300     info->WinType.pszType      = info->pszType     = strdupAtoW(GetChmString(info->pCHMInfo, (DWORD_PTR)info->WinType.pszType));
301     info->WinType.pszCaption   = info->pszCaption  = strdupAtoW(GetChmString(info->pCHMInfo, (DWORD_PTR)info->WinType.pszCaption));
302     info->WinType.pszHome      = info->pszHome     = strdupAtoW(GetChmString(info->pCHMInfo, (DWORD_PTR)info->WinType.pszHome));
303     info->WinType.pszJump1     = info->pszJump1    = strdupAtoW(GetChmString(info->pCHMInfo, (DWORD_PTR)info->WinType.pszJump1));
304     info->WinType.pszJump2     = info->pszJump2    = strdupAtoW(GetChmString(info->pCHMInfo, (DWORD_PTR)info->WinType.pszJump2));
305     info->WinType.pszUrlJump1  = info->pszUrlJump1 = strdupAtoW(GetChmString(info->pCHMInfo, (DWORD_PTR)info->WinType.pszUrlJump1));
306     info->WinType.pszUrlJump2  = info->pszUrlJump2 = strdupAtoW(GetChmString(info->pCHMInfo, (DWORD_PTR)info->WinType.pszUrlJump2));
307
308     if (info->WinType.pszFile)
309         info->WinType.pszFile  = info->pszFile     = strdupAtoW(GetChmString(info->pCHMInfo, (DWORD_PTR)info->WinType.pszFile));
310     else
311         info->WinType.pszFile  = strdupW(info->pCHMInfo->defTopic ? info->pCHMInfo->defTopic : null);
312     if (info->WinType.pszToc)
313         info->WinType.pszToc   = info->pszToc      = strdupAtoW(GetChmString(info->pCHMInfo, (DWORD_PTR)info->WinType.pszToc));
314     else
315         info->WinType.pszToc   = info->pszToc      = FindHTMLHelpSetting(info, toc_extW);
316     if (info->WinType.pszIndex)
317         info->WinType.pszIndex = info->pszIndex    = strdupAtoW(GetChmString(info->pCHMInfo, (DWORD_PTR)info->WinType.pszIndex));
318     else
319         info->WinType.pszIndex = info->pszIndex    = FindHTMLHelpSetting(info, index_extW);
320
321     /* FIXME: pszCustomTabs is a list of multiple zero-terminated strings so ReadString won't
322      * work in this case
323      */
324 #if 0
325     info->WinType.pszCustomTabs = info->pszCustomTabs = CHM_ReadString(pChmInfo, (DWORD_PTR)info->WinType.pszCustomTabs);
326 #endif
327
328 done:
329     IStream_Release(pStream);
330
331     return SUCCEEDED(hr);
332 }
333
334 LPCWSTR skip_schema(LPCWSTR url)
335 {
336     static const WCHAR its_schema[] = {'i','t','s',':'};
337     static const WCHAR msits_schema[] = {'m','s','-','i','t','s',':'};
338     static const WCHAR mk_schema[] = {'m','k',':','@','M','S','I','T','S','t','o','r','e',':'};
339
340     if(!strncmpiW(its_schema, url, sizeof(its_schema)/sizeof(WCHAR)))
341         return url+sizeof(its_schema)/sizeof(WCHAR);
342     if(!strncmpiW(msits_schema, url, sizeof(msits_schema)/sizeof(WCHAR)))
343         return url+sizeof(msits_schema)/sizeof(WCHAR);
344     if(!strncmpiW(mk_schema, url, sizeof(mk_schema)/sizeof(WCHAR)))
345         return url+sizeof(mk_schema)/sizeof(WCHAR);
346
347     return url;
348 }
349
350 void SetChmPath(ChmPath *file, LPCWSTR base_file, LPCWSTR path)
351 {
352     LPCWSTR ptr;
353     static const WCHAR separatorW[] = {':',':',0};
354
355     path = skip_schema(path);
356
357     ptr = strstrW(path, separatorW);
358     if(ptr) {
359         WCHAR chm_file[MAX_PATH];
360         WCHAR rel_path[MAX_PATH];
361         WCHAR base_path[MAX_PATH];
362         LPWSTR p;
363
364         strcpyW(base_path, base_file);
365         p = strrchrW(base_path, '\\');
366         if(p)
367             *p = 0;
368
369         memcpy(rel_path, path, (ptr-path)*sizeof(WCHAR));
370         rel_path[ptr-path] = 0;
371
372         PathCombineW(chm_file, base_path, rel_path);
373
374         file->chm_file = strdupW(chm_file);
375         ptr += 2;
376     }else {
377         file->chm_file = strdupW(base_file);
378         ptr = path;
379     }
380
381     file->chm_index = strdupW(ptr);
382
383     TRACE("ChmFile = {%s %s}\n", debugstr_w(file->chm_file), debugstr_w(file->chm_index));
384 }
385
386 IStream *GetChmStream(CHMInfo *info, LPCWSTR parent_chm, ChmPath *chm_file)
387 {
388     IStorage *storage;
389     IStream *stream = NULL;
390     HRESULT hres;
391
392     TRACE("%s (%s :: %s)\n", debugstr_w(parent_chm), debugstr_w(chm_file->chm_file),
393           debugstr_w(chm_file->chm_index));
394
395     if(parent_chm || chm_file->chm_file) {
396         hres = IITStorage_StgOpenStorage(info->pITStorage,
397                 chm_file->chm_file ? chm_file->chm_file : parent_chm, NULL,
398                 STGM_READ | STGM_SHARE_DENY_WRITE, NULL, 0, &storage);
399         if(FAILED(hres)) {
400             WARN("Could not open storage: %08x\n", hres);
401             return NULL;
402         }
403     }else {
404         storage = info->pStorage;
405         IStorage_AddRef(info->pStorage);
406     }
407
408     hres = IStorage_OpenStream(storage, chm_file->chm_index, NULL, STGM_READ, 0, &stream);
409     IStorage_Release(storage);
410     if(FAILED(hres))
411         WARN("Could not open stream: %08x\n", hres);
412
413     return stream;
414 }
415
416 /* Opens the CHM file for reading */
417 CHMInfo *OpenCHM(LPCWSTR szFile)
418 {
419     HRESULT hres;
420     CHMInfo *ret;
421
422     static const WCHAR wszSTRINGS[] = {'#','S','T','R','I','N','G','S',0};
423
424     if (!(ret = heap_alloc_zero(sizeof(CHMInfo))))
425         return NULL;
426     ret->codePage = CP_ACP;
427
428     if (!(ret->szFile = strdupW(szFile))) {
429         heap_free(ret);
430         return NULL;
431     }
432
433     hres = CoCreateInstance(&CLSID_ITStorage, NULL, CLSCTX_INPROC_SERVER,
434             &IID_IITStorage, (void **) &ret->pITStorage) ;
435     if(FAILED(hres)) {
436         WARN("Could not create ITStorage: %08x\n", hres);
437         return CloseCHM(ret);
438     }
439
440     hres = IITStorage_StgOpenStorage(ret->pITStorage, szFile, NULL,
441             STGM_READ | STGM_SHARE_DENY_WRITE, NULL, 0, &ret->pStorage);
442     if(FAILED(hres)) {
443         WARN("Could not open storage: %08x\n", hres);
444         return CloseCHM(ret);
445     }
446     hres = IStorage_OpenStream(ret->pStorage, wszSTRINGS, NULL, STGM_READ, 0,
447             &ret->strings_stream);
448     if(FAILED(hres)) {
449         WARN("Could not open #STRINGS stream: %08x\n", hres);
450         /* It's not critical, so we pass */
451     }
452
453     if(!ReadChmSystem(ret)) {
454         WARN("Could not read #SYSTEM\n");
455         return CloseCHM(ret);
456     }
457
458     return ret;
459 }
460
461 CHMInfo *CloseCHM(CHMInfo *chm)
462 {
463     if(chm->pITStorage)
464         IITStorage_Release(chm->pITStorage);
465
466     if(chm->pStorage)
467         IStorage_Release(chm->pStorage);
468
469     if(chm->strings_stream)
470         IStream_Release(chm->strings_stream);
471
472     if(chm->strings_size) {
473         DWORD i;
474
475         for(i=0; i<chm->strings_size; i++)
476             heap_free(chm->strings[i]);
477     }
478
479     heap_free(chm->strings);
480     heap_free(chm->defTitle);
481     heap_free(chm->defTopic);
482     heap_free(chm->defToc);
483     heap_free(chm->szFile);
484     heap_free(chm->compiledFile);
485     heap_free(chm);
486
487     return NULL;
488 }