wininet: Use stored status code in HTTP_HttpEndRequestW.
[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 Indicies, 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 toc_extW[] = {'h','h','c',0};
255     static const WCHAR index_extW[] = {'h','h','k',0};
256     static const WCHAR windowsW[] = {'#','W','I','N','D','O','W','S',0};
257
258     hr = IStorage_OpenStream(pStorage, windowsW, NULL, STGM_READ, 0, &pStream);
259     if (FAILED(hr))
260     {
261         /* no defined window types so use (hopefully) sane defaults */
262         static const WCHAR defaultwinW[] = {'d','e','f','a','u','l','t','w','i','n','\0'};
263         static const WCHAR null[] = {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.pszFile      = info->pszFile     = strdupAtoW(GetChmString(info->pCHMInfo, (DWORD_PTR)info->WinType.pszFile));
296     info->WinType.pszHome      = info->pszHome     = strdupAtoW(GetChmString(info->pCHMInfo, (DWORD_PTR)info->WinType.pszHome));
297     info->WinType.pszJump1     = info->pszJump1    = strdupAtoW(GetChmString(info->pCHMInfo, (DWORD_PTR)info->WinType.pszJump1));
298     info->WinType.pszJump2     = info->pszJump2    = strdupAtoW(GetChmString(info->pCHMInfo, (DWORD_PTR)info->WinType.pszJump2));
299     info->WinType.pszUrlJump1  = info->pszUrlJump1 = strdupAtoW(GetChmString(info->pCHMInfo, (DWORD_PTR)info->WinType.pszUrlJump1));
300     info->WinType.pszUrlJump2  = info->pszUrlJump2 = strdupAtoW(GetChmString(info->pCHMInfo, (DWORD_PTR)info->WinType.pszUrlJump2));
301
302     if (info->WinType.pszToc)
303         info->WinType.pszToc   = info->pszToc      = strdupAtoW(GetChmString(info->pCHMInfo, (DWORD_PTR)info->WinType.pszToc));
304     else
305         info->WinType.pszToc   = info->pszToc      = FindHTMLHelpSetting(info, toc_extW);
306     if (info->WinType.pszIndex)
307         info->WinType.pszIndex = info->pszIndex    = strdupAtoW(GetChmString(info->pCHMInfo, (DWORD_PTR)info->WinType.pszIndex));
308     else
309         info->WinType.pszIndex = info->pszIndex    = FindHTMLHelpSetting(info, index_extW);
310
311     /* FIXME: pszCustomTabs is a list of multiple zero-terminated strings so ReadString won't
312      * work in this case
313      */
314 #if 0
315     info->WinType.pszCustomTabs = info->pszCustomTabs = CHM_ReadString(pChmInfo, (DWORD_PTR)info->WinType.pszCustomTabs);
316 #endif
317
318 done:
319     IStream_Release(pStream);
320
321     return SUCCEEDED(hr);
322 }
323
324 LPCWSTR skip_schema(LPCWSTR url)
325 {
326     static const WCHAR its_schema[] = {'i','t','s',':'};
327     static const WCHAR msits_schema[] = {'m','s','-','i','t','s',':'};
328     static const WCHAR mk_schema[] = {'m','k',':','@','M','S','I','T','S','t','o','r','e',':'};
329
330     if(!strncmpiW(its_schema, url, sizeof(its_schema)/sizeof(WCHAR)))
331         return url+sizeof(its_schema)/sizeof(WCHAR);
332     if(!strncmpiW(msits_schema, url, sizeof(msits_schema)/sizeof(WCHAR)))
333         return url+sizeof(msits_schema)/sizeof(WCHAR);
334     if(!strncmpiW(mk_schema, url, sizeof(mk_schema)/sizeof(WCHAR)))
335         return url+sizeof(mk_schema)/sizeof(WCHAR);
336
337     return url;
338 }
339
340 void SetChmPath(ChmPath *file, LPCWSTR base_file, LPCWSTR path)
341 {
342     LPCWSTR ptr;
343     static const WCHAR separatorW[] = {':',':',0};
344
345     path = skip_schema(path);
346
347     ptr = strstrW(path, separatorW);
348     if(ptr) {
349         WCHAR chm_file[MAX_PATH];
350         WCHAR rel_path[MAX_PATH];
351         WCHAR base_path[MAX_PATH];
352         LPWSTR p;
353
354         strcpyW(base_path, base_file);
355         p = strrchrW(base_path, '\\');
356         if(p)
357             *p = 0;
358
359         memcpy(rel_path, path, (ptr-path)*sizeof(WCHAR));
360         rel_path[ptr-path] = 0;
361
362         PathCombineW(chm_file, base_path, rel_path);
363
364         file->chm_file = strdupW(chm_file);
365         ptr += 2;
366     }else {
367         file->chm_file = strdupW(base_file);
368         ptr = path;
369     }
370
371     file->chm_index = strdupW(ptr);
372
373     TRACE("ChmFile = {%s %s}\n", debugstr_w(file->chm_file), debugstr_w(file->chm_index));
374 }
375
376 IStream *GetChmStream(CHMInfo *info, LPCWSTR parent_chm, ChmPath *chm_file)
377 {
378     IStorage *storage;
379     IStream *stream = NULL;
380     HRESULT hres;
381
382     TRACE("%s (%s :: %s)\n", debugstr_w(parent_chm), debugstr_w(chm_file->chm_file),
383           debugstr_w(chm_file->chm_index));
384
385     if(parent_chm || chm_file->chm_file) {
386         hres = IITStorage_StgOpenStorage(info->pITStorage,
387                 chm_file->chm_file ? chm_file->chm_file : parent_chm, NULL,
388                 STGM_READ | STGM_SHARE_DENY_WRITE, NULL, 0, &storage);
389         if(FAILED(hres)) {
390             WARN("Could not open storage: %08x\n", hres);
391             return NULL;
392         }
393     }else {
394         storage = info->pStorage;
395         IStorage_AddRef(info->pStorage);
396     }
397
398     hres = IStorage_OpenStream(storage, chm_file->chm_index, NULL, STGM_READ, 0, &stream);
399     IStorage_Release(storage);
400     if(FAILED(hres))
401         WARN("Could not open stream: %08x\n", hres);
402
403     return stream;
404 }
405
406 /* Opens the CHM file for reading */
407 CHMInfo *OpenCHM(LPCWSTR szFile)
408 {
409     HRESULT hres;
410     CHMInfo *ret;
411
412     static const WCHAR wszSTRINGS[] = {'#','S','T','R','I','N','G','S',0};
413
414     if (!(ret = heap_alloc_zero(sizeof(CHMInfo))))
415         return NULL;
416
417     if (!(ret->szFile = strdupW(szFile))) {
418         heap_free(ret);
419         return NULL;
420     }
421
422     hres = CoCreateInstance(&CLSID_ITStorage, NULL, CLSCTX_INPROC_SERVER,
423             &IID_IITStorage, (void **) &ret->pITStorage) ;
424     if(FAILED(hres)) {
425         WARN("Could not create ITStorage: %08x\n", hres);
426         return CloseCHM(ret);
427     }
428
429     hres = IITStorage_StgOpenStorage(ret->pITStorage, szFile, NULL,
430             STGM_READ | STGM_SHARE_DENY_WRITE, NULL, 0, &ret->pStorage);
431     if(FAILED(hres)) {
432         WARN("Could not open storage: %08x\n", hres);
433         return CloseCHM(ret);
434     }
435     hres = IStorage_OpenStream(ret->pStorage, wszSTRINGS, NULL, STGM_READ, 0,
436             &ret->strings_stream);
437     if(FAILED(hres)) {
438         WARN("Could not open #STRINGS stream: %08x\n", hres);
439         /* It's not critical, so we pass */
440     }
441
442     if(!ReadChmSystem(ret)) {
443         WARN("Could not read #SYSTEM\n");
444         return CloseCHM(ret);
445     }
446
447     return ret;
448 }
449
450 CHMInfo *CloseCHM(CHMInfo *chm)
451 {
452     if(chm->pITStorage)
453         IITStorage_Release(chm->pITStorage);
454
455     if(chm->pStorage)
456         IStorage_Release(chm->pStorage);
457
458     if(chm->strings_stream)
459         IStream_Release(chm->strings_stream);
460
461     if(chm->strings_size) {
462         DWORD i;
463
464         for(i=0; i<chm->strings_size; i++)
465             heap_free(chm->strings[i]);
466     }
467
468     heap_free(chm->strings);
469     heap_free(chm->defTitle);
470     heap_free(chm->defTopic);
471     heap_free(chm->defToc);
472     heap_free(chm->szFile);
473     heap_free(chm->compiledFile);
474     heap_free(chm);
475
476     return NULL;
477 }