hhctrl.ocx: Don't use GWLP_USERDATA to store private data.
[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 0x5:
137             TRACE("Default window is %s\n", debugstr_an(buf, entry.len));
138             break;
139         case 0x6:
140             TRACE("Compiled file is %s\n", debugstr_an(buf, entry.len));
141             heap_free(chm->compiledFile);
142             chm->compiledFile = strdupnAtoW(buf, entry.len);
143             break;
144         case 0x9:
145             TRACE("Version is %s\n", debugstr_an(buf, entry.len));
146             break;
147         case 0xa:
148             TRACE("Time is %08x\n", *(DWORD*)buf);
149             break;
150         case 0xc:
151             TRACE("Number of info types: %d\n", *(DWORD*)buf);
152             break;
153         case 0xf:
154             TRACE("Check sum: %x\n", *(DWORD*)buf);
155             break;
156         default:
157             TRACE("unhandled code %x, size %x\n", entry.code, entry.len);
158         }
159     }
160
161     heap_free(buf);
162     IStream_Release(stream);
163
164     return SUCCEEDED(hres);
165 }
166
167 LPWSTR FindContextAlias(CHMInfo *chm, DWORD index)
168 {
169     IStream *ivb_stream;
170     DWORD size, read, i;
171     DWORD *buf;
172     LPCSTR ret = NULL;
173     HRESULT hres;
174
175     static const WCHAR wszIVB[] = {'#','I','V','B',0};
176
177     hres = IStorage_OpenStream(chm->pStorage, wszIVB, NULL, STGM_READ, 0, &ivb_stream);
178     if(FAILED(hres)) {
179         WARN("Could not open #IVB stream: %08x\n", hres);
180         return NULL;
181     }
182
183     hres = IStream_Read(ivb_stream, &size, sizeof(size), &read);
184     if(FAILED(hres)) {
185         WARN("Read failed: %08x\n", hres);
186         IStream_Release(ivb_stream);
187         return NULL;
188     }
189
190     buf = heap_alloc(size);
191     hres = IStream_Read(ivb_stream, buf, size, &read);
192     IStream_Release(ivb_stream);
193     if(FAILED(hres)) {
194         WARN("Read failed: %08x\n", hres);
195         heap_free(buf);
196         return NULL;
197     }
198
199     size /= 2*sizeof(DWORD);
200
201     for(i=0; i<size; i++) {
202         if(buf[2*i] == index) {
203             ret = GetChmString(chm, buf[2*i+1]);
204             break;
205         }
206     }
207
208     heap_free(buf);
209
210     TRACE("returning %s\n", debugstr_a(ret));
211     return strdupAtoW(ret);
212 }
213
214 /*
215  * Tests if the file <chmfile>.<ext> exists, used for loading Indices, Table of Contents, etc.
216  * when these files are not available from the HH_WINTYPE structure.
217  */
218 static WCHAR *FindHTMLHelpSetting(HHInfo *info, const WCHAR *extW)
219 {
220     static const WCHAR periodW[] = {'.',0};
221     IStorage *pStorage = info->pCHMInfo->pStorage;
222     IStream *pStream;
223     WCHAR *filename;
224     HRESULT hr;
225
226     filename = heap_alloc( (strlenW(info->pCHMInfo->compiledFile)
227                             + strlenW(periodW) + strlenW(extW) + 1) * sizeof(WCHAR) );
228     strcpyW(filename, info->pCHMInfo->compiledFile);
229     strcatW(filename, periodW);
230     strcatW(filename, extW);
231     hr = IStorage_OpenStream(pStorage, filename, NULL, STGM_READ, 0, &pStream);
232     if (FAILED(hr))
233     {
234         heap_free(filename);
235         return strdupAtoW("");
236     }
237     IStream_Release(pStream);
238     return filename;
239 }
240
241 /* Loads the HH_WINTYPE data from the CHM file
242  *
243  * FIXME: There may be more than one window type in the file, so
244  *        add the ability to choose a certain window type
245  */
246 BOOL LoadWinTypeFromCHM(HHInfo *info)
247 {
248     LARGE_INTEGER liOffset;
249     IStorage *pStorage = info->pCHMInfo->pStorage;
250     IStream *pStream;
251     HRESULT hr;
252     DWORD cbRead;
253
254     static const WCHAR null[] = {0};
255     static const WCHAR toc_extW[] = {'h','h','c',0};
256     static const WCHAR index_extW[] = {'h','h','k',0};
257     static const WCHAR windowsW[] = {'#','W','I','N','D','O','W','S',0};
258
259     hr = IStorage_OpenStream(pStorage, windowsW, NULL, STGM_READ, 0, &pStream);
260     if (FAILED(hr))
261     {
262         /* no defined window types so use (hopefully) sane defaults */
263         static const WCHAR defaultwinW[] = {'d','e','f','a','u','l','t','w','i','n','\0'};
264         memset((void*)&(info->WinType), 0, sizeof(info->WinType));
265         info->WinType.cbStruct=sizeof(info->WinType);
266         info->WinType.fUniCodeStrings=TRUE;
267         info->WinType.pszType=strdupW(defaultwinW);
268         info->WinType.pszToc = strdupW(info->pCHMInfo->defToc ? info->pCHMInfo->defToc : null);
269         info->WinType.pszIndex = strdupW(null);
270         info->WinType.fsValidMembers=0;
271         info->WinType.fsWinProperties=HHWIN_PROP_TRI_PANE;
272         info->WinType.pszCaption=strdupW(info->pCHMInfo->defTitle ? info->pCHMInfo->defTitle : null);
273         info->WinType.dwStyles=WS_POPUP;
274         info->WinType.dwExStyles=0;
275         info->WinType.nShowState=SW_SHOW;
276         info->WinType.pszFile=strdupW(info->pCHMInfo->defTopic ? info->pCHMInfo->defTopic : null);
277         info->WinType.curNavType=HHWIN_NAVTYPE_TOC;
278         return TRUE;
279     }
280
281     /* jump past the #WINDOWS header */
282     liOffset.QuadPart = sizeof(DWORD) * 2;
283
284     hr = IStream_Seek(pStream, liOffset, STREAM_SEEK_SET, NULL);
285     if (FAILED(hr)) goto done;
286
287     /* read the HH_WINTYPE struct data */
288     hr = IStream_Read(pStream, &info->WinType, sizeof(info->WinType), &cbRead);
289     if (FAILED(hr)) goto done;
290
291     /* convert the #STRINGS offsets to actual strings */
292
293     info->WinType.pszType      = info->pszType     = strdupAtoW(GetChmString(info->pCHMInfo, (DWORD_PTR)info->WinType.pszType));
294     info->WinType.pszCaption   = info->pszCaption  = strdupAtoW(GetChmString(info->pCHMInfo, (DWORD_PTR)info->WinType.pszCaption));
295     info->WinType.pszHome      = info->pszHome     = strdupAtoW(GetChmString(info->pCHMInfo, (DWORD_PTR)info->WinType.pszHome));
296     info->WinType.pszJump1     = info->pszJump1    = strdupAtoW(GetChmString(info->pCHMInfo, (DWORD_PTR)info->WinType.pszJump1));
297     info->WinType.pszJump2     = info->pszJump2    = strdupAtoW(GetChmString(info->pCHMInfo, (DWORD_PTR)info->WinType.pszJump2));
298     info->WinType.pszUrlJump1  = info->pszUrlJump1 = strdupAtoW(GetChmString(info->pCHMInfo, (DWORD_PTR)info->WinType.pszUrlJump1));
299     info->WinType.pszUrlJump2  = info->pszUrlJump2 = strdupAtoW(GetChmString(info->pCHMInfo, (DWORD_PTR)info->WinType.pszUrlJump2));
300
301     if (info->WinType.pszFile)
302         info->WinType.pszFile  = info->pszFile     = strdupAtoW(GetChmString(info->pCHMInfo, (DWORD_PTR)info->WinType.pszFile));
303     else
304         info->WinType.pszFile  = strdupW(info->pCHMInfo->defTopic ? info->pCHMInfo->defTopic : null);
305     if (info->WinType.pszToc)
306         info->WinType.pszToc   = info->pszToc      = strdupAtoW(GetChmString(info->pCHMInfo, (DWORD_PTR)info->WinType.pszToc));
307     else
308         info->WinType.pszToc   = info->pszToc      = FindHTMLHelpSetting(info, toc_extW);
309     if (info->WinType.pszIndex)
310         info->WinType.pszIndex = info->pszIndex    = strdupAtoW(GetChmString(info->pCHMInfo, (DWORD_PTR)info->WinType.pszIndex));
311     else
312         info->WinType.pszIndex = info->pszIndex    = FindHTMLHelpSetting(info, index_extW);
313
314     /* FIXME: pszCustomTabs is a list of multiple zero-terminated strings so ReadString won't
315      * work in this case
316      */
317 #if 0
318     info->WinType.pszCustomTabs = info->pszCustomTabs = CHM_ReadString(pChmInfo, (DWORD_PTR)info->WinType.pszCustomTabs);
319 #endif
320
321 done:
322     IStream_Release(pStream);
323
324     return SUCCEEDED(hr);
325 }
326
327 LPCWSTR skip_schema(LPCWSTR url)
328 {
329     static const WCHAR its_schema[] = {'i','t','s',':'};
330     static const WCHAR msits_schema[] = {'m','s','-','i','t','s',':'};
331     static const WCHAR mk_schema[] = {'m','k',':','@','M','S','I','T','S','t','o','r','e',':'};
332
333     if(!strncmpiW(its_schema, url, sizeof(its_schema)/sizeof(WCHAR)))
334         return url+sizeof(its_schema)/sizeof(WCHAR);
335     if(!strncmpiW(msits_schema, url, sizeof(msits_schema)/sizeof(WCHAR)))
336         return url+sizeof(msits_schema)/sizeof(WCHAR);
337     if(!strncmpiW(mk_schema, url, sizeof(mk_schema)/sizeof(WCHAR)))
338         return url+sizeof(mk_schema)/sizeof(WCHAR);
339
340     return url;
341 }
342
343 void SetChmPath(ChmPath *file, LPCWSTR base_file, LPCWSTR path)
344 {
345     LPCWSTR ptr;
346     static const WCHAR separatorW[] = {':',':',0};
347
348     path = skip_schema(path);
349
350     ptr = strstrW(path, separatorW);
351     if(ptr) {
352         WCHAR chm_file[MAX_PATH];
353         WCHAR rel_path[MAX_PATH];
354         WCHAR base_path[MAX_PATH];
355         LPWSTR p;
356
357         strcpyW(base_path, base_file);
358         p = strrchrW(base_path, '\\');
359         if(p)
360             *p = 0;
361
362         memcpy(rel_path, path, (ptr-path)*sizeof(WCHAR));
363         rel_path[ptr-path] = 0;
364
365         PathCombineW(chm_file, base_path, rel_path);
366
367         file->chm_file = strdupW(chm_file);
368         ptr += 2;
369     }else {
370         file->chm_file = strdupW(base_file);
371         ptr = path;
372     }
373
374     file->chm_index = strdupW(ptr);
375
376     TRACE("ChmFile = {%s %s}\n", debugstr_w(file->chm_file), debugstr_w(file->chm_index));
377 }
378
379 IStream *GetChmStream(CHMInfo *info, LPCWSTR parent_chm, ChmPath *chm_file)
380 {
381     IStorage *storage;
382     IStream *stream = NULL;
383     HRESULT hres;
384
385     TRACE("%s (%s :: %s)\n", debugstr_w(parent_chm), debugstr_w(chm_file->chm_file),
386           debugstr_w(chm_file->chm_index));
387
388     if(parent_chm || chm_file->chm_file) {
389         hres = IITStorage_StgOpenStorage(info->pITStorage,
390                 chm_file->chm_file ? chm_file->chm_file : parent_chm, NULL,
391                 STGM_READ | STGM_SHARE_DENY_WRITE, NULL, 0, &storage);
392         if(FAILED(hres)) {
393             WARN("Could not open storage: %08x\n", hres);
394             return NULL;
395         }
396     }else {
397         storage = info->pStorage;
398         IStorage_AddRef(info->pStorage);
399     }
400
401     hres = IStorage_OpenStream(storage, chm_file->chm_index, NULL, STGM_READ, 0, &stream);
402     IStorage_Release(storage);
403     if(FAILED(hres))
404         WARN("Could not open stream: %08x\n", hres);
405
406     return stream;
407 }
408
409 /* Opens the CHM file for reading */
410 CHMInfo *OpenCHM(LPCWSTR szFile)
411 {
412     HRESULT hres;
413     CHMInfo *ret;
414
415     static const WCHAR wszSTRINGS[] = {'#','S','T','R','I','N','G','S',0};
416
417     if (!(ret = heap_alloc_zero(sizeof(CHMInfo))))
418         return NULL;
419
420     if (!(ret->szFile = strdupW(szFile))) {
421         heap_free(ret);
422         return NULL;
423     }
424
425     hres = CoCreateInstance(&CLSID_ITStorage, NULL, CLSCTX_INPROC_SERVER,
426             &IID_IITStorage, (void **) &ret->pITStorage) ;
427     if(FAILED(hres)) {
428         WARN("Could not create ITStorage: %08x\n", hres);
429         return CloseCHM(ret);
430     }
431
432     hres = IITStorage_StgOpenStorage(ret->pITStorage, szFile, NULL,
433             STGM_READ | STGM_SHARE_DENY_WRITE, NULL, 0, &ret->pStorage);
434     if(FAILED(hres)) {
435         WARN("Could not open storage: %08x\n", hres);
436         return CloseCHM(ret);
437     }
438     hres = IStorage_OpenStream(ret->pStorage, wszSTRINGS, NULL, STGM_READ, 0,
439             &ret->strings_stream);
440     if(FAILED(hres)) {
441         WARN("Could not open #STRINGS stream: %08x\n", hres);
442         /* It's not critical, so we pass */
443     }
444
445     if(!ReadChmSystem(ret)) {
446         WARN("Could not read #SYSTEM\n");
447         return CloseCHM(ret);
448     }
449
450     return ret;
451 }
452
453 CHMInfo *CloseCHM(CHMInfo *chm)
454 {
455     if(chm->pITStorage)
456         IITStorage_Release(chm->pITStorage);
457
458     if(chm->pStorage)
459         IStorage_Release(chm->pStorage);
460
461     if(chm->strings_stream)
462         IStream_Release(chm->strings_stream);
463
464     if(chm->strings_size) {
465         DWORD i;
466
467         for(i=0; i<chm->strings_size; i++)
468             heap_free(chm->strings[i]);
469     }
470
471     heap_free(chm->strings);
472     heap_free(chm->defTitle);
473     heap_free(chm->defTopic);
474     heap_free(chm->defToc);
475     heap_free(chm->szFile);
476     heap_free(chm->compiledFile);
477     heap_free(chm);
478
479     return NULL;
480 }