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