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