xref: /reactos/dll/win32/inetcomm/protocol.c (revision 4561998a)
1 /*
2  * Copyright 2017 Jacek Caban for CodeWeavers
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
17  */
18 
19 #define COBJMACROS
20 #define NONAMELESSUNION
21 
22 #include <assert.h>
23 
24 #include "mimeole.h"
25 #include "inetcomm_private.h"
26 
27 #include "wine/debug.h"
28 #include "wine/heap.h"
29 #include "wine/unicode.h"
30 
31 WINE_DEFAULT_DEBUG_CHANNEL(inetcomm);
32 
33 typedef struct {
34     IUnknown IUnknown_inner;
35     IInternetProtocol IInternetProtocol_iface;
36     IInternetProtocolInfo IInternetProtocolInfo_iface;
37 
38     LONG ref;
39     IUnknown *outer_unk;
40 
41     WCHAR *location;
42     IStream *stream;
43     IInternetProtocolSink *sink;
44 } MimeHtmlProtocol;
45 
46 typedef struct {
47     const WCHAR *mhtml;
48     size_t mhtml_len;
49     const WCHAR *location;
50 } mhtml_url_t;
51 
52 typedef struct {
53     IBindStatusCallback IBindStatusCallback_iface;
54 
55     LONG ref;
56 
57     MimeHtmlProtocol *protocol;
58     HRESULT status;
59     IStream *stream;
60     WCHAR url[1];
61 } MimeHtmlBinding;
62 
63 static const WCHAR mhtml_prefixW[] = {'m','h','t','m','l',':'};
64 static const WCHAR mhtml_separatorW[] = {'!','x','-','u','s','c',':'};
65 
66 static inline LPWSTR heap_strdupW(LPCWSTR str)
67 {
68     LPWSTR ret = NULL;
69 
70     if(str) {
71         DWORD size;
72 
73         size = (strlenW(str)+1)*sizeof(WCHAR);
74         ret = heap_alloc(size);
75         if(ret)
76             memcpy(ret, str, size);
77     }
78 
79     return ret;
80 }
81 
82 static HRESULT parse_mhtml_url(const WCHAR *url, mhtml_url_t *r)
83 {
84     const WCHAR *p;
85 
86     if(strncmpiW(url, mhtml_prefixW, ARRAY_SIZE(mhtml_prefixW)))
87         return E_FAIL;
88 
89     r->mhtml = url + ARRAY_SIZE(mhtml_prefixW);
90     p = strchrW(r->mhtml, '!');
91     if(p) {
92         r->mhtml_len = p - r->mhtml;
93         /* FIXME: We handle '!' and '!x-usc:' in URLs as the same thing. Those should not be the same. */
94         if(!strncmpW(p, mhtml_separatorW, ARRAY_SIZE(mhtml_separatorW)))
95             p += ARRAY_SIZE(mhtml_separatorW);
96         else
97             p++;
98     }else {
99         r->mhtml_len = strlenW(r->mhtml);
100     }
101 
102     r->location = p;
103     return S_OK;
104 }
105 
106 static HRESULT report_result(MimeHtmlProtocol *protocol, HRESULT result)
107 {
108     if(protocol->sink) {
109         IInternetProtocolSink_ReportResult(protocol->sink, result, ERROR_SUCCESS, NULL);
110         IInternetProtocolSink_Release(protocol->sink);
111         protocol->sink = NULL;
112     }
113 
114     return result;
115 }
116 
117 static HRESULT on_mime_message_available(MimeHtmlProtocol *protocol, IMimeMessage *mime_message)
118 {
119     FINDBODY find = {NULL};
120     IMimeBody *mime_body;
121     PROPVARIANT value;
122     HBODY body;
123     HRESULT hres;
124 
125     hres = IMimeMessage_FindFirst(mime_message, &find, &body);
126     if(FAILED(hres))
127         return report_result(protocol, hres);
128 
129     if(protocol->location) {
130         BOOL found = FALSE;
131         do {
132             hres = IMimeMessage_FindNext(mime_message, &find, &body);
133             if(FAILED(hres)) {
134                 WARN("location %s not found\n", debugstr_w(protocol->location));
135                 return report_result(protocol, hres);
136             }
137 
138             value.vt = VT_LPWSTR;
139             hres = IMimeMessage_GetBodyProp(mime_message, body, "content-location", 0, &value);
140             if(hres == MIME_E_NOT_FOUND)
141                 continue;
142             if(FAILED(hres))
143                 return report_result(protocol, hres);
144 
145             found = !strcmpW(protocol->location, value.u.pwszVal);
146             PropVariantClear(&value);
147         }while(!found);
148     }else {
149         hres = IMimeMessage_FindNext(mime_message, &find, &body);
150         if(FAILED(hres)) {
151             WARN("location %s not found\n", debugstr_w(protocol->location));
152             return report_result(protocol, hres);
153         }
154     }
155 
156     hres = IMimeMessage_BindToObject(mime_message, body, &IID_IMimeBody, (void**)&mime_body);
157     if(FAILED(hres))
158         return report_result(protocol, hres);
159 
160     value.vt = VT_LPWSTR;
161     hres = IMimeBody_GetProp(mime_body, "content-type", 0, &value);
162     if(SUCCEEDED(hres)) {
163         hres = IInternetProtocolSink_ReportProgress(protocol->sink, BINDSTATUS_MIMETYPEAVAILABLE, value.u.pwszVal);
164         PropVariantClear(&value);
165     }
166 
167     /* FIXME: Create and report cache file. */
168 
169     hres = IMimeBody_GetData(mime_body, IET_DECODED, &protocol->stream);
170     if(FAILED(hres))
171         return report_result(protocol, hres);
172 
173     IInternetProtocolSink_ReportData(protocol->sink, BSCF_FIRSTDATANOTIFICATION
174                                      | BSCF_INTERMEDIATEDATANOTIFICATION
175                                      | BSCF_LASTDATANOTIFICATION
176                                      | BSCF_DATAFULLYAVAILABLE
177                                      | BSCF_AVAILABLEDATASIZEUNKNOWN, 0, 0);
178 
179     return report_result(protocol, S_OK);
180 }
181 
182 static HRESULT load_mime_message(IStream *stream, IMimeMessage **ret)
183 {
184     IMimeMessage *mime_message;
185     HRESULT hres;
186 
187     hres = MimeMessage_create(NULL, (void**)&mime_message);
188     if(FAILED(hres))
189         return hres;
190 
191     IMimeMessage_InitNew(mime_message);
192 
193     hres = IMimeMessage_Load(mime_message, stream);
194     if(FAILED(hres)) {
195         IMimeMessage_Release(mime_message);
196         return hres;
197     }
198 
199     *ret = mime_message;
200     return S_OK;
201 }
202 
203 static inline MimeHtmlBinding *impl_from_IBindStatusCallback(IBindStatusCallback *iface)
204 {
205     return CONTAINING_RECORD(iface, MimeHtmlBinding, IBindStatusCallback_iface);
206 }
207 
208 static HRESULT WINAPI BindStatusCallback_QueryInterface(IBindStatusCallback *iface,
209         REFIID riid, void **ppv)
210 {
211     MimeHtmlBinding *This = impl_from_IBindStatusCallback(iface);
212 
213     if(IsEqualGUID(&IID_IUnknown, riid)) {
214         TRACE("(%p)->(IID_IUnknown %p)\n", This, ppv);
215         *ppv = &This->IBindStatusCallback_iface;
216     }else if(IsEqualGUID(&IID_IBindStatusCallback, riid)) {
217         TRACE("(%p)->(IID_IBindStatusCallback %p)\n", This, ppv);
218         *ppv = &This->IBindStatusCallback_iface;
219     }else {
220         TRACE("(%p)->(%s %p)\n", This, debugstr_guid(riid), ppv);
221         *ppv = NULL;
222         return E_NOINTERFACE;
223     }
224 
225     IUnknown_AddRef((IUnknown*)*ppv);
226     return S_OK;
227 }
228 
229 static ULONG WINAPI BindStatusCallback_AddRef(IBindStatusCallback *iface)
230 {
231     MimeHtmlBinding *This = impl_from_IBindStatusCallback(iface);
232     LONG ref = InterlockedIncrement(&This->ref);
233 
234     TRACE("(%p) ref=%d\n", This, ref);
235 
236     return ref;
237 }
238 
239 static ULONG WINAPI BindStatusCallback_Release(IBindStatusCallback *iface)
240 {
241     MimeHtmlBinding *This = impl_from_IBindStatusCallback(iface);
242     LONG ref = InterlockedDecrement(&This->ref);
243 
244     TRACE("(%p) ref=%d\n", This, ref);
245 
246     if(!ref) {
247         if(This->protocol)
248             IInternetProtocol_Release(&This->protocol->IInternetProtocol_iface);
249         if(This->stream)
250             IStream_Release(This->stream);
251         heap_free(This);
252     }
253 
254     return ref;
255 }
256 
257 static HRESULT WINAPI BindStatusCallback_OnStartBinding(IBindStatusCallback *iface,
258         DWORD dwReserved, IBinding *pib)
259 {
260     MimeHtmlBinding *This = impl_from_IBindStatusCallback(iface);
261 
262     TRACE("(%p)->(%x %p)\n", This, dwReserved, pib);
263 
264     assert(!This->stream);
265     return CreateStreamOnHGlobal(NULL, TRUE, &This->stream);
266 }
267 
268 static HRESULT WINAPI BindStatusCallback_GetPriority(IBindStatusCallback *iface, LONG *pnPriority)
269 {
270     return E_NOTIMPL;
271 }
272 
273 static HRESULT WINAPI BindStatusCallback_OnLowResource(IBindStatusCallback *iface, DWORD dwReserved)
274 {
275     return E_NOTIMPL;
276 }
277 
278 static HRESULT WINAPI BindStatusCallback_OnProgress(IBindStatusCallback *iface, ULONG ulProgress,
279         ULONG ulProgressMax, ULONG ulStatusCode, LPCWSTR szStatusText)
280 {
281     MimeHtmlBinding *This = impl_from_IBindStatusCallback(iface);
282     TRACE("(%p)->(%u/%u %u %s)\n", This, ulProgress, ulProgressMax, ulStatusCode, debugstr_w(szStatusText));
283     return S_OK;
284 }
285 
286 static HRESULT WINAPI BindStatusCallback_OnStopBinding(IBindStatusCallback *iface, HRESULT hresult, LPCWSTR szError)
287 {
288     MimeHtmlBinding *This = impl_from_IBindStatusCallback(iface);
289     IMimeMessage *mime_message = NULL;
290 
291     TRACE("(%p)->(%x %s)\n", This, hresult, debugstr_w(szError));
292 
293     if(SUCCEEDED(hresult)) {
294         hresult = load_mime_message(This->stream, &mime_message);
295         IStream_Release(This->stream);
296         This->stream = NULL;
297     }
298 
299     This->status = hresult;
300 
301     if(mime_message)
302         on_mime_message_available(This->protocol, mime_message);
303     else
304         report_result(This->protocol, hresult);
305 
306     if(mime_message)
307         IMimeMessage_Release(mime_message);
308     IInternetProtocol_Release(&This->protocol->IInternetProtocol_iface);
309     This->protocol = NULL;
310     return S_OK;
311 }
312 
313 static HRESULT WINAPI BindStatusCallback_GetBindInfo(IBindStatusCallback *iface,
314         DWORD* grfBINDF, BINDINFO* pbindinfo)
315 {
316     MimeHtmlBinding *This = impl_from_IBindStatusCallback(iface);
317 
318     TRACE("(%p)\n", This);
319 
320     *grfBINDF = BINDF_ASYNCHRONOUS;
321     return S_OK;
322 }
323 
324 static HRESULT WINAPI BindStatusCallback_OnDataAvailable(IBindStatusCallback *iface, DWORD grfBSCF,
325         DWORD dwSize, FORMATETC* pformatetc, STGMEDIUM* pstgmed)
326 {
327     MimeHtmlBinding *This = impl_from_IBindStatusCallback(iface);
328     BYTE buf[4*1024];
329     DWORD read;
330     HRESULT hres;
331 
332     TRACE("(%p)\n", This);
333 
334     assert(pstgmed->tymed == TYMED_ISTREAM);
335 
336     while(1) {
337         hres = IStream_Read(pstgmed->u.pstm, buf, sizeof(buf), &read);
338         if(FAILED(hres))
339             return hres;
340         if(!read)
341             break;
342         hres = IStream_Write(This->stream, buf, read, NULL);
343         if(FAILED(hres))
344             return hres;
345     }
346     return S_OK;
347 }
348 
349 static HRESULT WINAPI BindStatusCallback_OnObjectAvailable(IBindStatusCallback *iface,
350         REFIID riid, IUnknown* punk)
351 {
352     ERR("\n");
353     return E_NOTIMPL;
354 }
355 
356 static const IBindStatusCallbackVtbl BindStatusCallbackVtbl = {
357     BindStatusCallback_QueryInterface,
358     BindStatusCallback_AddRef,
359     BindStatusCallback_Release,
360     BindStatusCallback_OnStartBinding,
361     BindStatusCallback_GetPriority,
362     BindStatusCallback_OnLowResource,
363     BindStatusCallback_OnProgress,
364     BindStatusCallback_OnStopBinding,
365     BindStatusCallback_GetBindInfo,
366     BindStatusCallback_OnDataAvailable,
367     BindStatusCallback_OnObjectAvailable
368 };
369 
370 static inline MimeHtmlProtocol *impl_from_IUnknown(IUnknown *iface)
371 {
372     return CONTAINING_RECORD(iface, MimeHtmlProtocol, IUnknown_inner);
373 }
374 
375 static HRESULT WINAPI MimeHtmlProtocol_QueryInterface(IUnknown *iface, REFIID riid, void **ppv)
376 {
377     MimeHtmlProtocol *This = impl_from_IUnknown(iface);
378 
379     if(IsEqualGUID(&IID_IUnknown, riid)) {
380         TRACE("(%p)->(IID_IUnknown %p)\n", This, ppv);
381         *ppv = &This->IInternetProtocol_iface;
382     }else if(IsEqualGUID(&IID_IInternetProtocolRoot, riid)) {
383         TRACE("(%p)->(IID_IInternetProtocolRoot %p)\n", This, ppv);
384         *ppv = &This->IInternetProtocol_iface;
385     }else if(IsEqualGUID(&IID_IInternetProtocol, riid)) {
386         TRACE("(%p)->(IID_IInternetProtocol %p)\n", This, ppv);
387         *ppv = &This->IInternetProtocol_iface;
388     }else if(IsEqualGUID(&IID_IInternetProtocolInfo, riid)) {
389         TRACE("(%p)->(IID_IInternetProtocolInfo %p)\n", This, ppv);
390         *ppv = &This->IInternetProtocolInfo_iface;
391     }else {
392         FIXME("unknown interface %s\n", debugstr_guid(riid));
393         *ppv = NULL;
394         return E_NOINTERFACE;
395     }
396 
397     IUnknown_AddRef((IUnknown*)*ppv);
398     return S_OK;
399 }
400 
401 static ULONG WINAPI MimeHtmlProtocol_AddRef(IUnknown *iface)
402 {
403     MimeHtmlProtocol *This = impl_from_IUnknown(iface);
404     ULONG ref = InterlockedIncrement(&This->ref);
405 
406     TRACE("(%p) ref=%d\n", This, ref);
407 
408     return ref;
409 }
410 
411 static ULONG WINAPI MimeHtmlProtocol_Release(IUnknown *iface)
412 {
413     MimeHtmlProtocol *This = impl_from_IUnknown(iface);
414     ULONG ref = InterlockedDecrement(&This->ref);
415 
416     TRACE("(%p) ref=%x\n", This, ref);
417 
418     if(!ref) {
419         if(This->sink)
420             IInternetProtocolSink_Release(This->sink);
421         if(This->stream)
422             IStream_Release(This->stream);
423         heap_free(This->location);
424         heap_free(This);
425     }
426 
427     return ref;
428 }
429 
430 static const IUnknownVtbl MimeHtmlProtocolInnerVtbl = {
431     MimeHtmlProtocol_QueryInterface,
432     MimeHtmlProtocol_AddRef,
433     MimeHtmlProtocol_Release
434 };
435 
436 static inline MimeHtmlProtocol *impl_from_IInternetProtocol(IInternetProtocol *iface)
437 {
438     return CONTAINING_RECORD(iface, MimeHtmlProtocol, IInternetProtocol_iface);
439 }
440 
441 static HRESULT WINAPI InternetProtocol_QueryInterface(IInternetProtocol *iface, REFIID riid, void **ppv)
442 {
443     MimeHtmlProtocol *This = impl_from_IInternetProtocol(iface);
444     return IUnknown_QueryInterface(This->outer_unk, riid, ppv);
445 }
446 
447 static ULONG WINAPI InternetProtocol_AddRef(IInternetProtocol *iface)
448 {
449     MimeHtmlProtocol *This = impl_from_IInternetProtocol(iface);
450     return IUnknown_AddRef(This->outer_unk);
451 }
452 
453 static ULONG WINAPI InternetProtocol_Release(IInternetProtocol *iface)
454 {
455     MimeHtmlProtocol *This = impl_from_IInternetProtocol(iface);
456     return IUnknown_Release(This->outer_unk);
457 }
458 
459 static HRESULT WINAPI MimeHtmlProtocol_Start(IInternetProtocol *iface, const WCHAR *szUrl,
460         IInternetProtocolSink* pOIProtSink, IInternetBindInfo* pOIBindInfo,
461         DWORD grfPI, HANDLE_PTR dwReserved)
462 {
463     MimeHtmlProtocol *This = impl_from_IInternetProtocol(iface);
464     BINDINFO bindinfo = { sizeof(bindinfo) };
465     MimeHtmlBinding *binding;
466     IBindCtx *bind_ctx;
467     IStream *stream;
468     mhtml_url_t url;
469     DWORD bindf = 0;
470     IMoniker *mon;
471     HRESULT hres;
472 
473     TRACE("(%p)->(%s %p %p %08x %lx)\n", This, debugstr_w(szUrl), pOIProtSink, pOIBindInfo, grfPI, dwReserved);
474 
475     hres = parse_mhtml_url(szUrl, &url);
476     if(FAILED(hres))
477         return hres;
478 
479     if(url.location && !(This->location = heap_strdupW(url.location)))
480         return E_OUTOFMEMORY;
481 
482     hres = IInternetBindInfo_GetBindInfo(pOIBindInfo, &bindf, &bindinfo);
483     if(FAILED(hres)) {
484         WARN("GetBindInfo failed: %08x\n", hres);
485         return hres;
486     }
487     if((bindf & (BINDF_ASYNCHRONOUS|BINDF_FROMURLMON|BINDF_NEEDFILE)) != (BINDF_ASYNCHRONOUS|BINDF_FROMURLMON|BINDF_NEEDFILE))
488         FIXME("unsupported bindf %x\n", bindf);
489 
490     IInternetProtocolSink_AddRef(This->sink = pOIProtSink);
491 
492     binding = heap_alloc(FIELD_OFFSET(MimeHtmlBinding,  url[url.mhtml_len+1]));
493     if(!binding)
494         return E_OUTOFMEMORY;
495     memcpy(binding->url, url.mhtml, url.mhtml_len*sizeof(WCHAR));
496     binding->url[url.mhtml_len] = 0;
497 
498     hres = CreateURLMoniker(NULL, binding->url, &mon);
499     if(FAILED(hres)) {
500         heap_free(binding);
501         return hres;
502     }
503 
504     binding->IBindStatusCallback_iface.lpVtbl = &BindStatusCallbackVtbl;
505     binding->ref = 1;
506     binding->status = E_PENDING;
507     binding->stream = NULL;
508     binding->protocol = NULL;
509 
510     hres = CreateAsyncBindCtx(0, &binding->IBindStatusCallback_iface, NULL, &bind_ctx);
511     if(FAILED(hres)) {
512         IMoniker_Release(mon);
513         IBindStatusCallback_Release(&binding->IBindStatusCallback_iface);
514         return hres;
515     }
516 
517     IInternetProtocol_AddRef(&This->IInternetProtocol_iface);
518     binding->protocol = This;
519 
520     hres = IMoniker_BindToStorage(mon, bind_ctx, NULL, &IID_IStream, (void**)&stream);
521     IBindCtx_Release(bind_ctx);
522     IMoniker_Release(mon);
523     if(stream)
524         IStream_Release(stream);
525     hres = binding->status;
526     IBindStatusCallback_Release(&binding->IBindStatusCallback_iface);
527     if(FAILED(hres) && hres != E_PENDING)
528         report_result(This, hres);
529     return hres;
530 }
531 
532 static HRESULT WINAPI MimeHtmlProtocol_Continue(IInternetProtocol *iface, PROTOCOLDATA *pProtocolData)
533 {
534     MimeHtmlProtocol *This = impl_from_IInternetProtocol(iface);
535     FIXME("(%p)->(%p)\n", This, pProtocolData);
536     return E_NOTIMPL;
537 }
538 
539 static HRESULT WINAPI MimeHtmlProtocol_Abort(IInternetProtocol *iface, HRESULT hrReason, DWORD dwOptions)
540 {
541     MimeHtmlProtocol *This = impl_from_IInternetProtocol(iface);
542     FIXME("(%p)->(%08x %08x)\n", This, hrReason, dwOptions);
543     return E_NOTIMPL;
544 }
545 
546 static HRESULT WINAPI MimeHtmlProtocol_Terminate(IInternetProtocol *iface, DWORD dwOptions)
547 {
548     MimeHtmlProtocol *This = impl_from_IInternetProtocol(iface);
549     TRACE("(%p)->(%08x)\n", This, dwOptions);
550     return S_OK;
551 }
552 
553 static HRESULT WINAPI MimeHtmlProtocol_Suspend(IInternetProtocol *iface)
554 {
555     MimeHtmlProtocol *This = impl_from_IInternetProtocol(iface);
556     FIXME("(%p)\n", This);
557     return E_NOTIMPL;
558 }
559 
560 static HRESULT WINAPI MimeHtmlProtocol_Resume(IInternetProtocol *iface)
561 {
562     MimeHtmlProtocol *This = impl_from_IInternetProtocol(iface);
563     FIXME("(%p)\n", This);
564     return E_NOTIMPL;
565 }
566 
567 static HRESULT WINAPI MimeHtmlProtocol_Read(IInternetProtocol *iface, void* pv, ULONG cb, ULONG* pcbRead)
568 {
569     MimeHtmlProtocol *This = impl_from_IInternetProtocol(iface);
570     ULONG read = 0;
571     HRESULT hres;
572 
573     TRACE("(%p)->(%p %u %p)\n", This, pv, cb, pcbRead);
574 
575     hres = IStream_Read(This->stream, pv, cb, &read);
576     if(pcbRead)
577         *pcbRead = read;
578     if(hres != S_OK)
579         return hres;
580 
581     return read ? S_OK : S_FALSE;
582 }
583 
584 static HRESULT WINAPI MimeHtmlProtocol_Seek(IInternetProtocol *iface, LARGE_INTEGER dlibMove,
585         DWORD dwOrigin, ULARGE_INTEGER* plibNewPosition)
586 {
587     MimeHtmlProtocol *This = impl_from_IInternetProtocol(iface);
588     FIXME("(%p)->(%d %d %p)\n", This, dlibMove.u.LowPart, dwOrigin, plibNewPosition);
589     return E_NOTIMPL;
590 }
591 
592 static HRESULT WINAPI MimeHtmlProtocol_LockRequest(IInternetProtocol *iface, DWORD dwOptions)
593 {
594     MimeHtmlProtocol *This = impl_from_IInternetProtocol(iface);
595     FIXME("(%p)->(%d)\n", This, dwOptions);
596     return S_OK;
597 }
598 
599 static HRESULT WINAPI MimeHtmlProtocol_UnlockRequest(IInternetProtocol *iface)
600 {
601     MimeHtmlProtocol *This = impl_from_IInternetProtocol(iface);
602     FIXME("(%p)\n", This);
603     return S_OK;
604 }
605 
606 static const IInternetProtocolVtbl MimeHtmlProtocolVtbl = {
607     InternetProtocol_QueryInterface,
608     InternetProtocol_AddRef,
609     InternetProtocol_Release,
610     MimeHtmlProtocol_Start,
611     MimeHtmlProtocol_Continue,
612     MimeHtmlProtocol_Abort,
613     MimeHtmlProtocol_Terminate,
614     MimeHtmlProtocol_Suspend,
615     MimeHtmlProtocol_Resume,
616     MimeHtmlProtocol_Read,
617     MimeHtmlProtocol_Seek,
618     MimeHtmlProtocol_LockRequest,
619     MimeHtmlProtocol_UnlockRequest
620 };
621 
622 static inline MimeHtmlProtocol *impl_from_IInternetProtocolInfo(IInternetProtocolInfo *iface)
623 {
624     return CONTAINING_RECORD(iface, MimeHtmlProtocol, IInternetProtocolInfo_iface);
625 }
626 
627 static HRESULT WINAPI MimeHtmlProtocolInfo_QueryInterface(IInternetProtocolInfo *iface, REFIID riid, void **ppv)
628 {
629     MimeHtmlProtocol *This = impl_from_IInternetProtocolInfo(iface);
630     return IUnknown_QueryInterface(This->outer_unk, riid, ppv);
631 }
632 
633 static ULONG WINAPI MimeHtmlProtocolInfo_AddRef(IInternetProtocolInfo *iface)
634 {
635     MimeHtmlProtocol *This = impl_from_IInternetProtocolInfo(iface);
636     return IUnknown_AddRef(This->outer_unk);
637 }
638 
639 static ULONG WINAPI MimeHtmlProtocolInfo_Release(IInternetProtocolInfo *iface)
640 {
641     MimeHtmlProtocol *This = impl_from_IInternetProtocolInfo(iface);
642     return IUnknown_Release(This->outer_unk);
643 }
644 
645 static HRESULT WINAPI MimeHtmlProtocolInfo_ParseUrl(IInternetProtocolInfo *iface, LPCWSTR pwzUrl,
646         PARSEACTION ParseAction, DWORD dwParseFlags, LPWSTR pwzResult, DWORD cchResult,
647         DWORD* pcchResult, DWORD dwReserved)
648 {
649     MimeHtmlProtocol *This = impl_from_IInternetProtocolInfo(iface);
650     FIXME("(%p)->(%s %d %x %p %d %p %d)\n", This, debugstr_w(pwzUrl), ParseAction,
651           dwParseFlags, pwzResult, cchResult, pcchResult, dwReserved);
652     return INET_E_DEFAULT_ACTION;
653 }
654 
655 static HRESULT WINAPI MimeHtmlProtocolInfo_CombineUrl(IInternetProtocolInfo *iface,
656         LPCWSTR pwzBaseUrl, LPCWSTR pwzRelativeUrl, DWORD dwCombineFlags, LPWSTR pwzResult,
657         DWORD cchResult, DWORD* pcchResult, DWORD dwReserved)
658 {
659     MimeHtmlProtocol *This = impl_from_IInternetProtocolInfo(iface);
660     size_t len = ARRAY_SIZE(mhtml_prefixW);
661     mhtml_url_t url;
662     WCHAR *p;
663     HRESULT hres;
664 
665     TRACE("(%p)->(%s %s %08x %p %d %p %d)\n", This, debugstr_w(pwzBaseUrl),
666           debugstr_w(pwzRelativeUrl), dwCombineFlags, pwzResult, cchResult,
667           pcchResult, dwReserved);
668 
669     hres = parse_mhtml_url(pwzBaseUrl, &url);
670     if(FAILED(hres))
671         return hres;
672 
673     if(!strncmpiW(pwzRelativeUrl, mhtml_prefixW, ARRAY_SIZE(mhtml_prefixW))) {
674         FIXME("Relative URL is mhtml protocol\n");
675         return INET_E_USE_DEFAULT_PROTOCOLHANDLER;
676     }
677 
678     len += url.mhtml_len;
679     if(*pwzRelativeUrl)
680         len += strlenW(pwzRelativeUrl) + ARRAY_SIZE(mhtml_separatorW);
681     if(len >= cchResult) {
682         *pcchResult = 0;
683         return E_FAIL;
684     }
685 
686     memcpy(pwzResult, mhtml_prefixW, sizeof(mhtml_prefixW));
687     p = pwzResult + ARRAY_SIZE(mhtml_prefixW);
688     memcpy(p, url.mhtml, url.mhtml_len*sizeof(WCHAR));
689     p += url.mhtml_len;
690     if(*pwzRelativeUrl) {
691         memcpy(p, mhtml_separatorW, sizeof(mhtml_separatorW));
692         p += ARRAY_SIZE(mhtml_separatorW);
693         strcpyW(p, pwzRelativeUrl);
694     }else {
695         *p = 0;
696     }
697 
698     *pcchResult = len;
699     return S_OK;
700 }
701 
702 static HRESULT WINAPI MimeHtmlProtocolInfo_CompareUrl(IInternetProtocolInfo *iface, LPCWSTR pwzUrl1,
703         LPCWSTR pwzUrl2, DWORD dwCompareFlags)
704 {
705     MimeHtmlProtocol *This = impl_from_IInternetProtocolInfo(iface);
706     FIXME("(%p)->(%s %s %08x)\n", This, debugstr_w(pwzUrl1), debugstr_w(pwzUrl2), dwCompareFlags);
707     return E_NOTIMPL;
708 }
709 
710 static HRESULT WINAPI MimeHtmlProtocolInfo_QueryInfo(IInternetProtocolInfo *iface, LPCWSTR pwzUrl,
711         QUERYOPTION QueryOption, DWORD dwQueryFlags, LPVOID pBuffer, DWORD cbBuffer, DWORD* pcbBuf,
712         DWORD dwReserved)
713 {
714     MimeHtmlProtocol *This = impl_from_IInternetProtocolInfo(iface);
715     FIXME("(%p)->(%s %08x %08x %p %d %p %d)\n", This, debugstr_w(pwzUrl), QueryOption, dwQueryFlags, pBuffer,
716           cbBuffer, pcbBuf, dwReserved);
717     return INET_E_USE_DEFAULT_PROTOCOLHANDLER;
718 }
719 
720 static const IInternetProtocolInfoVtbl MimeHtmlProtocolInfoVtbl = {
721     MimeHtmlProtocolInfo_QueryInterface,
722     MimeHtmlProtocolInfo_AddRef,
723     MimeHtmlProtocolInfo_Release,
724     MimeHtmlProtocolInfo_ParseUrl,
725     MimeHtmlProtocolInfo_CombineUrl,
726     MimeHtmlProtocolInfo_CompareUrl,
727     MimeHtmlProtocolInfo_QueryInfo
728 };
729 
730 HRESULT MimeHtmlProtocol_create(IUnknown *outer, void **obj)
731 {
732     MimeHtmlProtocol *protocol;
733 
734     protocol = heap_alloc(sizeof(*protocol));
735     if(!protocol)
736         return E_OUTOFMEMORY;
737 
738     protocol->IUnknown_inner.lpVtbl = &MimeHtmlProtocolInnerVtbl;
739     protocol->IInternetProtocol_iface.lpVtbl = &MimeHtmlProtocolVtbl;
740     protocol->IInternetProtocolInfo_iface.lpVtbl = &MimeHtmlProtocolInfoVtbl;
741     protocol->ref = 1;
742     protocol->outer_unk = outer ? outer : &protocol->IUnknown_inner;
743     protocol->location = NULL;
744     protocol->stream = NULL;
745     protocol->sink = NULL;
746 
747     *obj = &protocol->IUnknown_inner;
748     return S_OK;
749 }
750