1/*
2                            __  __            _
3                         ___\ \/ /_ __   __ _| |_
4                        / _ \\  /| '_ \ / _` | __|
5                       |  __//  \| |_) | (_| | |_
6                        \___/_/\_\ .__/ \__,_|\__|
7                                 |_| XML parser
8
9   Copyright (c) 1997-2000 Thai Open Source Software Center Ltd
10   Copyright (c) 2000-2017 Expat development team
11   Licensed under the MIT license:
12
13   Permission is  hereby granted,  free of charge,  to any  person obtaining
14   a  copy  of  this  software   and  associated  documentation  files  (the
15   "Software"),  to  deal in  the  Software  without restriction,  including
16   without  limitation the  rights  to use,  copy,  modify, merge,  publish,
17   distribute, sublicense, and/or sell copies of the Software, and to permit
18   persons  to whom  the Software  is  furnished to  do so,  subject to  the
19   following conditions:
20
21   The above copyright  notice and this permission notice  shall be included
22   in all copies or substantial portions of the Software.
23
24   THE  SOFTWARE  IS  PROVIDED  "AS  IS",  WITHOUT  WARRANTY  OF  ANY  KIND,
25   EXPRESS  OR IMPLIED,  INCLUDING  BUT  NOT LIMITED  TO  THE WARRANTIES  OF
26   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
27   NO EVENT SHALL THE AUTHORS OR  COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
28   DAMAGES OR  OTHER LIABILITY, WHETHER  IN AN  ACTION OF CONTRACT,  TORT OR
29   OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
30   USE OR OTHER DEALINGS IN THE SOFTWARE.
31*/
32
33#include "expat.h"
34#ifdef XML_UNICODE
35#define UNICODE
36#endif
37#include <windows.h>
38#include <urlmon.h>
39#include <wininet.h>
40#include <stdio.h>
41#include <tchar.h>
42#include "xmlurl.h"
43#include "xmlmime.h"
44
45static int
46processURL(XML_Parser parser, IMoniker *baseMoniker, const XML_Char *url);
47
48typedef void (*StopHandler)(void *, HRESULT);
49
50class Callback : public IBindStatusCallback {
51public:
52  // IUnknown methods
53  STDMETHODIMP QueryInterface(REFIID,void **);
54  STDMETHODIMP_(ULONG) AddRef();
55  STDMETHODIMP_(ULONG) Release();
56  // IBindStatusCallback methods
57  STDMETHODIMP OnStartBinding(DWORD, IBinding *);
58  STDMETHODIMP GetPriority(LONG *);
59  STDMETHODIMP OnLowResource(DWORD);
60  STDMETHODIMP OnProgress(ULONG, ULONG, ULONG, LPCWSTR);
61  STDMETHODIMP OnStopBinding(HRESULT, LPCWSTR);
62  STDMETHODIMP GetBindInfo(DWORD *, BINDINFO *);
63  STDMETHODIMP OnDataAvailable(DWORD, DWORD, FORMATETC *, STGMEDIUM *);
64  STDMETHODIMP OnObjectAvailable(REFIID, IUnknown *);
65  Callback(XML_Parser, IMoniker *, StopHandler, void *);
66  ~Callback();
67  int externalEntityRef(const XML_Char *context,
68                        const XML_Char *systemId, const XML_Char *publicId);
69private:
70  XML_Parser parser_;
71  IMoniker *baseMoniker_;
72  DWORD totalRead_;
73  ULONG ref_;
74  IBinding *pBinding_;
75  StopHandler stopHandler_;
76  void *stopArg_;
77};
78
79STDMETHODIMP_(ULONG)
80Callback::AddRef()
81{
82  return ref_++;
83}
84
85STDMETHODIMP_(ULONG)
86Callback::Release()
87{
88  if (--ref_ == 0) {
89    delete this;
90    return 0;
91  }
92  return ref_;
93}
94
95STDMETHODIMP
96Callback::QueryInterface(REFIID riid, void** ppv)
97{
98  if (IsEqualGUID(riid, IID_IUnknown))
99    *ppv = (IUnknown *)this;
100  else if (IsEqualGUID(riid, IID_IBindStatusCallback))
101    *ppv = (IBindStatusCallback *)this;
102  else
103    return E_NOINTERFACE;
104  ((LPUNKNOWN)*ppv)->AddRef();
105  return S_OK;
106}
107
108STDMETHODIMP
109Callback::OnStartBinding(DWORD, IBinding* pBinding)
110{
111  pBinding_ = pBinding;
112  pBinding->AddRef();
113  return S_OK;
114}
115
116STDMETHODIMP
117Callback::GetPriority(LONG *)
118{
119  return E_NOTIMPL;
120}
121
122STDMETHODIMP
123Callback::OnLowResource(DWORD)
124{
125  return E_NOTIMPL;
126}
127
128STDMETHODIMP
129Callback::OnProgress(ULONG, ULONG, ULONG, LPCWSTR)
130{
131  return S_OK;
132}
133
134STDMETHODIMP
135Callback::OnStopBinding(HRESULT hr, LPCWSTR szError)
136{
137  if (pBinding_) {
138    pBinding_->Release();
139    pBinding_ = 0;
140  }
141  if (baseMoniker_) {
142    baseMoniker_->Release();
143    baseMoniker_ = 0;
144  }
145  stopHandler_(stopArg_, hr);
146  return S_OK;
147}
148
149STDMETHODIMP
150Callback::GetBindInfo(DWORD* pgrfBINDF, BINDINFO* pbindinfo)
151{
152  *pgrfBINDF = BINDF_ASYNCHRONOUS;
153  return S_OK;
154}
155
156static void
157reportError(XML_Parser parser)
158{
159  int code = XML_GetErrorCode(parser);
160  const XML_Char *message = XML_ErrorString(code);
161  if (message)
162    _ftprintf(stderr, _T("%s:%d:%ld: %s\n"),
163	     XML_GetBase(parser),
164	     XML_GetErrorLineNumber(parser),
165	     XML_GetErrorColumnNumber(parser),
166	     message);
167  else
168    _ftprintf(stderr, _T("%s: (unknown message %d)\n"),
169              XML_GetBase(parser), code);
170}
171
172STDMETHODIMP
173Callback::OnDataAvailable(DWORD grfBSCF,
174                          DWORD dwSize,
175                          FORMATETC *pfmtetc,
176                          STGMEDIUM* pstgmed)
177{
178  if (grfBSCF & BSCF_FIRSTDATANOTIFICATION) {
179    IWinInetHttpInfo *hp;
180    HRESULT hr = pBinding_->QueryInterface(IID_IWinInetHttpInfo,
181                                           (void **)&hp);
182    if (SUCCEEDED(hr)) {
183      char contentType[1024];
184      DWORD bufSize = sizeof(contentType);
185      DWORD flags = 0;
186      contentType[0] = 0;
187      hr = hp->QueryInfo(HTTP_QUERY_CONTENT_TYPE, contentType,
188                         &bufSize, 0, NULL);
189      if (SUCCEEDED(hr)) {
190	char charset[CHARSET_MAX];
191	getXMLCharset(contentType, charset);
192	if (charset[0]) {
193#ifdef XML_UNICODE
194	  XML_Char wcharset[CHARSET_MAX];
195	  XML_Char *p1 = wcharset;
196	  const char *p2 = charset;
197	  while ((*p1++ = (unsigned char)*p2++) != 0)
198	    ;
199	  XML_SetEncoding(parser_, wcharset);
200#else
201	  XML_SetEncoding(parser_, charset);
202#endif
203	}
204      }
205      hp->Release();
206    }
207  }
208  if (!parser_)
209    return E_ABORT;
210  if (pstgmed->tymed == TYMED_ISTREAM) {
211    while (totalRead_ < dwSize) {
212#define READ_MAX (64*1024)
213      DWORD nToRead = dwSize - totalRead_;
214      if (nToRead > READ_MAX)
215	nToRead = READ_MAX;
216      void *buf = XML_GetBuffer(parser_, nToRead);
217      if (!buf) {
218	_ftprintf(stderr, _T("out of memory\n"));
219	return E_ABORT;
220      }
221      DWORD nRead;
222      HRESULT hr = pstgmed->pstm->Read(buf, nToRead, &nRead);
223      if (SUCCEEDED(hr)) {
224	totalRead_ += nRead;
225	if (!XML_ParseBuffer(parser_,
226			     nRead,
227			     (grfBSCF & BSCF_LASTDATANOTIFICATION) != 0
228			     && totalRead_ == dwSize)) {
229	  reportError(parser_);
230	  return E_ABORT;
231	}
232      }
233    }
234  }
235  return S_OK;
236}
237
238STDMETHODIMP
239Callback::OnObjectAvailable(REFIID, IUnknown *)
240{
241  return S_OK;
242}
243
244int
245Callback::externalEntityRef(const XML_Char *context,
246                            const XML_Char *systemId,
247                            const XML_Char *publicId)
248{
249  XML_Parser entParser = XML_ExternalEntityParserCreate(parser_, context, 0);
250  XML_SetBase(entParser, systemId);
251  int ret = processURL(entParser, baseMoniker_, systemId);
252  XML_ParserFree(entParser);
253  return ret;
254}
255
256Callback::Callback(XML_Parser parser, IMoniker *baseMoniker,
257                   StopHandler stopHandler, void *stopArg)
258: parser_(parser),
259  baseMoniker_(baseMoniker),
260  ref_(0),
261  pBinding_(0),
262  totalRead_(0),
263  stopHandler_(stopHandler),
264  stopArg_(stopArg)
265{
266  if (baseMoniker_)
267    baseMoniker_->AddRef();
268}
269
270Callback::~Callback()
271{
272  if (pBinding_)
273    pBinding_->Release();
274  if (baseMoniker_)
275    baseMoniker_->Release();
276}
277
278static int
279externalEntityRef(void *arg,
280                  const XML_Char *context,
281                  const XML_Char *base,
282                  const XML_Char *systemId,
283                  const XML_Char *publicId)
284{
285  return ((Callback *)arg)->externalEntityRef(context, systemId, publicId);
286}
287
288
289static HRESULT
290openStream(XML_Parser parser,
291           IMoniker *baseMoniker,
292           const XML_Char *uri,
293           StopHandler stopHandler, void *stopArg)
294{
295  if (!XML_SetBase(parser, uri))
296    return E_OUTOFMEMORY;
297  HRESULT hr;
298  IMoniker *m;
299#ifdef XML_UNICODE
300  hr = CreateURLMoniker(0, uri, &m);
301#else
302  LPWSTR uriw = new wchar_t[strlen(uri) + 1];
303  for (int i = 0;; i++) {
304    uriw[i] = uri[i];
305    if (uriw[i] == 0)
306      break;
307  }
308  hr = CreateURLMoniker(baseMoniker, uriw, &m);
309  delete [] uriw;
310#endif
311  if (FAILED(hr))
312    return hr;
313  IBindStatusCallback *cb = new Callback(parser, m, stopHandler, stopArg);
314  XML_SetExternalEntityRefHandler(parser, externalEntityRef);
315  XML_SetExternalEntityRefHandlerArg(parser, cb);
316  cb->AddRef();
317  IBindCtx *b;
318  if (FAILED(hr = CreateAsyncBindCtx(0, cb, 0, &b))) {
319    cb->Release();
320    m->Release();
321    return hr;
322  }
323  cb->Release();
324  IStream *pStream;
325  hr = m->BindToStorage(b, 0, IID_IStream, (void **)&pStream);
326  if (SUCCEEDED(hr)) {
327    if (pStream)
328      pStream->Release();
329  }
330  if (hr == MK_S_ASYNCHRONOUS)
331    hr = S_OK;
332  m->Release();
333  b->Release();
334  return hr;
335}
336
337struct QuitInfo {
338  const XML_Char *url;
339  HRESULT hr;
340  int stop;
341};
342
343static void
344winPerror(const XML_Char *url, HRESULT hr)
345{
346  LPVOID buf;
347  if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER
348		    | FORMAT_MESSAGE_FROM_HMODULE,
349		    GetModuleHandleA("urlmon.dll"),
350		    hr,
351		    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
352		    (LPTSTR) &buf,
353		    0,
354		    NULL)
355      || FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER
356		      | FORMAT_MESSAGE_FROM_SYSTEM,
357		      0,
358		      hr,
359		      MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
360		      (LPTSTR) &buf,
361		      0,
362		      NULL)) {
363    /* The system error messages seem to end with a newline. */
364    _ftprintf(stderr, _T("%s: %s"), url, buf);
365    fflush(stderr);
366    LocalFree(buf);
367  }
368  else
369    _ftprintf(stderr, _T("%s: error %x\n"), url, hr);
370}
371
372static void
373threadQuit(void *p, HRESULT hr)
374{
375  QuitInfo *qi = (QuitInfo *)p;
376  qi->hr = hr;
377  qi->stop = 1;
378}
379
380extern "C"
381int
382XML_URLInit(void)
383{
384  return SUCCEEDED(CoInitialize(0));
385}
386
387extern "C"
388void
389XML_URLUninit(void)
390{
391  CoUninitialize();
392}
393
394static int
395processURL(XML_Parser parser, IMoniker *baseMoniker,
396           const XML_Char *url)
397{
398  QuitInfo qi;
399  qi.stop = 0;
400  qi.url = url;
401
402  XML_SetBase(parser, url);
403  HRESULT hr = openStream(parser, baseMoniker, url, threadQuit, &qi);
404  if (FAILED(hr)) {
405    winPerror(url, hr);
406    return 0;
407  }
408  else if (FAILED(qi.hr)) {
409    winPerror(url, qi.hr);
410    return 0;
411  }
412  MSG msg;
413  while (!qi.stop && GetMessage (&msg, NULL, 0, 0)) {
414    TranslateMessage (&msg);
415    DispatchMessage (&msg);
416  }
417  return 1;
418}
419
420extern "C"
421int
422XML_ProcessURL(XML_Parser parser,
423               const XML_Char *url,
424               unsigned flags)
425{
426  return processURL(parser, 0, url);
427}
428