diff --git a/dlls/urlmon/http.c b/dlls/urlmon/http.c index 5d102a60f1d..c7d463bdc87 100644 --- a/dlls/urlmon/http.c +++ b/dlls/urlmon/http.c @@ -144,14 +144,6 @@ static void HTTPPROTOCOL_ReportData(HttpProtocol *This) } } -static void HTTPPROTOCOL_AllDataRead(HttpProtocol *This) -{ - if (!(This->base.flags & FLAG_ALL_DATA_READ)) - This->base.flags |= FLAG_ALL_DATA_READ; - HTTPPROTOCOL_ReportData(This); - HTTPPROTOCOL_ReportResult(This, S_OK); -} - static void CALLBACK HTTPPROTOCOL_InternetStatusCallback( HINTERNET hInternet, DWORD_PTR dwContext, DWORD dwInternetStatus, LPVOID lpvStatusInformation, DWORD dwStatusInformationLength) @@ -735,80 +727,10 @@ static HRESULT WINAPI HttpProtocol_Read(IInternetProtocol *iface, void *pv, ULONG cb, ULONG *pcbRead) { HttpProtocol *This = PROTOCOL_THIS(iface); - ULONG read = 0, len = 0; - HRESULT hres = S_FALSE; TRACE("(%p)->(%p %u %p)\n", This, pv, cb, pcbRead); - if (!(This->base.flags & FLAG_REQUEST_COMPLETE)) - { - hres = E_PENDING; - } - else while (!(This->base.flags & FLAG_ALL_DATA_READ) && - read < cb) - { - if (This->base.available_bytes == 0) - { - /* InternetQueryDataAvailable may immediately fork and perform its asynchronous - * read, so clear the flag _before_ calling so it does not incorrectly get cleared - * after the status callback is called */ - This->base.flags &= ~FLAG_REQUEST_COMPLETE; - if (!InternetQueryDataAvailable(This->base.request, &This->base.available_bytes, 0, 0)) - { - if (GetLastError() == ERROR_IO_PENDING) - { - hres = E_PENDING; - } - else - { - WARN("InternetQueryDataAvailable failed: %d\n", GetLastError()); - hres = INET_E_DATA_NOT_AVAILABLE; - HTTPPROTOCOL_ReportResult(This, hres); - } - goto done; - } - else if (This->base.available_bytes == 0) - { - HTTPPROTOCOL_AllDataRead(This); - } - } - else - { - if (!InternetReadFile(This->base.request, ((BYTE *)pv)+read, - This->base.available_bytes > cb-read ? - cb-read : This->base.available_bytes, &len)) - { - WARN("InternetReadFile failed: %d\n", GetLastError()); - hres = INET_E_DOWNLOAD_FAILURE; - HTTPPROTOCOL_ReportResult(This, hres); - goto done; - } - else if (len == 0) - { - HTTPPROTOCOL_AllDataRead(This); - } - else - { - read += len; - This->base.current_position += len; - This->base.available_bytes -= len; - } - } - } - - /* Per MSDN this should be if (read == cb), but native returns S_OK - * if any bytes were read, so we will too */ - if (read) - hres = S_OK; - -done: - if (pcbRead) - *pcbRead = read; - - if (hres != E_PENDING) - This->base.flags |= FLAG_REQUEST_COMPLETE; - - return hres; + return protocol_read(&This->base, pv, cb, pcbRead); } static HRESULT WINAPI HttpProtocol_Seek(IInternetProtocol *iface, LARGE_INTEGER dlibMove, diff --git a/dlls/urlmon/protocol.c b/dlls/urlmon/protocol.c index a2e801b0a59..46e6264c707 100644 --- a/dlls/urlmon/protocol.c +++ b/dlls/urlmon/protocol.c @@ -23,6 +23,149 @@ WINE_DEFAULT_DEBUG_CHANNEL(urlmon); +/* Flags are needed for, among other things, return HRESULTs from the Read function + * to conform to native. For example, Read returns: + * + * 1. E_PENDING if called before the request has completed, + * (flags = 0) + * 2. S_FALSE after all data has been read and S_OK has been reported, + * (flags = FLAG_REQUEST_COMPLETE | FLAG_ALL_DATA_READ | FLAG_RESULT_REPORTED) + * 3. INET_E_DATA_NOT_AVAILABLE if InternetQueryDataAvailable fails. The first time + * this occurs, INET_E_DATA_NOT_AVAILABLE will also be reported to the sink, + * (flags = FLAG_REQUEST_COMPLETE) + * but upon subsequent calls to Read no reporting will take place, yet + * InternetQueryDataAvailable will still be called, and, on failure, + * INET_E_DATA_NOT_AVAILABLE will still be returned. + * (flags = FLAG_REQUEST_COMPLETE | FLAG_RESULT_REPORTED) + * + * FLAG_FIRST_DATA_REPORTED and FLAG_LAST_DATA_REPORTED are needed for proper + * ReportData reporting. For example, if OnResponse returns S_OK, Continue will + * report BSCF_FIRSTDATANOTIFICATION, and when all data has been read Read will + * report BSCF_INTERMEDIATEDATANOTIFICATION|BSCF_LASTDATANOTIFICATION. However, + * if OnResponse does not return S_OK, Continue will not report data, and Read + * will report BSCF_FIRSTDATANOTIFICATION|BSCF_LASTDATANOTIFICATION when all + * data has been read. + */ +#define FLAG_REQUEST_COMPLETE 0x0001 +#define FLAG_FIRST_CONTINUE_COMPLETE 0x0002 +#define FLAG_FIRST_DATA_REPORTED 0x0004 +#define FLAG_ALL_DATA_READ 0x0008 +#define FLAG_LAST_DATA_REPORTED 0x0010 +#define FLAG_RESULT_REPORTED 0x0020 + +static inline HRESULT report_result(Protocol *protocol, HRESULT hres) +{ + if (!(protocol->flags & FLAG_RESULT_REPORTED) && protocol->protocol_sink) { + protocol->flags |= FLAG_RESULT_REPORTED; + IInternetProtocolSink_ReportResult(protocol->protocol_sink, hres, 0, NULL); + } + + return hres; +} + +static void report_data(Protocol *protocol) +{ + DWORD bscf; + + if((protocol->flags & FLAG_LAST_DATA_REPORTED) || !protocol->protocol_sink) + return; + + if(protocol->flags & FLAG_FIRST_DATA_REPORTED) { + bscf = BSCF_INTERMEDIATEDATANOTIFICATION; + }else { + protocol->flags |= FLAG_FIRST_DATA_REPORTED; + bscf = BSCF_FIRSTDATANOTIFICATION; + } + + if(protocol->flags & FLAG_ALL_DATA_READ && !(protocol->flags & FLAG_LAST_DATA_REPORTED)) { + protocol->flags |= FLAG_LAST_DATA_REPORTED; + bscf |= BSCF_LASTDATANOTIFICATION; + } + + IInternetProtocolSink_ReportData(protocol->protocol_sink, bscf, + protocol->current_position+protocol->available_bytes, + protocol->content_length); +} + +static void all_data_read(Protocol *protocol) +{ + protocol->flags |= FLAG_ALL_DATA_READ; + + report_data(protocol); + report_result(protocol, S_OK); +} + +HRESULT protocol_read(Protocol *protocol, void *buf, ULONG size, ULONG *read_ret) +{ + ULONG read = 0; + BOOL res; + HRESULT hres = S_FALSE; + + if(!(protocol->flags & FLAG_REQUEST_COMPLETE)) { + *read_ret = 0; + return E_PENDING; + } + + if(protocol->flags & FLAG_ALL_DATA_READ) { + *read_ret = 0; + return S_FALSE; + } + + while(read < size) { + if(protocol->available_bytes) { + ULONG len; + + res = InternetReadFile(protocol->request, ((BYTE *)buf)+read, + protocol->available_bytes > size-read ? size-read : protocol->available_bytes, &len); + if(!res) { + WARN("InternetReadFile failed: %d\n", GetLastError()); + hres = INET_E_DOWNLOAD_FAILURE; + report_result(protocol, hres); + break; + } + + if(!len) { + all_data_read(protocol); + break; + } + + read += len; + protocol->current_position += len; + protocol->available_bytes -= len; + }else { + /* InternetQueryDataAvailable may immediately fork and perform its asynchronous + * read, so clear the flag _before_ calling so it does not incorrectly get cleared + * after the status callback is called */ + protocol->flags &= ~FLAG_REQUEST_COMPLETE; + res = InternetQueryDataAvailable(protocol->request, &protocol->available_bytes, 0, 0); + if(!res) { + if (GetLastError() == ERROR_IO_PENDING) { + hres = E_PENDING; + }else { + WARN("InternetQueryDataAvailable failed: %d\n", GetLastError()); + hres = INET_E_DATA_NOT_AVAILABLE; + report_result(protocol, hres); + } + break; + } + + if(!protocol->available_bytes) { + all_data_read(protocol); + break; + } + } + } + + *read_ret = read; + + if (hres != E_PENDING) + protocol->flags |= FLAG_REQUEST_COMPLETE; + if(FAILED(hres)) + return hres; + + return read ? S_OK : S_FALSE; +} + HRESULT protocol_lock_request(Protocol *protocol) { if (!InternetLockRequestFile(protocol->request, &protocol->lock)) diff --git a/dlls/urlmon/urlmon_main.h b/dlls/urlmon/urlmon_main.h index 6519630efbb..a75c1ed21ab 100644 --- a/dlls/urlmon/urlmon_main.h +++ b/dlls/urlmon/urlmon_main.h @@ -106,6 +106,7 @@ struct ProtocolVtbl { void (*close_connection)(Protocol*); }; +HRESULT protocol_read(Protocol*,void*,ULONG,ULONG*); HRESULT protocol_lock_request(Protocol*); HRESULT protocol_unlock_request(Protocol*); void protocol_close_connection(Protocol*);