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