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