| /* | 
 |  * 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 (strlenW(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); | 
 |         } | 
 |         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; | 
 |     if (This->grfBINDF & BINDF_NEEDFILE) | 
 |         request_flags |= INTERNET_FLAG_NEED_FILE; | 
 |     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); | 
 |         } | 
 |  | 
 |     if(This->grfBINDF & BINDF_NEEDFILE) { | 
 |         WCHAR cache_file[MAX_PATH]; | 
 |         DWORD buflen = sizeof(cache_file); | 
 |  | 
 |         if(InternetQueryOptionW(This->request, INTERNET_OPTION_DATAFILE_NAME, | 
 |                                 cache_file, &buflen)) | 
 |         { | 
 |             IInternetProtocolSink_ReportProgress(This->protocol_sink, | 
 |                                                  BINDSTATUS_CACHEFILENAMEAVAILABLE, | 
 |                                                  cache_file); | 
 |         }else { | 
 |             FIXME("Could not get cache file\n"); | 
 |         } | 
 |     } | 
 |  | 
 |         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; | 
 | } |