From dab80a8d9f3ffe5a8362578da5ae80e6781a9c2b Mon Sep 17 00:00:00 2001 From: Misha Koshelev Date: Tue, 10 Jul 2007 10:52:03 -0500 Subject: [PATCH] urlmon: Implement HttpProtocol. --- dlls/urlmon/http.c | 560 ++++++++++++++++++++++++++++++++++- dlls/urlmon/tests/protocol.c | 4 +- 2 files changed, 548 insertions(+), 16 deletions(-) diff --git a/dlls/urlmon/http.c b/dlls/urlmon/http.c index f7613140959..c501f059baa 100644 --- a/dlls/urlmon/http.c +++ b/dlls/urlmon/http.c @@ -1,5 +1,6 @@ /* * 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 @@ -16,6 +17,12 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ +/* + * TODO: + * - Handle redirects as native. + * - Add support for non-GET requests (e.g., POST). + */ + #include #define COBJMACROS @@ -25,21 +32,177 @@ #include "winuser.h" #include "ole2.h" #include "urlmon.h" +#include "wininet.h" #include "urlmon_main.h" #include "wine/debug.h" +#include "wine/unicode.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_DATA_REPORTED 0x2 +#define FLAG_ALL_DATA_READ 0x4 +#define FLAG_LAST_DATA_REPORTED 0x8 +#define FLAG_RESULT_REPORTED 0x10 + typedef struct { const IInternetProtocolVtbl *lpInternetProtocolVtbl; const IInternetPriorityVtbl *lpInternetPriorityVtbl; + DWORD flags; + IInternetProtocolSink *protocol_sink; + IHttpNegotiate *http_negotiate; + HINTERNET internet, connect, request; + HANDLE lock; + ULONG current_position, content_length, available_bytes; LONG priority; LONG ref; } HttpProtocol; +/* + * 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->protocol_sink) + { + IInternetProtocolSink_Release(This->protocol_sink); + This->protocol_sink = 0; + } + if (This->http_negotiate) + { + IHttpNegotiate_Release(This->http_negotiate); + This->http_negotiate = 0; + } + if (This->request) + InternetCloseHandle(This->request); + CloseHandle(This->connect); + CloseHandle(This->internet); + This->request = This->connect = This->internet = 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; + data.pData = (LPVOID)BINDSTATUS_DOWNLOADINGDATA; + IInternetProtocolSink_Switch(This->protocol_sink, &data); + return; + default: + WARN("Unhandled Internet status callback %d\n", dwInternetStatus); + return; + } + + IInternetProtocolSink_ReportProgress(This->protocol_sink, ulStatusCode, (LPWSTR)lpvStatusInformation); +} + +static inline LPWSTR strndupW(LPWSTR string, int len) +{ + LPWSTR ret = HeapAlloc(GetProcessHeap(), 0, (len+1)*sizeof(WCHAR)); + if (ret) + { + 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) @@ -89,6 +252,7 @@ static ULONG WINAPI HttpProtocol_Release(IInternetProtocol *iface) TRACE("(%p) ref=%d\n", This, ref); if(!ref) { + HTTPPROTOCOL_Close(This); HeapFree(GetProcessHeap(), 0, This); URLMON_UnlockModule(); @@ -102,16 +266,310 @@ static HRESULT WINAPI HttpProtocol_Start(IInternetProtocol *iface, LPCWSTR szUrl DWORD grfPI, DWORD dwReserved) { HttpProtocol *This = PROTOCOL_THIS(iface); - FIXME("(%p)->(%s %p %p %08x %d)\n", This, debugstr_w(szUrl), pOIProtSink, + URL_COMPONENTSW url; + BINDINFO bindinfo; + DWORD grfBINDF = 0, len = 0; + ULONG num = 0; + IServiceProvider *service_provider = 0; + IHttpNegotiate2 *http_negotiate2 = 0; + LPWSTR host = 0, path = 0, user = 0, pass = 0, addl_header = 0; + BYTE security_id[512]; + LPOLESTR user_agent, accept_mimes[257]; + HRESULT hres; + + static const WCHAR wszHttp[] = {'h','t','t','p',':'}; + 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}; + + TRACE("(%p)->(%s %p %p %08x %d)\n", This, debugstr_w(szUrl), pOIProtSink, pOIBindInfo, grfPI, dwReserved); - return E_NOTIMPL; + + memset(&bindinfo, 0, sizeof(bindinfo)); + bindinfo.cbSize = sizeof(BINDINFO); + hres = IInternetBindInfo_GetBindInfo(pOIBindInfo, &grfBINDF, &bindinfo); + 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, ICU_ESCAPE, &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; + + 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 = HeapAlloc(GetProcessHeap(), 0, 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)); + } + HeapFree(GetProcessHeap(), 0, 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; + } + + IInternetProtocolSink_AddRef(pOIProtSink); + This->protocol_sink = pOIProtSink; + + /* 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; + + This->request = HttpOpenRequestW(This->connect, NULL, path, NULL, NULL, + (LPCWSTR *)accept_mimes, 0, (DWORD)This); + if (!This->request) + { + WARN("HttpOpenRequest failed: %d\n", GetLastError()); + hres = INET_E_RESOURCE_NOT_FOUND; + goto done; + } + + hres = IInternetProtocolSink_QueryInterface(pOIProtSink, &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; + } + + 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 (!HttpSendRequestW(This->request, wszHeaders, lstrlenW(wszHeaders), NULL, 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(pOIProtSink, hres, 0, NULL); + HTTPPROTOCOL_Close(This); + } + + CoTaskMemFree(addl_header); + if (http_negotiate2) + IHttpNegotiate2_Release(http_negotiate2); + if (service_provider) + IServiceProvider_Release(service_provider); + + while (num(%p)\n", This, pProtocolData); - return E_NOTIMPL; + 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"); + else if (!This->request) + WARN("Expected request to be non-NULL\n"); + else if (!This->http_negotiate) + WARN("Expected IHttpNegotiate pointer to be non-NULL\n"); + else if (!This->protocol_sink) + WARN("Expected IInternetProtocolSink pointer to be non-NULL\n"); + else 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 = HeapAlloc(GetProcessHeap(), 0, 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 = HeapAlloc(GetProcessHeap(), 0, len)) || + !HttpQueryInfoW(This->request, HTTP_QUERY_CONTENT_TYPE, content_type, &len, NULL)) + { + WARN("HttpQueryInfo failed: %d\n", GetLastError()); + IInternetProtocolSink_ReportProgress(This->protocol_sink, + BINDSTATUS_MIMETYPEAVAILABLE, + wszDefaultContentType); + } + else + { + IInternetProtocolSink_ReportProgress(This->protocol_sink, + BINDSTATUS_MIMETYPEAVAILABLE, + content_type); + } + + len = 0; + if ((!HttpQueryInfoW(This->request, HTTP_QUERY_CONTENT_LENGTH, content_length, &len, NULL) && + GetLastError() != ERROR_INSUFFICIENT_BUFFER) || + !(content_length = HeapAlloc(GetProcessHeap(), 0, 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); + } + + if (!InternetQueryDataAvailable(This->request, &This->available_bytes, 0, 0)) + { + WARN("InternetQueryDataAvailable failed: %d\n", GetLastError()); + HTTPPROTOCOL_ReportResult(This, INET_E_DATA_NOT_AVAILABLE); + } + else + { + HTTPPROTOCOL_ReportData(This); + } + } + +done: + HeapFree(GetProcessHeap(), 0, response_headers); + HeapFree(GetProcessHeap(), 0, content_type); + HeapFree(GetProcessHeap(), 0, content_length); + + /* Returns S_OK on native */ + return S_OK; } static HRESULT WINAPI HttpProtocol_Abort(IInternetProtocol *iface, HRESULT hrReason, @@ -147,8 +605,66 @@ static HRESULT WINAPI HttpProtocol_Read(IInternetProtocol *iface, void *pv, ULONG cb, ULONG *pcbRead) { HttpProtocol *This = PROTOCOL_THIS(iface); - FIXME("(%p)->(%p %u %p)\n", This, pv, cb, pcbRead); - return E_NOTIMPL; + 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) + { + if (!InternetQueryDataAvailable(This->request, &This->available_bytes, 0, 0)) + { + 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; + + return hres; } static HRESULT WINAPI HttpProtocol_Seek(IInternetProtocol *iface, LARGE_INTEGER dlibMove, @@ -162,15 +678,29 @@ static HRESULT WINAPI HttpProtocol_Seek(IInternetProtocol *iface, LARGE_INTEGER static HRESULT WINAPI HttpProtocol_LockRequest(IInternetProtocol *iface, DWORD dwOptions) { HttpProtocol *This = PROTOCOL_THIS(iface); - FIXME("(%p)->(%08x)\n", This, dwOptions); - return E_NOTIMPL; + + 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); - FIXME("(%p)\n", This); - return E_NOTIMPL; + + 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 @@ -253,10 +783,14 @@ HRESULT HttpProtocol_Construct(IUnknown *pUnkOuter, LPVOID *ppobj) ret->lpInternetProtocolVtbl = &HttpProtocolVtbl; ret->lpInternetPriorityVtbl = &HttpPriorityVtbl; - - ret->ref = 1; - + ret->flags = 0; + ret->protocol_sink = 0; + ret->http_negotiate = 0; + ret->internet = ret->connect = ret->request = 0; + ret->lock = 0; + ret->current_position = ret->content_length = ret->available_bytes = 0; ret->priority = 0; + ret->ref = 1; *ppobj = PROTOCOL(ret); diff --git a/dlls/urlmon/tests/protocol.c b/dlls/urlmon/tests/protocol.c index 2f46b5dea7d..366a5436440 100644 --- a/dlls/urlmon/tests/protocol.c +++ b/dlls/urlmon/tests/protocol.c @@ -1057,9 +1057,7 @@ static BOOL http_protocol_start(LPCWSTR url, BOOL is_first) SET_EXPECT(GetRootSecurityId); hres = IInternetProtocol_Start(http_protocol, url, &protocol_sink, &bind_info, 0, 0); - todo_wine { - ok(hres == S_OK, "Start failed: %08x\n", hres); - } + ok(hres == S_OK, "Start failed: %08x\n", hres); if(FAILED(hres)) return FALSE;