| /* |
| * Copyright 2005 Jacek Caban |
| * Copyright 2007 Misha Koshelev |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
| */ |
| |
| /* |
| * TODO: |
| * - Handle redirects as native. |
| */ |
| |
| #include "urlmon_main.h" |
| #include "wininet.h" |
| |
| #include "wine/debug.h" |
| |
| 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 0x1 |
| #define FLAG_FIRST_CONTINUE_COMPLETE 0x2 |
| #define FLAG_FIRST_DATA_REPORTED 0x4 |
| #define FLAG_ALL_DATA_READ 0x8 |
| #define FLAG_LAST_DATA_REPORTED 0x10 |
| #define FLAG_RESULT_REPORTED 0x20 |
| |
| typedef struct { |
| const IInternetProtocolVtbl *lpInternetProtocolVtbl; |
| const IInternetPriorityVtbl *lpInternetPriorityVtbl; |
| |
| DWORD flags, grfBINDF; |
| BINDINFO bind_info; |
| IInternetProtocolSink *protocol_sink; |
| IHttpNegotiate *http_negotiate; |
| HINTERNET internet, connect, request; |
| LPWSTR full_header; |
| HANDLE lock; |
| ULONG current_position, content_length, available_bytes; |
| LONG priority; |
| |
| LONG ref; |
| } HttpProtocol; |
| |
| /* Default headers from native */ |
| static const WCHAR wszHeaders[] = {'A','c','c','e','p','t','-','E','n','c','o','d','i','n','g', |
| ':',' ','g','z','i','p',',',' ','d','e','f','l','a','t','e',0}; |
| |
| /* |
| * Helpers |
| */ |
| |
| static void HTTPPROTOCOL_ReportResult(HttpProtocol *This, HRESULT hres) |
| { |
| if (!(This->flags & FLAG_RESULT_REPORTED) && |
| This->protocol_sink) |
| { |
| This->flags |= FLAG_RESULT_REPORTED; |
| IInternetProtocolSink_ReportResult(This->protocol_sink, hres, 0, NULL); |
| } |
| } |
| |
| static void HTTPPROTOCOL_ReportData(HttpProtocol *This) |
| { |
| DWORD bscf; |
| if (!(This->flags & FLAG_LAST_DATA_REPORTED) && |
| This->protocol_sink) |
| { |
| if (This->flags & FLAG_FIRST_DATA_REPORTED) |
| { |
| bscf = BSCF_INTERMEDIATEDATANOTIFICATION; |
| } |
| else |
| { |
| This->flags |= FLAG_FIRST_DATA_REPORTED; |
| bscf = BSCF_FIRSTDATANOTIFICATION; |
| } |
| if (This->flags & FLAG_ALL_DATA_READ && |
| !(This->flags & FLAG_LAST_DATA_REPORTED)) |
| { |
| This->flags |= FLAG_LAST_DATA_REPORTED; |
| bscf |= BSCF_LASTDATANOTIFICATION; |
| } |
| IInternetProtocolSink_ReportData(This->protocol_sink, bscf, |
| This->current_position+This->available_bytes, |
| This->content_length); |
| } |
| } |
| |
| static void HTTPPROTOCOL_AllDataRead(HttpProtocol *This) |
| { |
| if (!(This->flags & FLAG_ALL_DATA_READ)) |
| This->flags |= FLAG_ALL_DATA_READ; |
| HTTPPROTOCOL_ReportData(This); |
| HTTPPROTOCOL_ReportResult(This, S_OK); |
| } |
| |
| static void HTTPPROTOCOL_Close(HttpProtocol *This) |
| { |
| if (This->http_negotiate) |
| { |
| IHttpNegotiate_Release(This->http_negotiate); |
| This->http_negotiate = 0; |
| } |
| if (This->request) |
| InternetCloseHandle(This->request); |
| if (This->connect) |
| InternetCloseHandle(This->connect); |
| if (This->internet) |
| { |
| InternetCloseHandle(This->internet); |
| This->internet = 0; |
| } |
| if (This->full_header) |
| { |
| if (This->full_header != wszHeaders) |
| heap_free(This->full_header); |
| This->full_header = 0; |
| } |
| This->flags = 0; |
| } |
| |
| static void CALLBACK HTTPPROTOCOL_InternetStatusCallback( |
| HINTERNET hInternet, DWORD_PTR dwContext, DWORD dwInternetStatus, |
| LPVOID lpvStatusInformation, DWORD dwStatusInformationLength) |
| { |
| HttpProtocol *This = (HttpProtocol *)dwContext; |
| PROTOCOLDATA data; |
| ULONG ulStatusCode; |
| |
| switch (dwInternetStatus) |
| { |
| case INTERNET_STATUS_RESOLVING_NAME: |
| ulStatusCode = BINDSTATUS_FINDINGRESOURCE; |
| break; |
| case INTERNET_STATUS_CONNECTING_TO_SERVER: |
| ulStatusCode = BINDSTATUS_CONNECTING; |
| break; |
| case INTERNET_STATUS_SENDING_REQUEST: |
| ulStatusCode = BINDSTATUS_SENDINGREQUEST; |
| break; |
| case INTERNET_STATUS_REQUEST_COMPLETE: |
| This->flags |= FLAG_REQUEST_COMPLETE; |
| /* PROTOCOLDATA same as native */ |
| memset(&data, 0, sizeof(data)); |
| data.dwState = 0xf1000000; |
| if (This->flags & FLAG_FIRST_CONTINUE_COMPLETE) |
| data.pData = (LPVOID)BINDSTATUS_ENDDOWNLOADCOMPONENTS; |
| else |
| data.pData = (LPVOID)BINDSTATUS_DOWNLOADINGDATA; |
| if (This->grfBINDF & BINDF_FROMURLMON) |
| IInternetProtocolSink_Switch(This->protocol_sink, &data); |
| else |
| IInternetProtocol_Continue((IInternetProtocol *)This, &data); |
| return; |
| case INTERNET_STATUS_HANDLE_CREATED: |
| IInternetProtocol_AddRef((IInternetProtocol *)This); |
| return; |
| case INTERNET_STATUS_HANDLE_CLOSING: |
| if (*(HINTERNET *)lpvStatusInformation == This->connect) |
| { |
| This->connect = 0; |
| } |
| else if (*(HINTERNET *)lpvStatusInformation == This->request) |
| { |
| This->request = 0; |
| if (This->protocol_sink) |
| { |
| IInternetProtocolSink_Release(This->protocol_sink); |
| This->protocol_sink = 0; |
| } |
| if (This->bind_info.cbSize) |
| { |
| ReleaseBindInfo(&This->bind_info); |
| memset(&This->bind_info, 0, sizeof(This->bind_info)); |
| } |
| } |
| IInternetProtocol_Release((IInternetProtocol *)This); |
| return; |
| default: |
| WARN("Unhandled Internet status callback %d\n", dwInternetStatus); |
| return; |
| } |
| |
| IInternetProtocolSink_ReportProgress(This->protocol_sink, ulStatusCode, (LPWSTR)lpvStatusInformation); |
| } |
| |
| static inline LPWSTR strndupW(LPCWSTR string, int len) |
| { |
| LPWSTR ret = NULL; |
| if (string && |
| (ret = heap_alloc((len+1)*sizeof(WCHAR))) != NULL) |
| { |
| memcpy(ret, string, len*sizeof(WCHAR)); |
| ret[len] = 0; |
| } |
| return ret; |
| } |
| |
| /* |
| * Interface implementations |
| */ |
| |
| #define PROTOCOL(x) ((IInternetProtocol*) &(x)->lpInternetProtocolVtbl) |
| #define PRIORITY(x) ((IInternetPriority*) &(x)->lpInternetPriorityVtbl) |
| |
| #define PROTOCOL_THIS(iface) DEFINE_THIS(HttpProtocol, InternetProtocol, iface) |
| |
| static HRESULT WINAPI HttpProtocol_QueryInterface(IInternetProtocol *iface, REFIID riid, void **ppv) |
| { |
| HttpProtocol *This = PROTOCOL_THIS(iface); |
| |
| *ppv = NULL; |
| if(IsEqualGUID(&IID_IUnknown, riid)) { |
| TRACE("(%p)->(IID_IUnknown %p)\n", This, ppv); |
| *ppv = PROTOCOL(This); |
| }else if(IsEqualGUID(&IID_IInternetProtocolRoot, riid)) { |
| TRACE("(%p)->(IID_IInternetProtocolRoot %p)\n", This, ppv); |
| *ppv = PROTOCOL(This); |
| }else if(IsEqualGUID(&IID_IInternetProtocol, riid)) { |
| TRACE("(%p)->(IID_IInternetProtocol %p)\n", This, ppv); |
| *ppv = PROTOCOL(This); |
| }else if(IsEqualGUID(&IID_IInternetPriority, riid)) { |
| TRACE("(%p)->(IID_IInternetPriority %p)\n", This, ppv); |
| *ppv = PRIORITY(This); |
| } |
| |
| if(*ppv) { |
| IInternetProtocol_AddRef(iface); |
| return S_OK; |
| } |
| |
| WARN("not supported interface %s\n", debugstr_guid(riid)); |
| return E_NOINTERFACE; |
| } |
| |
| static ULONG WINAPI HttpProtocol_AddRef(IInternetProtocol *iface) |
| { |
| HttpProtocol *This = PROTOCOL_THIS(iface); |
| LONG ref = InterlockedIncrement(&This->ref); |
| TRACE("(%p) ref=%d\n", This, ref); |
| return ref; |
| } |
| |
| static ULONG WINAPI HttpProtocol_Release(IInternetProtocol *iface) |
| { |
| HttpProtocol *This = PROTOCOL_THIS(iface); |
| LONG ref = InterlockedDecrement(&This->ref); |
| |
| TRACE("(%p) ref=%d\n", This, ref); |
| |
| if(!ref) { |
| HTTPPROTOCOL_Close(This); |
| heap_free(This); |
| |
| URLMON_UnlockModule(); |
| } |
| |
| return ref; |
| } |
| |
| static HRESULT WINAPI HttpProtocol_Start(IInternetProtocol *iface, LPCWSTR szUrl, |
| IInternetProtocolSink *pOIProtSink, IInternetBindInfo *pOIBindInfo, |
| DWORD grfPI, DWORD dwReserved) |
| { |
| HttpProtocol *This = PROTOCOL_THIS(iface); |
| URL_COMPONENTSW url; |
| DWORD len = 0, request_flags = INTERNET_FLAG_KEEP_CONNECTION; |
| ULONG num = 0; |
| IServiceProvider *service_provider = 0; |
| IHttpNegotiate2 *http_negotiate2 = 0; |
| LPWSTR host = 0, path = 0, user = 0, pass = 0, addl_header = 0, |
| post_cookie = 0, optional = 0; |
| BYTE security_id[512]; |
| LPOLESTR user_agent = NULL, accept_mimes[257]; |
| HRESULT hres; |
| |
| static const WCHAR wszHttp[] = {'h','t','t','p',':'}; |
| static const WCHAR wszBindVerb[BINDVERB_CUSTOM][5] = |
| {{'G','E','T',0}, |
| {'P','O','S','T',0}, |
| {'P','U','T',0}}; |
| |
| TRACE("(%p)->(%s %p %p %08x %d)\n", This, debugstr_w(szUrl), pOIProtSink, |
| pOIBindInfo, grfPI, dwReserved); |
| |
| IInternetProtocolSink_AddRef(pOIProtSink); |
| This->protocol_sink = pOIProtSink; |
| |
| memset(&This->bind_info, 0, sizeof(This->bind_info)); |
| This->bind_info.cbSize = sizeof(BINDINFO); |
| hres = IInternetBindInfo_GetBindInfo(pOIBindInfo, &This->grfBINDF, &This->bind_info); |
| if (hres != S_OK) |
| { |
| WARN("GetBindInfo failed: %08x\n", hres); |
| goto done; |
| } |
| |
| if (lstrlenW(szUrl) < sizeof(wszHttp)/sizeof(WCHAR) |
| || memcmp(szUrl, wszHttp, sizeof(wszHttp))) |
| { |
| hres = MK_E_SYNTAX; |
| goto done; |
| } |
| |
| memset(&url, 0, sizeof(url)); |
| url.dwStructSize = sizeof(url); |
| url.dwSchemeLength = url.dwHostNameLength = url.dwUrlPathLength = url.dwUserNameLength = |
| url.dwPasswordLength = 1; |
| if (!InternetCrackUrlW(szUrl, 0, 0, &url)) |
| { |
| hres = MK_E_SYNTAX; |
| goto done; |
| } |
| host = strndupW(url.lpszHostName, url.dwHostNameLength); |
| path = strndupW(url.lpszUrlPath, url.dwUrlPathLength); |
| user = strndupW(url.lpszUserName, url.dwUserNameLength); |
| pass = strndupW(url.lpszPassword, url.dwPasswordLength); |
| if (!url.nPort) |
| url.nPort = INTERNET_DEFAULT_HTTP_PORT; |
| |
| if(!(This->grfBINDF & BINDF_FROMURLMON)) |
| IInternetProtocolSink_ReportProgress(This->protocol_sink, BINDSTATUS_DIRECTBIND, NULL); |
| |
| hres = IInternetBindInfo_GetBindString(pOIBindInfo, BINDSTRING_USER_AGENT, &user_agent, |
| 1, &num); |
| if (hres != S_OK || !num) |
| { |
| CHAR null_char = 0; |
| LPSTR user_agenta = NULL; |
| len = 0; |
| if ((hres = ObtainUserAgentString(0, &null_char, &len)) != E_OUTOFMEMORY) |
| { |
| WARN("ObtainUserAgentString failed: %08x\n", hres); |
| } |
| else if (!(user_agenta = heap_alloc(len*sizeof(CHAR)))) |
| { |
| WARN("Out of memory\n"); |
| } |
| else if ((hres = ObtainUserAgentString(0, user_agenta, &len)) != S_OK) |
| { |
| WARN("ObtainUserAgentString failed: %08x\n", hres); |
| } |
| else |
| { |
| if (!(user_agent = CoTaskMemAlloc((len)*sizeof(WCHAR)))) |
| WARN("Out of memory\n"); |
| else |
| MultiByteToWideChar(CP_ACP, 0, user_agenta, -1, user_agent, len*sizeof(WCHAR)); |
| } |
| heap_free(user_agenta); |
| } |
| |
| This->internet = InternetOpenW(user_agent, 0, NULL, NULL, INTERNET_FLAG_ASYNC); |
| if (!This->internet) |
| { |
| WARN("InternetOpen failed: %d\n", GetLastError()); |
| hres = INET_E_NO_SESSION; |
| goto done; |
| } |
| |
| /* Native does not check for success of next call, so we won't either */ |
| InternetSetStatusCallbackW(This->internet, HTTPPROTOCOL_InternetStatusCallback); |
| |
| This->connect = InternetConnectW(This->internet, host, url.nPort, user, |
| pass, INTERNET_SERVICE_HTTP, 0, (DWORD)This); |
| if (!This->connect) |
| { |
| WARN("InternetConnect failed: %d\n", GetLastError()); |
| hres = INET_E_CANNOT_CONNECT; |
| goto done; |
| } |
| |
| num = sizeof(accept_mimes)/sizeof(accept_mimes[0])-1; |
| hres = IInternetBindInfo_GetBindString(pOIBindInfo, BINDSTRING_ACCEPT_MIMES, |
| accept_mimes, |
| num, &num); |
| if (hres != S_OK) |
| { |
| WARN("GetBindString BINDSTRING_ACCEPT_MIMES failed: %08x\n", hres); |
| hres = INET_E_NO_VALID_MEDIA; |
| goto done; |
| } |
| accept_mimes[num] = 0; |
| |
| if (This->grfBINDF & BINDF_NOWRITECACHE) |
| request_flags |= INTERNET_FLAG_NO_CACHE_WRITE; |
| This->request = HttpOpenRequestW(This->connect, This->bind_info.dwBindVerb < BINDVERB_CUSTOM ? |
| wszBindVerb[This->bind_info.dwBindVerb] : |
| This->bind_info.szCustomVerb, |
| path, NULL, NULL, (LPCWSTR *)accept_mimes, |
| request_flags, (DWORD)This); |
| if (!This->request) |
| { |
| WARN("HttpOpenRequest failed: %d\n", GetLastError()); |
| hres = INET_E_RESOURCE_NOT_FOUND; |
| goto done; |
| } |
| |
| hres = IInternetProtocolSink_QueryInterface(This->protocol_sink, &IID_IServiceProvider, |
| (void **)&service_provider); |
| if (hres != S_OK) |
| { |
| WARN("IInternetProtocolSink_QueryInterface IID_IServiceProvider failed: %08x\n", hres); |
| goto done; |
| } |
| |
| hres = IServiceProvider_QueryService(service_provider, &IID_IHttpNegotiate, |
| &IID_IHttpNegotiate, (void **)&This->http_negotiate); |
| if (hres != S_OK) |
| { |
| WARN("IServiceProvider_QueryService IID_IHttpNegotiate failed: %08x\n", hres); |
| goto done; |
| } |
| |
| hres = IHttpNegotiate_BeginningTransaction(This->http_negotiate, szUrl, wszHeaders, |
| 0, &addl_header); |
| if (hres != S_OK) |
| { |
| WARN("IHttpNegotiate_BeginningTransaction failed: %08x\n", hres); |
| goto done; |
| } |
| else if (addl_header == NULL) |
| { |
| This->full_header = (LPWSTR)wszHeaders; |
| } |
| else |
| { |
| int len_addl_header = lstrlenW(addl_header); |
| This->full_header = heap_alloc(len_addl_header*sizeof(WCHAR)+sizeof(wszHeaders)); |
| if (!This->full_header) |
| { |
| WARN("Out of memory\n"); |
| hres = E_OUTOFMEMORY; |
| goto done; |
| } |
| lstrcpyW(This->full_header, addl_header); |
| lstrcpyW(&This->full_header[len_addl_header], wszHeaders); |
| } |
| |
| hres = IServiceProvider_QueryService(service_provider, &IID_IHttpNegotiate2, |
| &IID_IHttpNegotiate2, (void **)&http_negotiate2); |
| if (hres != S_OK) |
| { |
| WARN("IServiceProvider_QueryService IID_IHttpNegotiate2 failed: %08x\n", hres); |
| /* No goto done as per native */ |
| } |
| else |
| { |
| len = sizeof(security_id)/sizeof(security_id[0]); |
| hres = IHttpNegotiate2_GetRootSecurityId(http_negotiate2, security_id, &len, 0); |
| if (hres != S_OK) |
| { |
| WARN("IHttpNegotiate2_GetRootSecurityId failed: %08x\n", hres); |
| /* No goto done as per native */ |
| } |
| } |
| |
| /* FIXME: Handle security_id. Native calls undocumented function IsHostInProxyBypassList. */ |
| |
| if (This->bind_info.dwBindVerb == BINDVERB_POST) |
| { |
| num = 0; |
| hres = IInternetBindInfo_GetBindString(pOIBindInfo, BINDSTRING_POST_COOKIE, &post_cookie, |
| 1, &num); |
| if (hres == S_OK && num && |
| !InternetSetOptionW(This->request, INTERNET_OPTION_SECONDARY_CACHE_KEY, |
| post_cookie, lstrlenW(post_cookie))) |
| { |
| WARN("InternetSetOption INTERNET_OPTION_SECONDARY_CACHE_KEY failed: %d\n", |
| GetLastError()); |
| } |
| } |
| |
| if (This->bind_info.dwBindVerb != BINDVERB_GET) |
| { |
| /* Native does not use GlobalLock/GlobalUnlock, so we won't either */ |
| if (This->bind_info.stgmedData.tymed != TYMED_HGLOBAL) |
| WARN("Expected This->bind_info.stgmedData.tymed to be TYMED_HGLOBAL, not %d\n", |
| This->bind_info.stgmedData.tymed); |
| else |
| optional = (LPWSTR)This->bind_info.stgmedData.u.hGlobal; |
| } |
| if (!HttpSendRequestW(This->request, This->full_header, lstrlenW(This->full_header), |
| optional, |
| optional ? This->bind_info.cbstgmedData : 0) && |
| GetLastError() != ERROR_IO_PENDING) |
| { |
| WARN("HttpSendRequest failed: %d\n", GetLastError()); |
| hres = INET_E_DOWNLOAD_FAILURE; |
| goto done; |
| } |
| |
| hres = S_OK; |
| done: |
| if (hres != S_OK) |
| { |
| IInternetProtocolSink_ReportResult(This->protocol_sink, hres, 0, NULL); |
| HTTPPROTOCOL_Close(This); |
| } |
| |
| CoTaskMemFree(post_cookie); |
| CoTaskMemFree(addl_header); |
| if (http_negotiate2) |
| IHttpNegotiate2_Release(http_negotiate2); |
| if (service_provider) |
| IServiceProvider_Release(service_provider); |
| |
| while (num<sizeof(accept_mimes)/sizeof(accept_mimes[0]) && |
| accept_mimes[num]) |
| CoTaskMemFree(accept_mimes[num++]); |
| CoTaskMemFree(user_agent); |
| |
| heap_free(pass); |
| heap_free(user); |
| heap_free(path); |
| heap_free(host); |
| |
| return hres; |
| } |
| |
| static HRESULT WINAPI HttpProtocol_Continue(IInternetProtocol *iface, PROTOCOLDATA *pProtocolData) |
| { |
| HttpProtocol *This = PROTOCOL_THIS(iface); |
| DWORD len = sizeof(DWORD), status_code; |
| LPWSTR response_headers = 0, content_type = 0, content_length = 0; |
| |
| static const WCHAR wszDefaultContentType[] = |
| {'t','e','x','t','/','h','t','m','l',0}; |
| |
| TRACE("(%p)->(%p)\n", This, pProtocolData); |
| |
| if (!pProtocolData) |
| { |
| WARN("Expected pProtocolData to be non-NULL\n"); |
| return S_OK; |
| } |
| else if (!This->request) |
| { |
| WARN("Expected request to be non-NULL\n"); |
| return S_OK; |
| } |
| else if (!This->http_negotiate) |
| { |
| WARN("Expected IHttpNegotiate pointer to be non-NULL\n"); |
| return S_OK; |
| } |
| else if (!This->protocol_sink) |
| { |
| WARN("Expected IInternetProtocolSink pointer to be non-NULL\n"); |
| return S_OK; |
| } |
| |
| if (pProtocolData->pData == (LPVOID)BINDSTATUS_DOWNLOADINGDATA) |
| { |
| if (!HttpQueryInfoW(This->request, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, |
| &status_code, &len, NULL)) |
| { |
| WARN("HttpQueryInfo failed: %d\n", GetLastError()); |
| } |
| else |
| { |
| len = 0; |
| if ((!HttpQueryInfoW(This->request, HTTP_QUERY_RAW_HEADERS_CRLF, response_headers, &len, |
| NULL) && |
| GetLastError() != ERROR_INSUFFICIENT_BUFFER) || |
| !(response_headers = heap_alloc(len)) || |
| !HttpQueryInfoW(This->request, HTTP_QUERY_RAW_HEADERS_CRLF, response_headers, &len, |
| NULL)) |
| { |
| WARN("HttpQueryInfo failed: %d\n", GetLastError()); |
| } |
| else |
| { |
| HRESULT hres = IHttpNegotiate_OnResponse(This->http_negotiate, status_code, |
| response_headers, NULL, NULL); |
| if (hres != S_OK) |
| { |
| WARN("IHttpNegotiate_OnResponse failed: %08x\n", hres); |
| goto done; |
| } |
| } |
| } |
| |
| len = 0; |
| if ((!HttpQueryInfoW(This->request, HTTP_QUERY_CONTENT_TYPE, content_type, &len, NULL) && |
| GetLastError() != ERROR_INSUFFICIENT_BUFFER) || |
| !(content_type = heap_alloc(len)) || |
| !HttpQueryInfoW(This->request, HTTP_QUERY_CONTENT_TYPE, content_type, &len, NULL)) |
| { |
| WARN("HttpQueryInfo failed: %d\n", GetLastError()); |
| IInternetProtocolSink_ReportProgress(This->protocol_sink, |
| (This->grfBINDF & BINDF_FROMURLMON) ? |
| BINDSTATUS_MIMETYPEAVAILABLE : |
| BINDSTATUS_RAWMIMETYPE, |
| wszDefaultContentType); |
| } |
| else |
| { |
| /* remove the charset, if present */ |
| LPWSTR p = strchrW(content_type, ';'); |
| if (p) *p = '\0'; |
| |
| IInternetProtocolSink_ReportProgress(This->protocol_sink, |
| (This->grfBINDF & BINDF_FROMURLMON) ? |
| BINDSTATUS_MIMETYPEAVAILABLE : |
| BINDSTATUS_RAWMIMETYPE, |
| content_type); |
| } |
| |
| len = 0; |
| if ((!HttpQueryInfoW(This->request, HTTP_QUERY_CONTENT_LENGTH, content_length, &len, NULL) && |
| GetLastError() != ERROR_INSUFFICIENT_BUFFER) || |
| !(content_length = heap_alloc(len)) || |
| !HttpQueryInfoW(This->request, HTTP_QUERY_CONTENT_LENGTH, content_length, &len, NULL)) |
| { |
| WARN("HttpQueryInfo failed: %d\n", GetLastError()); |
| This->content_length = 0; |
| } |
| else |
| { |
| This->content_length = atoiW(content_length); |
| } |
| |
| This->flags |= FLAG_FIRST_CONTINUE_COMPLETE; |
| } |
| |
| if (pProtocolData->pData >= (LPVOID)BINDSTATUS_DOWNLOADINGDATA) |
| { |
| /* 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->flags &= ~FLAG_REQUEST_COMPLETE; |
| if (!InternetQueryDataAvailable(This->request, &This->available_bytes, 0, 0)) |
| { |
| if (GetLastError() != ERROR_IO_PENDING) |
| { |
| This->flags |= FLAG_REQUEST_COMPLETE; |
| WARN("InternetQueryDataAvailable failed: %d\n", GetLastError()); |
| HTTPPROTOCOL_ReportResult(This, INET_E_DATA_NOT_AVAILABLE); |
| } |
| } |
| else |
| { |
| This->flags |= FLAG_REQUEST_COMPLETE; |
| HTTPPROTOCOL_ReportData(This); |
| } |
| } |
| |
| done: |
| heap_free(response_headers); |
| heap_free(content_type); |
| heap_free(content_length); |
| |
| /* Returns S_OK on native */ |
| return S_OK; |
| } |
| |
| static HRESULT WINAPI HttpProtocol_Abort(IInternetProtocol *iface, HRESULT hrReason, |
| DWORD dwOptions) |
| { |
| HttpProtocol *This = PROTOCOL_THIS(iface); |
| FIXME("(%p)->(%08x %08x)\n", This, hrReason, dwOptions); |
| return E_NOTIMPL; |
| } |
| |
| static HRESULT WINAPI HttpProtocol_Terminate(IInternetProtocol *iface, DWORD dwOptions) |
| { |
| HttpProtocol *This = PROTOCOL_THIS(iface); |
| |
| TRACE("(%p)->(%08x)\n", This, dwOptions); |
| HTTPPROTOCOL_Close(This); |
| |
| return S_OK; |
| } |
| |
| static HRESULT WINAPI HttpProtocol_Suspend(IInternetProtocol *iface) |
| { |
| HttpProtocol *This = PROTOCOL_THIS(iface); |
| FIXME("(%p)\n", This); |
| return E_NOTIMPL; |
| } |
| |
| static HRESULT WINAPI HttpProtocol_Resume(IInternetProtocol *iface) |
| { |
| HttpProtocol *This = PROTOCOL_THIS(iface); |
| FIXME("(%p)\n", This); |
| return E_NOTIMPL; |
| } |
| |
| 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->flags & FLAG_REQUEST_COMPLETE)) |
| { |
| hres = E_PENDING; |
| } |
| else while (!(This->flags & FLAG_ALL_DATA_READ) && |
| read < cb) |
| { |
| if (This->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->flags &= ~FLAG_REQUEST_COMPLETE; |
| if (!InternetQueryDataAvailable(This->request, &This->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->available_bytes == 0) |
| { |
| HTTPPROTOCOL_AllDataRead(This); |
| } |
| } |
| else |
| { |
| if (!InternetReadFile(This->request, ((BYTE *)pv)+read, |
| This->available_bytes > cb-read ? |
| cb-read : This->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->current_position += len; |
| This->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->flags |= FLAG_REQUEST_COMPLETE; |
| |
| return hres; |
| } |
| |
| static HRESULT WINAPI HttpProtocol_Seek(IInternetProtocol *iface, LARGE_INTEGER dlibMove, |
| DWORD dwOrigin, ULARGE_INTEGER *plibNewPosition) |
| { |
| HttpProtocol *This = PROTOCOL_THIS(iface); |
| FIXME("(%p)->(%d %d %p)\n", This, dlibMove.u.LowPart, dwOrigin, plibNewPosition); |
| return E_NOTIMPL; |
| } |
| |
| static HRESULT WINAPI HttpProtocol_LockRequest(IInternetProtocol *iface, DWORD dwOptions) |
| { |
| HttpProtocol *This = PROTOCOL_THIS(iface); |
| |
| TRACE("(%p)->(%08x)\n", This, dwOptions); |
| |
| if (!InternetLockRequestFile(This->request, &This->lock)) |
| WARN("InternetLockRequest failed: %d\n", GetLastError()); |
| |
| return S_OK; |
| } |
| |
| static HRESULT WINAPI HttpProtocol_UnlockRequest(IInternetProtocol *iface) |
| { |
| HttpProtocol *This = PROTOCOL_THIS(iface); |
| |
| TRACE("(%p)\n", This); |
| |
| if (This->lock) |
| { |
| if (!InternetUnlockRequestFile(This->lock)) |
| WARN("InternetUnlockRequest failed: %d\n", GetLastError()); |
| This->lock = 0; |
| } |
| |
| return S_OK; |
| } |
| |
| #undef PROTOCOL_THIS |
| |
| #define PRIORITY_THIS(iface) DEFINE_THIS(HttpProtocol, InternetPriority, iface) |
| |
| static HRESULT WINAPI HttpPriority_QueryInterface(IInternetPriority *iface, REFIID riid, void **ppv) |
| { |
| HttpProtocol *This = PRIORITY_THIS(iface); |
| return IInternetProtocol_QueryInterface(PROTOCOL(This), riid, ppv); |
| } |
| |
| static ULONG WINAPI HttpPriority_AddRef(IInternetPriority *iface) |
| { |
| HttpProtocol *This = PRIORITY_THIS(iface); |
| return IInternetProtocol_AddRef(PROTOCOL(This)); |
| } |
| |
| static ULONG WINAPI HttpPriority_Release(IInternetPriority *iface) |
| { |
| HttpProtocol *This = PRIORITY_THIS(iface); |
| return IInternetProtocol_Release(PROTOCOL(This)); |
| } |
| |
| static HRESULT WINAPI HttpPriority_SetPriority(IInternetPriority *iface, LONG nPriority) |
| { |
| HttpProtocol *This = PRIORITY_THIS(iface); |
| |
| TRACE("(%p)->(%d)\n", This, nPriority); |
| |
| This->priority = nPriority; |
| return S_OK; |
| } |
| |
| static HRESULT WINAPI HttpPriority_GetPriority(IInternetPriority *iface, LONG *pnPriority) |
| { |
| HttpProtocol *This = PRIORITY_THIS(iface); |
| |
| TRACE("(%p)->(%p)\n", This, pnPriority); |
| |
| *pnPriority = This->priority; |
| return S_OK; |
| } |
| |
| #undef PRIORITY_THIS |
| |
| static const IInternetPriorityVtbl HttpPriorityVtbl = { |
| HttpPriority_QueryInterface, |
| HttpPriority_AddRef, |
| HttpPriority_Release, |
| HttpPriority_SetPriority, |
| HttpPriority_GetPriority |
| }; |
| |
| static const IInternetProtocolVtbl HttpProtocolVtbl = { |
| HttpProtocol_QueryInterface, |
| HttpProtocol_AddRef, |
| HttpProtocol_Release, |
| HttpProtocol_Start, |
| HttpProtocol_Continue, |
| HttpProtocol_Abort, |
| HttpProtocol_Terminate, |
| HttpProtocol_Suspend, |
| HttpProtocol_Resume, |
| HttpProtocol_Read, |
| HttpProtocol_Seek, |
| HttpProtocol_LockRequest, |
| HttpProtocol_UnlockRequest |
| }; |
| |
| HRESULT HttpProtocol_Construct(IUnknown *pUnkOuter, LPVOID *ppobj) |
| { |
| HttpProtocol *ret; |
| |
| TRACE("(%p %p)\n", pUnkOuter, ppobj); |
| |
| URLMON_LockModule(); |
| |
| ret = heap_alloc(sizeof(HttpProtocol)); |
| |
| ret->lpInternetProtocolVtbl = &HttpProtocolVtbl; |
| ret->lpInternetPriorityVtbl = &HttpPriorityVtbl; |
| ret->flags = ret->grfBINDF = 0; |
| memset(&ret->bind_info, 0, sizeof(ret->bind_info)); |
| ret->protocol_sink = 0; |
| ret->http_negotiate = 0; |
| ret->internet = ret->connect = ret->request = 0; |
| ret->full_header = 0; |
| ret->lock = 0; |
| ret->current_position = ret->content_length = ret->available_bytes = 0; |
| ret->priority = 0; |
| ret->ref = 1; |
| |
| *ppobj = PROTOCOL(ret); |
| |
| return S_OK; |
| } |
| |
| HRESULT HttpSProtocol_Construct(IUnknown *pUnkOuter, LPVOID *ppobj) |
| { |
| FIXME("(%p %p)\n", pUnkOuter, ppobj); |
| return E_NOINTERFACE; |
| } |